From 144ecb3934ab722f31aac3c2406b806b48cc1606 Mon Sep 17 00:00:00 2001 From: ehouse Date: Thu, 18 Dec 2008 13:23:12 +0000 Subject: [PATCH] branch for smartphone release --- xwords4/Makefile | 5 + xwords4/common/.cvsignore | 11 + xwords4/common/Makefile | 5 + xwords4/common/board.c | 3051 +++++++++++ xwords4/common/board.h | 167 + xwords4/common/boarddrw.c | 568 ++ xwords4/common/boardp.h | 300 ++ xwords4/common/commmgr.h | 54 + xwords4/common/comms.c | 1643 ++++++ xwords4/common/comms.h | 138 + xwords4/common/comtypes.h | 241 + xwords4/common/config.mk | 81 + xwords4/common/dawg.h | 88 + xwords4/common/dbgutil.c | 75 + xwords4/common/dbgutil.h | 29 + xwords4/common/dictnry.c | 531 ++ xwords4/common/dictnry.h | 168 + xwords4/common/dictnryp.h | 36 + xwords4/common/dragdrpp.c | 573 ++ xwords4/common/dragdrpp.h | 61 + xwords4/common/draw.c | 366 ++ xwords4/common/draw.h | 309 ++ xwords4/common/engine.c | 1245 +++++ xwords4/common/engine.h | 67 + xwords4/common/game.c | 511 ++ xwords4/common/game.h | 114 + xwords4/common/mempool.c | 276 + xwords4/common/mempool.h | 53 + xwords4/common/memstream.c | 469 ++ xwords4/common/memstream.h | 46 + xwords4/common/model.c | 1837 +++++++ xwords4/common/model.h | 261 + xwords4/common/modelp.h | 80 + xwords4/common/movestak.c | 386 ++ xwords4/common/movestak.h | 95 + xwords4/common/mscore.c | 850 +++ xwords4/common/nwgamest.c | 581 +++ xwords4/common/nwgamest.h | 115 + xwords4/common/pool.c | 240 + xwords4/common/pool.h | 43 + xwords4/common/rules.mk | 31 + xwords4/common/scorebdp.c | 369 ++ xwords4/common/scorebdp.h | 38 + xwords4/common/server.c | 2557 +++++++++ xwords4/common/server.h | 127 + xwords4/common/states.h | 61 + xwords4/common/strutils.c | 283 + xwords4/common/strutils.h | 97 + xwords4/common/tray.c | 687 +++ xwords4/common/util.h | 272 + xwords4/common/virtuals.h | 33 + xwords4/common/vtabmgr.c | 75 + xwords4/common/vtabmgr.h | 50 + xwords4/common/xwproto.h | 53 + xwords4/common/xwstate.h | 29 + xwords4/common/xwstream.h | 164 + xwords4/dawg/Catalan/info.txt | 96 + xwords4/dawg/Czech-CP1250/Makefile | 43 + xwords4/dawg/Czech-CP1250/info.txt | 84 + xwords4/dawg/Czech-ISO8859-2/Makefile | 43 + xwords4/dawg/Czech-ISO8859-2/info.txt | 84 + xwords4/dawg/Danish/.cvsignore | 5 + xwords4/dawg/Danish/Makefile | 42 + xwords4/dawg/Danish/info.txt | 79 + xwords4/dawg/Dutch/.cvsignore | 3 + xwords4/dawg/Dutch/Makefile | 41 + xwords4/dawg/Dutch/info.txt | 72 + xwords4/dawg/English/.cvsignore | 10 + xwords4/dawg/English/BasEnglish.dict.gz | Bin 0 -> 2537 bytes xwords4/dawg/English/BasEnglish2to8.xwd | Bin 0 -> 4171 bytes xwords4/dawg/English/Makefile | 42 + xwords4/dawg/English/Makefile.BasEnglish | 35 + xwords4/dawg/English/Makefile.CSW | 36 + xwords4/dawg/English/Makefile.Enable | 39 + xwords4/dawg/English/Makefile.OWL2 | 36 + xwords4/dawg/English/Makefile.SOWPODS | 36 + xwords4/dawg/English/README.txt | 38 + xwords4/dawg/English/info.txt | 70 + xwords4/dawg/French/.cvsignore | 3 + xwords4/dawg/French/Makefile | 34 + xwords4/dawg/French/Makefile.ODS4 | 34 + xwords4/dawg/French/info.txt | 67 + xwords4/dawg/German/.cvsignore | 3 + xwords4/dawg/German/Makefile | 42 + xwords4/dawg/German/info.txt | 82 + xwords4/dawg/Hex/.cvsignore | 4 + xwords4/dawg/Hex/Makefile | 41 + xwords4/dawg/Hex/info.txt | 61 + xwords4/dawg/Italian/.cvsignore | 3 + xwords4/dawg/Italian/Makefile | 34 + xwords4/dawg/Italian/info.txt | 60 + xwords4/dawg/Makefile | 39 + xwords4/dawg/Makefile.2to8 | 7 + xwords4/dawg/Makefile.langcommon | 285 + xwords4/dawg/Polish/.cvsignore | 8 + xwords4/dawg/Polish/Makefile | 35 + xwords4/dawg/Polish/info.txt | 86 + xwords4/dawg/Portuguese/Makefile.BrOffice | 42 + xwords4/dawg/Portuguese/Makefile.Minho | 42 + xwords4/dawg/Portuguese/info.txt | 70 + xwords4/dawg/Russian/Makefile | 41 + xwords4/dawg/Russian/info.txt | 76 + xwords4/dawg/Spanish/.cvsignore | 7 + xwords4/dawg/Spanish/Makefile | 61 + .../dawg/Spanish/bmps/franklin/large_ch.pbitm | 11 + .../dawg/Spanish/bmps/franklin/large_ll.pbitm | 11 + .../dawg/Spanish/bmps/franklin/large_rr.pbitm | 11 + .../dawg/Spanish/bmps/franklin/small_ch.pbitm | 9 + .../dawg/Spanish/bmps/franklin/small_ll.pbitm | 9 + .../dawg/Spanish/bmps/franklin/small_rr.pbitm | 9 + xwords4/dawg/Spanish/bmps/palm/large_ch.pbitm | 9 + xwords4/dawg/Spanish/bmps/palm/large_ll.pbitm | 9 + xwords4/dawg/Spanish/bmps/palm/large_rr.pbitm | 9 + xwords4/dawg/Spanish/bmps/palm/small_ch.pbitm | 7 + xwords4/dawg/Spanish/bmps/palm/small_ll.pbitm | 7 + xwords4/dawg/Spanish/bmps/palm/small_rr.pbitm | 7 + xwords4/dawg/Spanish/info.txt | 110 + xwords4/dawg/Swedish/.cvsignore | 4 + xwords4/dawg/Swedish/Makefile | 45 + xwords4/dawg/Swedish/info.txt | 75 + xwords4/dawg/allchars.pl | 10 + xwords4/dawg/dawg2dict.pl | 368 ++ xwords4/dawg/dict2dawg.cpp | 1184 +++++ xwords4/dawg/dict2dawg.pl | 857 +++ xwords4/dawg/dictstats.pl | 67 + xwords4/dawg/frank_mkspecials.pl | 47 + xwords4/dawg/mkxwdcab.pl | 87 + xwords4/dawg/palm_mkspecials.pl | 111 + xwords4/dawg/par.pl | 234 + xwords4/dawg/pbitm2bin.pl | 87 + xwords4/dawg/xloc.pl | 47 + xwords4/dawg/xloc.pm | 180 + xwords4/franklin/.cvsignore | 23 + xwords4/franklin/LocalizedStrIncludes.h | 53 + xwords4/franklin/Makefile | 166 + xwords4/franklin/README.txt | 56 + xwords4/franklin/bmps/.cvsignore | 4 + xwords4/franklin/bmps/flip.pbitm | 12 + xwords4/franklin/bmps/lightbulb.pbitm | 11 + xwords4/franklin/bmps/undo.pbitm | 11 + xwords4/franklin/bmps/valuebutton.pbitm | 12 + xwords4/franklin/frankask.cpp | 108 + xwords4/franklin/frankask.h | 40 + xwords4/franklin/frankdict.cpp | 445 ++ xwords4/franklin/frankdict.h | 47 + xwords4/franklin/frankdlist.cpp | 122 + xwords4/franklin/frankdlist.h | 65 + xwords4/franklin/frankdraw.cpp | 638 +++ xwords4/franklin/frankdraw.h | 31 + xwords4/franklin/frankgamesdb.cpp | 313 ++ xwords4/franklin/frankgamesdb.h | 81 + xwords4/franklin/frankids.h | 91 + xwords4/franklin/frankletter.cpp | 119 + xwords4/franklin/frankletter.h | 35 + xwords4/franklin/frankmain.cpp | 1673 ++++++ xwords4/franklin/frankmain.h | 77 + xwords4/franklin/frankpasswd.cpp | 112 + xwords4/franklin/frankpasswd.h | 40 + xwords4/franklin/frankplayer.cpp | 466 ++ xwords4/franklin/frankplayer.h | 66 + xwords4/franklin/franksavedgames.cpp | 231 + xwords4/franklin/franksavedgames.h | 47 + xwords4/franklin/frankshowtext.cpp | 154 + xwords4/franklin/frankshowtext.h | 42 + xwords4/franklin/initial.mom | 10 + xwords4/franklin/pbitm2frank.pl | 124 + xwords4/franklin/xptypes.h | 112 + xwords4/franklin/xwords4.atts | 36 + xwords4/franklin/xwords4_icon.bmp | Bin 0 -> 798 bytes xwords4/linux/.cvsignore | 1 + xwords4/linux/LocalizedStrIncludes.h | 63 + xwords4/linux/Makefile | 213 + xwords4/linux/README.txt | 52 + xwords4/linux/cursesask.c | 128 + xwords4/linux/cursesask.h | 29 + xwords4/linux/cursesdlgutil.c | 92 + xwords4/linux/cursesdlgutil.h | 49 + xwords4/linux/cursesdraw.c | 568 ++ xwords4/linux/cursesletterask.c | 218 + xwords4/linux/cursesletterask.h | 30 + xwords4/linux/cursesmain.c | 1473 ++++++ xwords4/linux/cursesmain.h | 112 + xwords4/linux/filestream.c | 176 + xwords4/linux/filestream.h | 26 + xwords4/linux/flip.xpm | 14 + xwords4/linux/gtkask.c | 38 + xwords4/linux/gtkask.h | 33 + xwords4/linux/gtkdraw.c | 1139 ++++ xwords4/linux/gtkdraw.h | 27 + xwords4/linux/gtkletterask.c | 127 + xwords4/linux/gtkletterask.h | 33 + xwords4/linux/gtkmain.c | 1945 +++++++ xwords4/linux/gtkmain.h | 146 + xwords4/linux/gtknewgame.c | 529 ++ xwords4/linux/gtknewgame.h | 31 + xwords4/linux/gtkntilesask.c | 96 + xwords4/linux/gtkntilesask.h | 31 + xwords4/linux/gtkpasswdask.c | 95 + xwords4/linux/gtkpasswdask.h | 31 + xwords4/linux/hint.xpm | 17 + xwords4/linux/juggle.xpm | 14 + xwords4/linux/linuxbt.c | 518 ++ xwords4/linux/linuxbt.h | 39 + xwords4/linux/linuxdict.c | 397 ++ xwords4/linux/linuxmain.c | 1001 ++++ xwords4/linux/linuxmain.h | 73 + xwords4/linux/linuxserver.c | 34 + xwords4/linux/linuxserver.h | 31 + xwords4/linux/linuxudp.c | 305 ++ xwords4/linux/linuxudp.h | 40 + xwords4/linux/linuxutl.c | 297 ++ xwords4/linux/linuxutl.h | 41 + xwords4/linux/main.h | 140 + xwords4/linux/scripts/playnum.sh | 15 + xwords4/linux/scripts/times.pl | 57 + xwords4/linux/scripts/wordlens.pl | 23 + xwords4/linux/value.xpm | 18 + xwords4/linux/xptypes.h | 120 + xwords4/palm/.cvsignore | 12 + xwords4/palm/Makefile | 337 ++ xwords4/palm/Makefile.PNO | 127 + xwords4/palm/bmps/bts_conn.bmp | Bin 0 -> 774 bytes xwords4/palm/bmps/bts_conn.pbitm | 7 + xwords4/palm/bmps/bts_listen.bmp | Bin 0 -> 774 bytes xwords4/palm/bmps/bts_listen.pbitm | 7 + xwords4/palm/bmps/bts_off.bmp | Bin 0 -> 774 bytes xwords4/palm/bmps/bts_off.pbitm | 7 + xwords4/palm/bmps/bts_seek.bmp | Bin 0 -> 774 bytes xwords4/palm/bmps/bts_seek.pbitm | 7 + xwords4/palm/bmps/downarrow.pbitm | 9 + xwords4/palm/bmps/downarrowhd.pbitm | 18 + xwords4/palm/bmps/flipbutton.pbitm | 8 + xwords4/palm/bmps/flipbuttonhd.pbitm | 16 + xwords4/palm/bmps/lightbulb.pbitm | 11 + xwords4/palm/bmps/lightbulbhd.pbitm | 23 + xwords4/palm/bmps/rightarrow.pbitm | 9 + xwords4/palm/bmps/rightarrowhd.pbitm | 18 + xwords4/palm/bmps/showtray.pbitm | 12 + xwords4/palm/bmps/startmark.pbitm | 8 + xwords4/palm/bmps/startmarkhd.pbitm | 16 + xwords4/palm/bmps/traybuttons.pbitm | 20 + xwords4/palm/bmps/traybuttonshd.pbitm | 32 + xwords4/palm/bmps/valuebutton.pbitm | 10 + xwords4/palm/bmps/valuebuttonhd.pbitm | 21 + xwords4/palm/bmps/xwbandwicon.bmp | Bin 0 -> 1550 bytes xwords4/palm/bmps/xwbandwicon_sm.bmp | Bin 0 -> 486 bytes xwords4/palm/bmps/xwcoloricon.bmp | Bin 0 -> 1550 bytes xwords4/palm/bmps/xwcoloricon_sm.bmp | Bin 0 -> 486 bytes xwords4/palm/bmps/xwords4.pbitm | 22 + xwords4/palm/bmps/xwords4small.pbitm | 9 + xwords4/palm/callback.h | 27 + xwords4/palm/common.rcp.pre | 244 + xwords4/palm/connsdlg.c | 397 ++ xwords4/palm/connsdlg.h | 37 + xwords4/palm/dictlist.c | 600 +++ xwords4/palm/dictlist.h | 55 + xwords4/palm/dictui.c | 286 + xwords4/palm/dictui.h | 34 + xwords4/palm/enter68k.c | 250 + xwords4/palm/fnavgen.c | 186 + xwords4/palm/funcfile.txt | 254 + xwords4/palm/gameutil.c | 206 + xwords4/palm/gameutil.h | 35 + xwords4/palm/gen_pace.pl | 715 +++ xwords4/palm/l10n/StrRes_en_US.pre | 182 + xwords4/palm/l10n/StrRes_es_CT.pre | 140 + xwords4/palm/l10n/StrRes_es_ES.pre | 138 + xwords4/palm/l10n/StrRes_fr_FR.pre | 185 + xwords4/palm/l10n/mkstrsres.c | 78 + xwords4/palm/l10n/xwords4_en_US.rcp.pre | 618 +++ xwords4/palm/l10n/xwords4_es_CT.rcp.pre | 414 ++ xwords4/palm/l10n/xwords4_es_ES.rcp.pre | 413 ++ xwords4/palm/l10n/xwords4_fr_FR.rcp.pre | 621 +++ xwords4/palm/ldscript.arm | 8 + xwords4/palm/newgame.c | 718 +++ xwords4/palm/newgame.h | 30 + xwords4/palm/pace_man.c | 1432 +++++ xwords4/palm/pace_man.h | 174 + xwords4/palm/palmbt.c | 1664 ++++++ xwords4/palm/palmbt.h | 88 + xwords4/palm/palmdbg.c | 320 ++ xwords4/palm/palmdbg.h | 38 + xwords4/palm/palmdict.c | 475 ++ xwords4/palm/palmdict.h | 39 + xwords4/palm/palmdraw.c | 1566 ++++++ xwords4/palm/palmip.c | 380 ++ xwords4/palm/palmip.h | 33 + xwords4/palm/palmir.c | 101 + xwords4/palm/palmir.h | 96 + xwords4/palm/palmmain.c | 4174 +++++++++++++++ xwords4/palm/palmmain.h | 439 ++ xwords4/palm/palmsavg.c | 261 + xwords4/palm/palmsavg.h | 31 + xwords4/palm/palmutil.c | 854 +++ xwords4/palm/palmutil.h | 105 + xwords4/palm/pnolet.c | 161 + xwords4/palm/pnostate.h | 44 + xwords4/palm/prefsdlg.c | 444 ++ xwords4/palm/prefsdlg.h | 43 + xwords4/palm/undef_hack.h | 6 + xwords4/palm/xptypes.h | 138 + xwords4/palm/xwcolors.h | 50 + xwords4/palm/xwords4defines.h | 451 ++ xwords4/relay/.cvsignore | 1 + xwords4/relay/Makefile | 56 + xwords4/relay/configs.cpp | 127 + xwords4/relay/configs.h | 65 + xwords4/relay/cref.cpp | 862 +++ xwords4/relay/cref.h | 210 + xwords4/relay/crefmgr.cpp | 614 +++ xwords4/relay/crefmgr.h | 228 + xwords4/relay/ctrl.cpp | 600 +++ xwords4/relay/ctrl.h | 28 + xwords4/relay/lstnrmgr.cpp | 203 + xwords4/relay/lstnrmgr.h | 73 + xwords4/relay/mlock.h | 130 + xwords4/relay/permid.cpp | 77 + xwords4/relay/permid.h | 50 + xwords4/relay/states.cpp | 236 + xwords4/relay/states.h | 160 + xwords4/relay/timermgr.cpp | 190 + xwords4/relay/timermgr.h | 71 + xwords4/relay/tpool.cpp | 351 ++ xwords4/relay/tpool.h | 85 + xwords4/relay/xwrelay.conf | 37 + xwords4/relay/xwrelay.cpp | 737 +++ xwords4/relay/xwrelay.h | 137 + xwords4/relay/xwrelay.sh | 41 + xwords4/relay/xwrelay_priv.h | 30 + xwords4/symbian/aif/lrgicon.bmp | Bin 0 -> 6494 bytes xwords4/symbian/aif/lrgiconmask.bmp | Bin 0 -> 6494 bytes xwords4/symbian/aif/xwaif.rss | 15 + xwords4/symbian/bmps/downarrow_80.bmp | Bin 0 -> 102 bytes xwords4/symbian/bmps/rightarrow_80.bmp | Bin 0 -> 102 bytes xwords4/symbian/bmps/robot_80.bmp | Bin 0 -> 774 bytes xwords4/symbian/bmps/robotmask_80.bmp | Bin 0 -> 774 bytes xwords4/symbian/bmps/star_80.bmp | Bin 0 -> 102 bytes xwords4/symbian/bmps/turnicon_80.bmp | Bin 0 -> 774 bytes xwords4/symbian/bmps/turniconmask_80.bmp | Bin 0 -> 774 bytes xwords4/symbian/group/.cvsignore | 24 + xwords4/symbian/group/Makefile | 63 + xwords4/symbian/group/bld.inf | 5 + xwords4/symbian/group/bldlinux.mk | 186 + xwords4/symbian/group/bldwin.mk | 303 ++ xwords4/symbian/group/common.mmp | 31 + xwords4/symbian/group/xwords.mmp | 88 + xwords4/symbian/group/xwords.rss | 620 +++ xwords4/symbian/inc/LocalizedStrIncludes.h | 63 + xwords4/symbian/inc/symaskdlg.h | 58 + xwords4/symbian/inc/symblnk.h | 50 + xwords4/symbian/inc/symdict.h | 37 + xwords4/symbian/inc/symdraw.h | 47 + xwords4/symbian/inc/symgamdl.h | 95 + xwords4/symbian/inc/symgamed.h | 48 + xwords4/symbian/inc/symgmdlg.h | 65 + xwords4/symbian/inc/symgmmgr.h | 76 + xwords4/symbian/inc/symrsock.h | 75 + xwords4/symbian/inc/symssock.h | 92 + xwords4/symbian/inc/symutil.h | 36 + xwords4/symbian/inc/xptypes.h | 137 + xwords4/symbian/inc/xwapp.h | 46 + xwords4/symbian/inc/xwappui.h | 72 + xwords4/symbian/inc/xwappview.h | 232 + xwords4/symbian/inc/xwdoc.h | 100 + xwords4/symbian/inc/xwords.hrh | 96 + xwords4/symbian/inc/xwords.pan | 37 + xwords4/symbian/src/.cvsignore | 2 + xwords4/symbian/src/symaskdlg.cpp | 154 + xwords4/symbian/src/symblnk.cpp | 91 + xwords4/symbian/src/symdict.cpp | 382 ++ xwords4/symbian/src/symdraw.cpp | 872 ++++ xwords4/symbian/src/symgamdl.cpp | 435 ++ xwords4/symbian/src/symgamed.cpp | 71 + xwords4/symbian/src/symgmdlg.cpp | 181 + xwords4/symbian/src/symgmmgr.cpp | 257 + xwords4/symbian/src/symrsock.cpp | 144 + xwords4/symbian/src/symssock.cpp | 347 ++ xwords4/symbian/src/symutil.cpp | 213 + xwords4/symbian/src/xwapp.cpp | 44 + xwords4/symbian/src/xwappui.cpp | 98 + xwords4/symbian/src/xwappview.cpp | 1375 +++++ xwords4/symbian/src/xwdoc.cpp | 63 + xwords4/symbian/src/xwmain.cpp | 36 + xwords4/tests/xwgame.pl | 82 + xwords4/wince/.cvsignore | 22 + xwords4/wince/LocalizedStrIncludes.h | 65 + xwords4/wince/Makefile | 229 + xwords4/wince/README.txt | 102 + xwords4/wince/StdAfx.cpp | 8 + xwords4/wince/armrel.mk | 73 + xwords4/wince/bmps/downarro.bmp | Bin 0 -> 106 bytes xwords4/wince/bmps/flip.bmp | Bin 0 -> 126 bytes xwords4/wince/bmps/hint.bmp | Bin 0 -> 126 bytes xwords4/wince/bmps/juggle.bmp | Bin 0 -> 126 bytes xwords4/wince/bmps/origin.bmp | Bin 0 -> 106 bytes xwords4/wince/bmps/rightarrow.bmp | Bin 0 -> 106 bytes xwords4/wince/bmps/values.bmp | Bin 0 -> 126 bytes xwords4/wince/bmps/xwords4_ico_16x16.png | Bin 0 -> 278 bytes xwords4/wince/bmps/xwords4_ico_22x22.png | Bin 0 -> 343 bytes xwords4/wince/bmps/xwords4_ico_32x32.png | Bin 0 -> 352 bytes xwords4/wince/ceaskpwd.c | 83 + xwords4/wince/ceaskpwd.h | 37 + xwords4/wince/ceblank.c | 151 + xwords4/wince/ceblank.h | 39 + xwords4/wince/ceclrsel.c | 468 ++ xwords4/wince/ceclrsel.h | 28 + xwords4/wince/cecondlg.c | 239 + xwords4/wince/cecondlg.h | 37 + xwords4/wince/cedebug.c | 150 + xwords4/wince/cedebug.h | 45 + xwords4/wince/cedefines.h | 37 + xwords4/wince/cedict.c | 797 +++ xwords4/wince/cedict.h | 51 + xwords4/wince/cedraw.c | 1856 +++++++ xwords4/wince/cedraw.h | 38 + xwords4/wince/cefonts.c | 265 + xwords4/wince/cefonts.h | 29 + xwords4/wince/ceginfo.c | 732 +++ xwords4/wince/ceginfo.h | 60 + xwords4/wince/cehntlim.c | 108 + xwords4/wince/cehntlim.h | 39 + xwords4/wince/ceir.h | 32 + xwords4/wince/cemain.c | 3399 ++++++++++++ xwords4/wince/cemain.h | 215 + xwords4/wince/ceprefs.c | 383 ++ xwords4/wince/ceprefs.h | 80 + xwords4/wince/cesockwr.c | 441 ++ xwords4/wince/cesockwr.h | 36 + xwords4/wince/cestrbx.c | 117 + xwords4/wince/cestrbx.h | 37 + xwords4/wince/cesvdgms.c | 512 ++ xwords4/wince/cesvdgms.h | 37 + xwords4/wince/ceutil.c | 907 ++++ xwords4/wince/ceutil.h | 99 + xwords4/wince/debhacks.c | 102 + xwords4/wince/debhacks.h | 94 + xwords4/wince/exe2cab.pl | 76 + xwords4/wince/newres.h | 39 + xwords4/wince/resource.h | 284 + xwords4/wince/scripts/makezip.sh | 86 + xwords4/wince/scripts/testsizes.sh | 58 + xwords4/wince/shared.mk | 46 + xwords4/wince/simrel.mk | 59 + xwords4/wince/stdafx.h | 25 + xwords4/wince/xptypes.h | 146 + xwords4/wince/xwd2cab.pl | 49 + xwords4/wince/xwords.vcp | 4630 +++++++++++++++++ xwords4/wince/xwords4.h | 11 + xwords4/wince/xwords4.ico | Bin 0 -> 1550 bytes xwords4/wince/xwords4.rc | 969 ++++ 450 files changed, 95959 insertions(+) create mode 100644 xwords4/Makefile create mode 100644 xwords4/common/.cvsignore create mode 100644 xwords4/common/Makefile create mode 100644 xwords4/common/board.c create mode 100644 xwords4/common/board.h create mode 100644 xwords4/common/boarddrw.c create mode 100644 xwords4/common/boardp.h create mode 100644 xwords4/common/commmgr.h create mode 100644 xwords4/common/comms.c create mode 100644 xwords4/common/comms.h create mode 100644 xwords4/common/comtypes.h create mode 100644 xwords4/common/config.mk create mode 100644 xwords4/common/dawg.h create mode 100644 xwords4/common/dbgutil.c create mode 100644 xwords4/common/dbgutil.h create mode 100644 xwords4/common/dictnry.c create mode 100644 xwords4/common/dictnry.h create mode 100644 xwords4/common/dictnryp.h create mode 100644 xwords4/common/dragdrpp.c create mode 100644 xwords4/common/dragdrpp.h create mode 100644 xwords4/common/draw.c create mode 100644 xwords4/common/draw.h create mode 100644 xwords4/common/engine.c create mode 100644 xwords4/common/engine.h create mode 100644 xwords4/common/game.c create mode 100644 xwords4/common/game.h create mode 100644 xwords4/common/mempool.c create mode 100644 xwords4/common/mempool.h create mode 100644 xwords4/common/memstream.c create mode 100644 xwords4/common/memstream.h create mode 100644 xwords4/common/model.c create mode 100644 xwords4/common/model.h create mode 100644 xwords4/common/modelp.h create mode 100644 xwords4/common/movestak.c create mode 100644 xwords4/common/movestak.h create mode 100644 xwords4/common/mscore.c create mode 100644 xwords4/common/nwgamest.c create mode 100644 xwords4/common/nwgamest.h create mode 100644 xwords4/common/pool.c create mode 100644 xwords4/common/pool.h create mode 100644 xwords4/common/rules.mk create mode 100644 xwords4/common/scorebdp.c create mode 100644 xwords4/common/scorebdp.h create mode 100644 xwords4/common/server.c create mode 100644 xwords4/common/server.h create mode 100644 xwords4/common/states.h create mode 100644 xwords4/common/strutils.c create mode 100644 xwords4/common/strutils.h create mode 100644 xwords4/common/tray.c create mode 100644 xwords4/common/util.h create mode 100644 xwords4/common/virtuals.h create mode 100644 xwords4/common/vtabmgr.c create mode 100644 xwords4/common/vtabmgr.h create mode 100644 xwords4/common/xwproto.h create mode 100644 xwords4/common/xwstate.h create mode 100644 xwords4/common/xwstream.h create mode 100644 xwords4/dawg/Catalan/info.txt create mode 100644 xwords4/dawg/Czech-CP1250/Makefile create mode 100644 xwords4/dawg/Czech-CP1250/info.txt create mode 100644 xwords4/dawg/Czech-ISO8859-2/Makefile create mode 100644 xwords4/dawg/Czech-ISO8859-2/info.txt create mode 100644 xwords4/dawg/Danish/.cvsignore create mode 100644 xwords4/dawg/Danish/Makefile create mode 100644 xwords4/dawg/Danish/info.txt create mode 100644 xwords4/dawg/Dutch/.cvsignore create mode 100644 xwords4/dawg/Dutch/Makefile create mode 100644 xwords4/dawg/Dutch/info.txt create mode 100644 xwords4/dawg/English/.cvsignore create mode 100644 xwords4/dawg/English/BasEnglish.dict.gz create mode 100644 xwords4/dawg/English/BasEnglish2to8.xwd create mode 100644 xwords4/dawg/English/Makefile create mode 100644 xwords4/dawg/English/Makefile.BasEnglish create mode 100644 xwords4/dawg/English/Makefile.CSW create mode 100644 xwords4/dawg/English/Makefile.Enable create mode 100644 xwords4/dawg/English/Makefile.OWL2 create mode 100644 xwords4/dawg/English/Makefile.SOWPODS create mode 100644 xwords4/dawg/English/README.txt create mode 100644 xwords4/dawg/English/info.txt create mode 100644 xwords4/dawg/French/.cvsignore create mode 100644 xwords4/dawg/French/Makefile create mode 100644 xwords4/dawg/French/Makefile.ODS4 create mode 100755 xwords4/dawg/French/info.txt create mode 100644 xwords4/dawg/German/.cvsignore create mode 100644 xwords4/dawg/German/Makefile create mode 100644 xwords4/dawg/German/info.txt create mode 100644 xwords4/dawg/Hex/.cvsignore create mode 100644 xwords4/dawg/Hex/Makefile create mode 100755 xwords4/dawg/Hex/info.txt create mode 100644 xwords4/dawg/Italian/.cvsignore create mode 100644 xwords4/dawg/Italian/Makefile create mode 100755 xwords4/dawg/Italian/info.txt create mode 100644 xwords4/dawg/Makefile create mode 100644 xwords4/dawg/Makefile.2to8 create mode 100644 xwords4/dawg/Makefile.langcommon create mode 100644 xwords4/dawg/Polish/.cvsignore create mode 100644 xwords4/dawg/Polish/Makefile create mode 100644 xwords4/dawg/Polish/info.txt create mode 100644 xwords4/dawg/Portuguese/Makefile.BrOffice create mode 100644 xwords4/dawg/Portuguese/Makefile.Minho create mode 100644 xwords4/dawg/Portuguese/info.txt create mode 100644 xwords4/dawg/Russian/Makefile create mode 100644 xwords4/dawg/Russian/info.txt create mode 100644 xwords4/dawg/Spanish/.cvsignore create mode 100644 xwords4/dawg/Spanish/Makefile create mode 100644 xwords4/dawg/Spanish/bmps/franklin/large_ch.pbitm create mode 100644 xwords4/dawg/Spanish/bmps/franklin/large_ll.pbitm create mode 100644 xwords4/dawg/Spanish/bmps/franklin/large_rr.pbitm create mode 100644 xwords4/dawg/Spanish/bmps/franklin/small_ch.pbitm create mode 100644 xwords4/dawg/Spanish/bmps/franklin/small_ll.pbitm create mode 100644 xwords4/dawg/Spanish/bmps/franklin/small_rr.pbitm create mode 100644 xwords4/dawg/Spanish/bmps/palm/large_ch.pbitm create mode 100644 xwords4/dawg/Spanish/bmps/palm/large_ll.pbitm create mode 100644 xwords4/dawg/Spanish/bmps/palm/large_rr.pbitm create mode 100644 xwords4/dawg/Spanish/bmps/palm/small_ch.pbitm create mode 100644 xwords4/dawg/Spanish/bmps/palm/small_ll.pbitm create mode 100644 xwords4/dawg/Spanish/bmps/palm/small_rr.pbitm create mode 100644 xwords4/dawg/Spanish/info.txt create mode 100644 xwords4/dawg/Swedish/.cvsignore create mode 100644 xwords4/dawg/Swedish/Makefile create mode 100644 xwords4/dawg/Swedish/info.txt create mode 100755 xwords4/dawg/allchars.pl create mode 100755 xwords4/dawg/dawg2dict.pl create mode 100644 xwords4/dawg/dict2dawg.cpp create mode 100755 xwords4/dawg/dict2dawg.pl create mode 100755 xwords4/dawg/dictstats.pl create mode 100755 xwords4/dawg/frank_mkspecials.pl create mode 100755 xwords4/dawg/mkxwdcab.pl create mode 100755 xwords4/dawg/palm_mkspecials.pl create mode 100755 xwords4/dawg/par.pl create mode 100755 xwords4/dawg/pbitm2bin.pl create mode 100755 xwords4/dawg/xloc.pl create mode 100644 xwords4/dawg/xloc.pm create mode 100644 xwords4/franklin/.cvsignore create mode 100644 xwords4/franklin/LocalizedStrIncludes.h create mode 100644 xwords4/franklin/Makefile create mode 100644 xwords4/franklin/README.txt create mode 100644 xwords4/franklin/bmps/.cvsignore create mode 100644 xwords4/franklin/bmps/flip.pbitm create mode 100644 xwords4/franklin/bmps/lightbulb.pbitm create mode 100644 xwords4/franklin/bmps/undo.pbitm create mode 100644 xwords4/franklin/bmps/valuebutton.pbitm create mode 100644 xwords4/franklin/frankask.cpp create mode 100644 xwords4/franklin/frankask.h create mode 100644 xwords4/franklin/frankdict.cpp create mode 100644 xwords4/franklin/frankdict.h create mode 100755 xwords4/franklin/frankdlist.cpp create mode 100755 xwords4/franklin/frankdlist.h create mode 100644 xwords4/franklin/frankdraw.cpp create mode 100644 xwords4/franklin/frankdraw.h create mode 100644 xwords4/franklin/frankgamesdb.cpp create mode 100644 xwords4/franklin/frankgamesdb.h create mode 100644 xwords4/franklin/frankids.h create mode 100644 xwords4/franklin/frankletter.cpp create mode 100644 xwords4/franklin/frankletter.h create mode 100644 xwords4/franklin/frankmain.cpp create mode 100644 xwords4/franklin/frankmain.h create mode 100644 xwords4/franklin/frankpasswd.cpp create mode 100644 xwords4/franklin/frankpasswd.h create mode 100644 xwords4/franklin/frankplayer.cpp create mode 100644 xwords4/franklin/frankplayer.h create mode 100644 xwords4/franklin/franksavedgames.cpp create mode 100644 xwords4/franklin/franksavedgames.h create mode 100644 xwords4/franklin/frankshowtext.cpp create mode 100644 xwords4/franklin/frankshowtext.h create mode 100644 xwords4/franklin/initial.mom create mode 100755 xwords4/franklin/pbitm2frank.pl create mode 100644 xwords4/franklin/xptypes.h create mode 100644 xwords4/franklin/xwords4.atts create mode 100644 xwords4/franklin/xwords4_icon.bmp create mode 100644 xwords4/linux/.cvsignore create mode 100644 xwords4/linux/LocalizedStrIncludes.h create mode 100644 xwords4/linux/Makefile create mode 100644 xwords4/linux/README.txt create mode 100644 xwords4/linux/cursesask.c create mode 100644 xwords4/linux/cursesask.h create mode 100644 xwords4/linux/cursesdlgutil.c create mode 100644 xwords4/linux/cursesdlgutil.h create mode 100644 xwords4/linux/cursesdraw.c create mode 100644 xwords4/linux/cursesletterask.c create mode 100644 xwords4/linux/cursesletterask.h create mode 100644 xwords4/linux/cursesmain.c create mode 100644 xwords4/linux/cursesmain.h create mode 100644 xwords4/linux/filestream.c create mode 100644 xwords4/linux/filestream.h create mode 100644 xwords4/linux/flip.xpm create mode 100644 xwords4/linux/gtkask.c create mode 100644 xwords4/linux/gtkask.h create mode 100644 xwords4/linux/gtkdraw.c create mode 100644 xwords4/linux/gtkdraw.h create mode 100644 xwords4/linux/gtkletterask.c create mode 100644 xwords4/linux/gtkletterask.h create mode 100644 xwords4/linux/gtkmain.c create mode 100644 xwords4/linux/gtkmain.h create mode 100644 xwords4/linux/gtknewgame.c create mode 100644 xwords4/linux/gtknewgame.h create mode 100644 xwords4/linux/gtkntilesask.c create mode 100644 xwords4/linux/gtkntilesask.h create mode 100644 xwords4/linux/gtkpasswdask.c create mode 100644 xwords4/linux/gtkpasswdask.h create mode 100644 xwords4/linux/hint.xpm create mode 100644 xwords4/linux/juggle.xpm create mode 100644 xwords4/linux/linuxbt.c create mode 100644 xwords4/linux/linuxbt.h create mode 100644 xwords4/linux/linuxdict.c create mode 100644 xwords4/linux/linuxmain.c create mode 100644 xwords4/linux/linuxmain.h create mode 100644 xwords4/linux/linuxserver.c create mode 100644 xwords4/linux/linuxserver.h create mode 100644 xwords4/linux/linuxudp.c create mode 100644 xwords4/linux/linuxudp.h create mode 100644 xwords4/linux/linuxutl.c create mode 100644 xwords4/linux/linuxutl.h create mode 100644 xwords4/linux/main.h create mode 100755 xwords4/linux/scripts/playnum.sh create mode 100755 xwords4/linux/scripts/times.pl create mode 100755 xwords4/linux/scripts/wordlens.pl create mode 100644 xwords4/linux/value.xpm create mode 100644 xwords4/linux/xptypes.h create mode 100644 xwords4/palm/.cvsignore create mode 100644 xwords4/palm/Makefile create mode 100644 xwords4/palm/Makefile.PNO create mode 100644 xwords4/palm/bmps/bts_conn.bmp create mode 100644 xwords4/palm/bmps/bts_conn.pbitm create mode 100644 xwords4/palm/bmps/bts_listen.bmp create mode 100644 xwords4/palm/bmps/bts_listen.pbitm create mode 100644 xwords4/palm/bmps/bts_off.bmp create mode 100644 xwords4/palm/bmps/bts_off.pbitm create mode 100644 xwords4/palm/bmps/bts_seek.bmp create mode 100644 xwords4/palm/bmps/bts_seek.pbitm create mode 100644 xwords4/palm/bmps/downarrow.pbitm create mode 100644 xwords4/palm/bmps/downarrowhd.pbitm create mode 100644 xwords4/palm/bmps/flipbutton.pbitm create mode 100644 xwords4/palm/bmps/flipbuttonhd.pbitm create mode 100644 xwords4/palm/bmps/lightbulb.pbitm create mode 100644 xwords4/palm/bmps/lightbulbhd.pbitm create mode 100644 xwords4/palm/bmps/rightarrow.pbitm create mode 100644 xwords4/palm/bmps/rightarrowhd.pbitm create mode 100644 xwords4/palm/bmps/showtray.pbitm create mode 100644 xwords4/palm/bmps/startmark.pbitm create mode 100644 xwords4/palm/bmps/startmarkhd.pbitm create mode 100644 xwords4/palm/bmps/traybuttons.pbitm create mode 100644 xwords4/palm/bmps/traybuttonshd.pbitm create mode 100644 xwords4/palm/bmps/valuebutton.pbitm create mode 100644 xwords4/palm/bmps/valuebuttonhd.pbitm create mode 100644 xwords4/palm/bmps/xwbandwicon.bmp create mode 100644 xwords4/palm/bmps/xwbandwicon_sm.bmp create mode 100644 xwords4/palm/bmps/xwcoloricon.bmp create mode 100644 xwords4/palm/bmps/xwcoloricon_sm.bmp create mode 100644 xwords4/palm/bmps/xwords4.pbitm create mode 100644 xwords4/palm/bmps/xwords4small.pbitm create mode 100644 xwords4/palm/callback.h create mode 100644 xwords4/palm/common.rcp.pre create mode 100644 xwords4/palm/connsdlg.c create mode 100644 xwords4/palm/connsdlg.h create mode 100644 xwords4/palm/dictlist.c create mode 100644 xwords4/palm/dictlist.h create mode 100644 xwords4/palm/dictui.c create mode 100644 xwords4/palm/dictui.h create mode 100644 xwords4/palm/enter68k.c create mode 100644 xwords4/palm/fnavgen.c create mode 100644 xwords4/palm/funcfile.txt create mode 100644 xwords4/palm/gameutil.c create mode 100644 xwords4/palm/gameutil.h create mode 100755 xwords4/palm/gen_pace.pl create mode 100644 xwords4/palm/l10n/StrRes_en_US.pre create mode 100644 xwords4/palm/l10n/StrRes_es_CT.pre create mode 100644 xwords4/palm/l10n/StrRes_es_ES.pre create mode 100644 xwords4/palm/l10n/StrRes_fr_FR.pre create mode 100644 xwords4/palm/l10n/mkstrsres.c create mode 100644 xwords4/palm/l10n/xwords4_en_US.rcp.pre create mode 100644 xwords4/palm/l10n/xwords4_es_CT.rcp.pre create mode 100644 xwords4/palm/l10n/xwords4_es_ES.rcp.pre create mode 100644 xwords4/palm/l10n/xwords4_fr_FR.rcp.pre create mode 100644 xwords4/palm/ldscript.arm create mode 100644 xwords4/palm/newgame.c create mode 100644 xwords4/palm/newgame.h create mode 100644 xwords4/palm/pace_man.c create mode 100644 xwords4/palm/pace_man.h create mode 100644 xwords4/palm/palmbt.c create mode 100644 xwords4/palm/palmbt.h create mode 100644 xwords4/palm/palmdbg.c create mode 100644 xwords4/palm/palmdbg.h create mode 100644 xwords4/palm/palmdict.c create mode 100644 xwords4/palm/palmdict.h create mode 100644 xwords4/palm/palmdraw.c create mode 100644 xwords4/palm/palmip.c create mode 100644 xwords4/palm/palmip.h create mode 100644 xwords4/palm/palmir.c create mode 100644 xwords4/palm/palmir.h create mode 100644 xwords4/palm/palmmain.c create mode 100644 xwords4/palm/palmmain.h create mode 100644 xwords4/palm/palmsavg.c create mode 100644 xwords4/palm/palmsavg.h create mode 100644 xwords4/palm/palmutil.c create mode 100644 xwords4/palm/palmutil.h create mode 100644 xwords4/palm/pnolet.c create mode 100644 xwords4/palm/pnostate.h create mode 100644 xwords4/palm/prefsdlg.c create mode 100644 xwords4/palm/prefsdlg.h create mode 100644 xwords4/palm/undef_hack.h create mode 100644 xwords4/palm/xptypes.h create mode 100644 xwords4/palm/xwcolors.h create mode 100644 xwords4/palm/xwords4defines.h create mode 100644 xwords4/relay/.cvsignore create mode 100644 xwords4/relay/Makefile create mode 100644 xwords4/relay/configs.cpp create mode 100644 xwords4/relay/configs.h create mode 100644 xwords4/relay/cref.cpp create mode 100644 xwords4/relay/cref.h create mode 100644 xwords4/relay/crefmgr.cpp create mode 100644 xwords4/relay/crefmgr.h create mode 100644 xwords4/relay/ctrl.cpp create mode 100644 xwords4/relay/ctrl.h create mode 100644 xwords4/relay/lstnrmgr.cpp create mode 100644 xwords4/relay/lstnrmgr.h create mode 100644 xwords4/relay/mlock.h create mode 100644 xwords4/relay/permid.cpp create mode 100644 xwords4/relay/permid.h create mode 100644 xwords4/relay/states.cpp create mode 100644 xwords4/relay/states.h create mode 100644 xwords4/relay/timermgr.cpp create mode 100644 xwords4/relay/timermgr.h create mode 100644 xwords4/relay/tpool.cpp create mode 100644 xwords4/relay/tpool.h create mode 100644 xwords4/relay/xwrelay.conf create mode 100644 xwords4/relay/xwrelay.cpp create mode 100644 xwords4/relay/xwrelay.h create mode 100755 xwords4/relay/xwrelay.sh create mode 100644 xwords4/relay/xwrelay_priv.h create mode 100755 xwords4/symbian/aif/lrgicon.bmp create mode 100755 xwords4/symbian/aif/lrgiconmask.bmp create mode 100755 xwords4/symbian/aif/xwaif.rss create mode 100644 xwords4/symbian/bmps/downarrow_80.bmp create mode 100644 xwords4/symbian/bmps/rightarrow_80.bmp create mode 100644 xwords4/symbian/bmps/robot_80.bmp create mode 100644 xwords4/symbian/bmps/robotmask_80.bmp create mode 100644 xwords4/symbian/bmps/star_80.bmp create mode 100755 xwords4/symbian/bmps/turnicon_80.bmp create mode 100755 xwords4/symbian/bmps/turniconmask_80.bmp create mode 100644 xwords4/symbian/group/.cvsignore create mode 100644 xwords4/symbian/group/Makefile create mode 100644 xwords4/symbian/group/bld.inf create mode 100644 xwords4/symbian/group/bldlinux.mk create mode 100755 xwords4/symbian/group/bldwin.mk create mode 100644 xwords4/symbian/group/common.mmp create mode 100644 xwords4/symbian/group/xwords.mmp create mode 100644 xwords4/symbian/group/xwords.rss create mode 100644 xwords4/symbian/inc/LocalizedStrIncludes.h create mode 100644 xwords4/symbian/inc/symaskdlg.h create mode 100644 xwords4/symbian/inc/symblnk.h create mode 100644 xwords4/symbian/inc/symdict.h create mode 100644 xwords4/symbian/inc/symdraw.h create mode 100644 xwords4/symbian/inc/symgamdl.h create mode 100755 xwords4/symbian/inc/symgamed.h create mode 100755 xwords4/symbian/inc/symgmdlg.h create mode 100755 xwords4/symbian/inc/symgmmgr.h create mode 100644 xwords4/symbian/inc/symrsock.h create mode 100644 xwords4/symbian/inc/symssock.h create mode 100644 xwords4/symbian/inc/symutil.h create mode 100644 xwords4/symbian/inc/xptypes.h create mode 100644 xwords4/symbian/inc/xwapp.h create mode 100644 xwords4/symbian/inc/xwappui.h create mode 100644 xwords4/symbian/inc/xwappview.h create mode 100644 xwords4/symbian/inc/xwdoc.h create mode 100644 xwords4/symbian/inc/xwords.hrh create mode 100644 xwords4/symbian/inc/xwords.pan create mode 100644 xwords4/symbian/src/.cvsignore create mode 100644 xwords4/symbian/src/symaskdlg.cpp create mode 100644 xwords4/symbian/src/symblnk.cpp create mode 100644 xwords4/symbian/src/symdict.cpp create mode 100644 xwords4/symbian/src/symdraw.cpp create mode 100644 xwords4/symbian/src/symgamdl.cpp create mode 100755 xwords4/symbian/src/symgamed.cpp create mode 100755 xwords4/symbian/src/symgmdlg.cpp create mode 100755 xwords4/symbian/src/symgmmgr.cpp create mode 100644 xwords4/symbian/src/symrsock.cpp create mode 100644 xwords4/symbian/src/symssock.cpp create mode 100644 xwords4/symbian/src/symutil.cpp create mode 100644 xwords4/symbian/src/xwapp.cpp create mode 100644 xwords4/symbian/src/xwappui.cpp create mode 100644 xwords4/symbian/src/xwappview.cpp create mode 100644 xwords4/symbian/src/xwdoc.cpp create mode 100644 xwords4/symbian/src/xwmain.cpp create mode 100755 xwords4/tests/xwgame.pl create mode 100755 xwords4/wince/.cvsignore create mode 100755 xwords4/wince/LocalizedStrIncludes.h create mode 100644 xwords4/wince/Makefile create mode 100644 xwords4/wince/README.txt create mode 100755 xwords4/wince/StdAfx.cpp create mode 100755 xwords4/wince/armrel.mk create mode 100755 xwords4/wince/bmps/downarro.bmp create mode 100755 xwords4/wince/bmps/flip.bmp create mode 100755 xwords4/wince/bmps/hint.bmp create mode 100644 xwords4/wince/bmps/juggle.bmp create mode 100755 xwords4/wince/bmps/origin.bmp create mode 100755 xwords4/wince/bmps/rightarrow.bmp create mode 100755 xwords4/wince/bmps/values.bmp create mode 100644 xwords4/wince/bmps/xwords4_ico_16x16.png create mode 100644 xwords4/wince/bmps/xwords4_ico_22x22.png create mode 100644 xwords4/wince/bmps/xwords4_ico_32x32.png create mode 100755 xwords4/wince/ceaskpwd.c create mode 100755 xwords4/wince/ceaskpwd.h create mode 100755 xwords4/wince/ceblank.c create mode 100755 xwords4/wince/ceblank.h create mode 100644 xwords4/wince/ceclrsel.c create mode 100644 xwords4/wince/ceclrsel.h create mode 100755 xwords4/wince/cecondlg.c create mode 100755 xwords4/wince/cecondlg.h create mode 100644 xwords4/wince/cedebug.c create mode 100644 xwords4/wince/cedebug.h create mode 100755 xwords4/wince/cedefines.h create mode 100755 xwords4/wince/cedict.c create mode 100755 xwords4/wince/cedict.h create mode 100755 xwords4/wince/cedraw.c create mode 100644 xwords4/wince/cedraw.h create mode 100644 xwords4/wince/cefonts.c create mode 100644 xwords4/wince/cefonts.h create mode 100755 xwords4/wince/ceginfo.c create mode 100755 xwords4/wince/ceginfo.h create mode 100755 xwords4/wince/cehntlim.c create mode 100755 xwords4/wince/cehntlim.h create mode 100755 xwords4/wince/ceir.h create mode 100755 xwords4/wince/cemain.c create mode 100755 xwords4/wince/cemain.h create mode 100755 xwords4/wince/ceprefs.c create mode 100755 xwords4/wince/ceprefs.h create mode 100755 xwords4/wince/cesockwr.c create mode 100755 xwords4/wince/cesockwr.h create mode 100755 xwords4/wince/cestrbx.c create mode 100755 xwords4/wince/cestrbx.h create mode 100644 xwords4/wince/cesvdgms.c create mode 100644 xwords4/wince/cesvdgms.h create mode 100755 xwords4/wince/ceutil.c create mode 100755 xwords4/wince/ceutil.h create mode 100644 xwords4/wince/debhacks.c create mode 100644 xwords4/wince/debhacks.h create mode 100755 xwords4/wince/exe2cab.pl create mode 100755 xwords4/wince/newres.h create mode 100755 xwords4/wince/resource.h create mode 100755 xwords4/wince/scripts/makezip.sh create mode 100755 xwords4/wince/scripts/testsizes.sh create mode 100755 xwords4/wince/shared.mk create mode 100755 xwords4/wince/simrel.mk create mode 100755 xwords4/wince/stdafx.h create mode 100755 xwords4/wince/xptypes.h create mode 100755 xwords4/wince/xwd2cab.pl create mode 100644 xwords4/wince/xwords.vcp create mode 100755 xwords4/wince/xwords4.h create mode 100644 xwords4/wince/xwords4.ico create mode 100755 xwords4/wince/xwords4.rc diff --git a/xwords4/Makefile b/xwords4/Makefile new file mode 100644 index 000000000..5a2d804da --- /dev/null +++ b/xwords4/Makefile @@ -0,0 +1,5 @@ + +tags: + etags $$(find . -name '*.c' -print \ + -o -name '*.h' -print \ + -o -name '*.cpp' -print) diff --git a/xwords4/common/.cvsignore b/xwords4/common/.cvsignore new file mode 100644 index 000000000..fb0305eaa --- /dev/null +++ b/xwords4/common/.cvsignore @@ -0,0 +1,11 @@ +franklin +palm +ARMV4Rel +emulatorDbg +PALM_PNO +obj_linux_memdbg +obj_linux_rel +obj_win32_dbg +obj_win32_rel +obj_wince_dbg +obj_wince_rel diff --git a/xwords4/common/Makefile b/xwords4/common/Makefile new file mode 100644 index 000000000..354070598 --- /dev/null +++ b/xwords4/common/Makefile @@ -0,0 +1,5 @@ +# -*- mode: Makefile; -*- + +clean: + rm -rf $(PLATFORM) + diff --git a/xwords4/common/board.c b/xwords4/common/board.c new file mode 100644 index 000000000..b47448d9a --- /dev/null +++ b/xwords4/common/board.c @@ -0,0 +1,3051 @@ +/* -*- fill-column: 78; compile-command: "cd ../linux && make -j MEMDEBUG=TRUE"; -*- */ +/* + * Copyright 1997 - 2008 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Re: boards that can't fit on the screen. Let's have an assumption, that + * the tray is always either below the board or overlapping its bottom. There + * is never any board visible below the tray. But it's possible to have a + * board small enough that scrolling is necessary even with the tray hidden. + * + * Currently we don't specify the board bounds. We give top,left and the size + * of cells, and the board figures out the bounds. That's probably a mistake. + * Better to give bounds, and maybe a min scale, and let it figure out how + * many cells can be visible. Could it also decide if the tray should overlap + * or be below? Some platforms have to own that decision since the tray is + * narrower than the board. So give them separate bounds-setting functions, + * and let the board code figure out if they overlap. + * + * Problem: the board size must always be a multiple of the scale. The + * platform-specific code has an easy time doing that math. The board can't: + * it'd have to take bounds, then spit them back out slightly modified. It'd + * also have to refuse to work (maybe just assert) if asked to take bounds + * before it had a min_scale. + * + * Another way of looking at it closer to the current: the board's position + * and the tray's bounds determine the board's bounds. If the board's vScale + * times the number of rows places its would-be bottom at or above the bottom + * of the tray, then it's potentially visible. If its would-be bottom is + * above the top of the tray, no scrolling is needed. But if it's below the + * tray entirely then scrolling will happen even with the tray hidden. As + * above, we assume the board never appears below the tray. + */ + +#include "comtypes.h" +#include "board.h" +#include "scorebdp.h" +#include "game.h" +#include "server.h" +#include "comms.h" /* for CHANNEL_NONE */ +#include "dictnry.h" +#include "draw.h" +#include "engine.h" +#include "util.h" +#include "mempool.h" /* debug only */ +#include "memstream.h" +#include "strutils.h" +#include "LocalizedStrIncludes.h" + +#include "boardp.h" +#include "dragdrpp.h" +#include "dbgutil.h" + +#define bEND 0x62454e44 + +#ifdef CPLUS +extern "C" { +#endif + +/****************************** prototypes ******************************/ +static void figureBoardRect( BoardCtxt* board ); +static void forceRectToBoard( const BoardCtxt* board, XP_Rect* rect ); + +static void boardCellChanged( void* board, XP_U16 turn, XP_U16 col, + XP_U16 row, XP_Bool added ); +static void boardTilesChanged( void* board, XP_U16 turn, XP_S16 index1, + XP_S16 index2 ); +static void dictChanged( void* p_board, const DictionaryCtxt* oldDict, + const DictionaryCtxt* newDict ); + +static void boardTurnChanged( void* board ); +static void boardGameOver( void* board ); +static void setArrow( BoardCtxt* board, XP_U16 row, XP_U16 col ); +static void setArrowFor( BoardCtxt* board, XP_U16 player, XP_U16 col, + XP_U16 row ); +static XP_Bool setArrowVisible( BoardCtxt* board, XP_Bool visible ); + +static void invalTradeWindow( BoardCtxt* board, XP_S16 turn, XP_Bool redraw ); +static void setTimerIf( BoardCtxt* board ); +static XP_Bool p_board_timerFired( void* closure, XWTimerReason why ); + +static XP_Bool replaceLastTile( BoardCtxt* board ); +static XP_Bool setTrayVisState( BoardCtxt* board, XW_TrayVisState newState ); +static XP_Bool advanceArrow( BoardCtxt* board ); +static XP_Bool exitTradeMode( BoardCtxt* board ); + +static XP_Bool getArrow( BoardCtxt* board, XP_U16* col, XP_U16* row ); +static XP_Bool setArrowVisibleFor( BoardCtxt* board, XP_U16 player, + XP_Bool visible ); +static XP_Bool board_moveArrow( BoardCtxt* board, XP_Key cursorKey ); + +#ifdef KEY_SUPPORT +static XP_Bool moveKeyTileToBoard( BoardCtxt* board, XP_Key cursorKey, + XP_Bool* gotArrow ); +static XP_S16 keyToIndex( BoardCtxt* board, XP_Key key, Tile* blankFace ); +#endif + +#ifdef KEYBOARD_NAV +static XP_Bool board_moveCursor( BoardCtxt* board, XP_Key cursorKey, + XP_Bool preflightOnly, XP_Bool* up ); +static XP_Bool invalFocusOwner( BoardCtxt* board ); +#else +# define invalFocusOwner(board) +#endif +#ifdef XWFEATURE_SEARCHLIMIT +static void clearCurHintRect( BoardCtxt* board ); + +#else +# define figureHintAtts(b,c,r) HINT_BORDER_NONE +#endif + +/***************************************************************************** + * + ****************************************************************************/ +BoardCtxt* +board_make( MPFORMAL ModelCtxt* model, ServerCtxt* server, DrawCtx* draw, + XW_UtilCtxt* util ) +{ + BoardCtxt* result = (BoardCtxt*)XP_MALLOC( mpool, sizeof( *result ) ); + XP_ASSERT( !!draw ); + XP_ASSERT( !!server ); + XP_ASSERT( !!util ); + XP_ASSERT( !!model ); + + if ( result != NULL ) { + + XP_MEMSET( result, 0, sizeof( *result ) ); + result->selInfo = result->pti; /* equates to selPlayer == 0 */ + + MPASSIGN(result->mpool, mpool); + + result->model = model; + result->server = server; + + result->draw = draw; + result->util = util; + result->gi = util->gameInfo; + XP_ASSERT( !!result->gi ); + + result->trayVisState = TRAY_HIDDEN; + + result->star_row = (XP_U16)(model_numRows(model) / 2); + + /* could just pass in invalCell.... PENDING(eeh) */ + model_setBoardListener( model, boardCellChanged, result ); + model_setTrayListener( model, boardTilesChanged, result ); + model_setDictListener( model, dictChanged, result ); + server_setTurnChangeListener( server, boardTurnChanged, result ); + server_setGameOverListener( server, boardGameOver, result ); + + setTimerIf( result ); + +#ifdef KEYBOARD_NAV + { + /* set up some useful initial values */ + XP_U16 ii; + for ( ii = 0; ii < MAX_NUM_PLAYERS; ++ii ) { + PerTurnInfo* pti = result->pti + ii; + pti->trayCursorLoc = 1; + pti->bdCursor.col = 5; + pti->bdCursor.row = 7; + } + } +#endif + + } + return result; +} /* board_make */ + +void +board_destroy( BoardCtxt* board ) +{ + XP_FREE( board->mpool, board ); +} /* board_destroy */ + +BoardCtxt* +board_makeFromStream( MPFORMAL XWStreamCtxt* stream, ModelCtxt* model, + ServerCtxt* server, DrawCtx* draw, XW_UtilCtxt* util, + XP_U16 nPlayers ) +{ + BoardCtxt* board; + XP_U16 i; + XP_U16 version = stream_getVersion( stream ); + + board = board_make( MPPARM(mpool) model, server, draw, util ); + + /* This won't be enough for 'doze case: square with the SIP visible */ + board->yOffset = (XP_U16)stream_getBits( stream, 2 ); + board->isFlipped = (XP_Bool)stream_getBits( stream, 1 ); + board->gameOver = (XP_Bool)stream_getBits( stream, 1 ); + board->showColors = (XP_Bool)stream_getBits( stream, 1 ); + board->showCellValues = (XP_Bool)stream_getBits( stream, 1 ); +#ifdef KEYBOARD_NAV + if ( version >= STREAM_VERS_KEYNAV ) { + board->focussed = (BoardObjectType)stream_getBits( stream, 2 ); + board->focusHasDived = (BoardObjectType)stream_getBits( stream, 1 ); + board->scoreCursorLoc = (XP_U8) + stream_getBits( stream, (version < STREAM_VERS_MODEL_NO_DICT? 2:3)); + } +#endif + + XP_ASSERT( !!server ); + + for ( i = 0; i < nPlayers; ++i ) { + PerTurnInfo* pti = &board->pti[i]; + BoardArrow* arrow = &pti->boardArrow; + arrow->col = (XP_U8)stream_getBits( stream, NUMCOLS_NBITS ); + arrow->row = (XP_U8)stream_getBits( stream, NUMCOLS_NBITS ); + arrow->vert = (XP_Bool)stream_getBits( stream, 1 ); + arrow->visible = (XP_Bool)stream_getBits( stream, 1 ); + + pti->dividerLoc = (XP_U8)stream_getBits( stream, NTILES_NBITS ); + pti->traySelBits = (TileBit)stream_getBits( stream, + MAX_TRAY_TILES ); + pti->tradeInProgress = (XP_Bool)stream_getBits( stream, 1 ); +#ifdef KEYBOARD_NAV + if ( version >= STREAM_VERS_KEYNAV ) { + pti->bdCursor.col = stream_getBits( stream, 4 ); + pti->bdCursor.row = stream_getBits( stream, 4 ); + pti->trayCursorLoc = stream_getBits( stream, 3 ); + } +#endif + +#ifdef XWFEATURE_SEARCHLIMIT + if ( version >= STREAM_VERS_41B4 ) { + pti->hasHintRect = stream_getBits( stream, 1 ); + } else { + pti->hasHintRect = XP_FALSE; + } + if ( pti->hasHintRect ) { + pti->limits.left = stream_getBits( stream, 4 ); + pti->limits.top = stream_getBits( stream, 4 ); + pti->limits.right = stream_getBits( stream, 4 ); + pti->limits.bottom = stream_getBits( stream, 4 ); + } +#endif + + } + + board->selPlayer = (XP_U8)stream_getBits( stream, PLAYERNUM_NBITS ); + board->selInfo = &board->pti[board->selPlayer]; + board->trayVisState = (XW_TrayVisState)stream_getBits( stream, 2 ); + + XP_ASSERT( stream_getU32( stream ) == bEND ); + return board; +} /* board_makeFromStream */ + +void +board_writeToStream( BoardCtxt* board, XWStreamCtxt* stream ) +{ + XP_U16 nPlayers, i; + + stream_putBits( stream, 2, board->yOffset ); + stream_putBits( stream, 1, board->isFlipped ); + stream_putBits( stream, 1, board->gameOver ); + stream_putBits( stream, 1, board->showColors ); + stream_putBits( stream, 1, board->showCellValues ); +#ifdef KEYBOARD_NAV + stream_putBits( stream, 2, board->focussed ); + stream_putBits( stream, 1, board->focusHasDived ); + stream_putBits( stream, 3, board->scoreCursorLoc ); +#endif + + XP_ASSERT( !!board->server ); + nPlayers = board->gi->nPlayers; + + for ( i = 0; i < nPlayers; ++i ) { + PerTurnInfo* pti = &board->pti[i]; + BoardArrow* arrow = &pti->boardArrow; + stream_putBits( stream, NUMCOLS_NBITS, arrow->col ); + stream_putBits( stream, NUMCOLS_NBITS, arrow->row ); + stream_putBits( stream, 1, arrow->vert ); + stream_putBits( stream, 1, arrow->visible ); + + stream_putBits( stream, NTILES_NBITS, pti->dividerLoc ); + stream_putBits( stream, MAX_TRAY_TILES, pti->traySelBits ); + stream_putBits( stream, 1, pti->tradeInProgress ); +#ifdef KEYBOARD_NAV + stream_putBits( stream, 4, pti->bdCursor.col ); + stream_putBits( stream, 4, pti->bdCursor.row ); + stream_putBits( stream, 3, pti->trayCursorLoc ); +#endif + +#ifdef XWFEATURE_SEARCHLIMIT + stream_putBits( stream, 1, pti->hasHintRect ); + if ( pti->hasHintRect ) { + stream_putBits( stream, 4, pti->limits.left ); + stream_putBits( stream, 4, pti->limits.top ); + stream_putBits( stream, 4, pti->limits.right ); + stream_putBits( stream, 4, pti->limits.bottom ); + } +#endif + } + + stream_putBits( stream, PLAYERNUM_NBITS, board->selPlayer ); + stream_putBits( stream, 2, board->trayVisState ); + +#ifdef DEBUG + stream_putU32( stream, bEND ); +#endif +} /* board_writeToStream */ + +void +board_reset( BoardCtxt* board ) +{ + XP_U16 i; + XW_TrayVisState newState; + + XP_ASSERT( !!board->model ); + + /* This is appropriate for a new game *ONLY*. reset */ + for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { + PerTurnInfo* pti = &board->pti[i]; + pti->traySelBits = 0; + pti->tradeInProgress = XP_FALSE; + pti->dividerLoc = 0; + XP_MEMSET( &pti->boardArrow, 0, sizeof(pti->boardArrow) ); + } + board->gameOver = XP_FALSE; + board->selPlayer = 0; + board->selInfo = board->pti; + board->star_row = (XP_U16)(model_numRows(board->model) / 2); + + newState = board->boardObscuresTray? TRAY_HIDDEN:TRAY_REVERSED; + setTrayVisState( board, newState ); + + board_invalAll( board ); + + setTimerIf( board ); +} /* board_reset */ + +void +board_setPos( BoardCtxt* board, XP_U16 left, XP_U16 top, + XP_Bool leftHanded ) +{ + board->boardBounds.left = left; + board->boardBounds.top = top; + board->leftHanded = leftHanded; + + figureBoardRect( board ); +} /* board_setPos */ + +void +board_setTimerLoc( BoardCtxt* board, + XP_U16 timerLeft, XP_U16 timerTop, + XP_U16 timerWidth, XP_U16 timerHeight ) +{ + board->timerBounds.left = timerLeft; + board->timerBounds.top = timerTop; + board->timerBounds.width = timerWidth; + board->timerBounds.height = timerHeight; +} /* board_setTimerLoc */ + +void +board_setScale( BoardCtxt* board, XP_U16 hScale, XP_U16 vScale ) +{ + if ( hScale != board->boardHScale || vScale != board->boardVScale ) { + board->boardVScale = vScale; + board->boardHScale = hScale; + figureBoardRect( board ); + board_invalAll( board ); + } +} /* board_setScale */ + +void +board_getScale( BoardCtxt* board, XP_U16* hScale, XP_U16* vScale ) +{ + *hScale = board->boardHScale; + *vScale = board->boardVScale; +} /* board_getScale */ + +XP_Bool +board_prefsChanged( BoardCtxt* board, CommonPrefs* cp ) +{ + XP_Bool showArrowChanged; + XP_Bool hideValChanged; + + showArrowChanged = cp->showBoardArrow == board->disableArrow; + hideValChanged = cp->hideTileValues != board->hideValsInTray; + + board->disableArrow = !cp->showBoardArrow; + board->hideValsInTray = cp->hideTileValues; + + if ( showArrowChanged ) { + showArrowChanged = setArrowVisible( board, XP_FALSE ); + } + if ( hideValChanged ) { + board_invalTrayTiles( board, ALLTILES ); + } + +#ifdef XWFEATURE_SEARCHLIMIT + if ( !board->gi->allowHintRect && board->selInfo->hasHintRect ) { + + EngineCtxt* engine = server_getEngineFor( board->server, + board->selPlayer ); + if ( !!engine ) { + engine_reset( engine ); + } + + clearCurHintRect( board ); + } +#endif + + return showArrowChanged || hideValChanged; +} /* board_prefsChanged */ + +XP_Bool +adjustYOffset( BoardCtxt* board, XP_S16 moveBy ) +{ + XP_U16 nVisible = board->lastVisibleRow - board->yOffset + 1; + XP_U16 nRows = model_numRows(board->model); + XP_S16 newOffset = board->yOffset - moveBy; + + if ( newOffset < 0 ) { + newOffset = 0; + } else if ( newOffset + nVisible > nRows ) { + newOffset = nRows - nVisible; + } + + return board_setYOffset( board, newOffset ); +} /* adjustYOffset */ + +XP_Bool +board_setYOffset( BoardCtxt* board, XP_U16 offset ) +{ + XP_U16 oldOffset = board->yOffset; + XP_Bool result = oldOffset != offset; + + if ( result ) { + XP_U16 nVisible = board->lastVisibleRow - board->yOffset + 1; + XP_U16 nRows = model_numRows(board->model); + + result = offset <= nRows - nVisible; + if ( result ) { + /* check if scrolling makes sense for this board in its current + state. */ + XP_U16 visibleHeight = board->boardBounds.height; + XP_U16 fullHeight = nRows * board->boardVScale; + result = visibleHeight < fullHeight; + + if ( result ) { + invalSelTradeWindow( board ); + board->yOffset = offset; + figureBoardRect( board ); + util_yOffsetChange( board->util, oldOffset, offset ); + invalSelTradeWindow( board ); + board->needsDrawing = XP_TRUE; + } + } + } + + return result; +} /* board_setYOffset */ + +XP_U16 +board_getYOffset( const BoardCtxt* board ) +{ + return board->yOffset; +} /* board_getYOffset */ + +#ifdef KEYBOARD_NAV +/* called by ncurses version only */ +BoardObjectType +board_getFocusOwner( BoardCtxt* board ) +{ + return board->focussed; +} /* board_getFocusOwner */ +#endif + +static void +invalArrowCell( BoardCtxt* board ) +{ + BoardArrow* arrow = &board->selInfo->boardArrow; + invalCell( board, arrow->col, arrow->row ); +} /* invalArrowCell */ + +static void +flipArrow( BoardCtxt* board ) +{ + BoardArrow* arrow = &board->selInfo->boardArrow; + XP_U16 tmp = arrow->col; + arrow->col = arrow->row; + arrow->row = tmp; + arrow->vert = !arrow->vert; +} /* flipArrow */ + +#ifdef KEYBOARD_NAV +static void +invalCursorCell( BoardCtxt* board ) +{ + BdCursorLoc loc = board->selInfo->bdCursor; + invalCell( board, loc.col, loc.row ); +} /* invalCursorCell */ +#endif + +static void +invalTradeWindow( BoardCtxt* board, XP_S16 turn, XP_Bool redraw ) +{ + XP_ASSERT( turn >= 0 ); + + if ( board->pti[turn].tradeInProgress ) { + MiniWindowStuff* stuff = &board->miniWindowStuff[MINIWINDOW_TRADING]; + invalCellsUnderRect( board, &stuff->rect ); + if ( redraw ) { + board->tradingMiniWindowInvalid = XP_TRUE; + } + } +} /* invalTradeWindow */ + +void +invalSelTradeWindow( BoardCtxt* board ) +{ + invalTradeWindow( board, board->selPlayer, + board->trayVisState == TRAY_REVEALED ); +} /* invalSelTradeWindow */ + +#if defined POINTER_SUPPORT || defined KEYBOARD_NAV +void +hideMiniWindow( BoardCtxt* board, XP_Bool destroy, MiniWindowType winType ) +{ + MiniWindowStuff* stuff = &board->miniWindowStuff[winType]; + + invalCellsUnderRect( board, &stuff->rect ); + + if ( destroy ) { + stuff->text = (XP_UCHAR*)NULL; + } +} /* hideMiniWindow */ +#endif + +static XP_Bool +warnBadWords( XP_UCHAR* word, void* closure ) +{ + BadWordInfo bwi; + XP_Bool ok; + BoardCtxt* board = (BoardCtxt*)closure; + XP_S16 turn = server_getCurrentTurn( board->server ); + + bwi.nWords = 1; + bwi.words[0] = word; + + ok = util_warnIllegalWord( board->util, &bwi, turn, XP_FALSE ); + board->badWordRejected = !ok || board->badWordRejected; + + return ok; +} /* warnBadWords */ + +XP_Bool +board_commitTurn( BoardCtxt* board ) +{ + XP_Bool result = XP_FALSE; + const XP_S16 turn = server_getCurrentTurn( board->server ); + PerTurnInfo* pti = board->pti + turn; + + if ( board->gameOver || turn < 0 ) { + /* do nothing */ + } else if ( turn != board->selPlayer ) { + if ( board->selInfo->tradeInProgress ) { + result = exitTradeMode( board ); + } else { + util_userError( board->util, ERR_NOT_YOUR_TURN ); + } + } else if ( checkRevealTray( board ) ) { + if ( pti->tradeInProgress ) { + result = XP_TRUE; /* there's at least the window to clean up + after */ + + invalSelTradeWindow( board ); + pti->tradeInProgress = XP_FALSE; + + if ( util_userQuery( board->util, QUERY_COMMIT_TRADE, + (XWStreamCtxt*)NULL ) ) { + result = server_commitTrade( board->server, + pti->traySelBits ); + /* XP_DEBUGF( "server_commitTrade returned %d\n", result ); */ + } + pti->traySelBits = 0x00; + } else { + XP_Bool warn, legal; + WordNotifierInfo info; + XWStreamCtxt* stream = + mem_stream_make( MPPARM(board->mpool) + util_getVTManager(board->util), NULL, + CHANNEL_NONE, (MemStreamCloseCallback)NULL ); + + const XP_UCHAR* str = util_getUserString(board->util, + STR_COMMIT_CONFIRM); + + stream_putBytes( stream, (void*)str, + (XP_U16)XP_STRLEN((const char*)str) ); + + warn = board->util->gameInfo->phoniesAction == PHONIES_WARN; + + board->badWordRejected = XP_FALSE; + info.proc = warnBadWords; + info.closure = board; + legal = model_checkMoveLegal( board->model, turn, stream, + warn? &info:(WordNotifierInfo*)NULL); + + if ( !legal || board->badWordRejected ) { + result = XP_FALSE; + } else { + /* Hide the tray so no peeking. Leave it hidden even if user + cancels as otherwise another player could get around + passwords and peek at tiles. */ + if ( gi_countLocalHumans( board->gi ) > 1 ) { + result = board_hideTray( board ); + } + + if ( util_userQuery( board->util, QUERY_COMMIT_TURN, + stream ) ) { + result = server_commitMove( board->server ) || result; + /* invalidate any selected tiles in case we'll be drawing + this tray again rather than some other -- as is the + case in a two-player game where one's a robot. */ + board_invalTrayTiles( board, pti->traySelBits ); + pti->traySelBits = 0x00; + } + } + stream_destroy( stream ); + + if ( result ) { + setArrowVisibleFor( board, turn, XP_FALSE ); + } + } + } + return result; +} /* board_commitTurn */ + +/* Make all the changes necessary for the board to reflect the currently + * selected player. Many slots are duplicated on a per-player basis, e.g. + * cursor and tray traySelBits. Others, such as the miniwindow stuff, are + * singletons that may have to be hidden or shown. + */ +static void +selectPlayerImpl( BoardCtxt* board, XP_U16 newPlayer, XP_Bool reveal ) +{ + if ( !board->gameOver && server_getCurrentTurn(board->server) < 0 ) { + /* game not started yet; do nothing */ + } else if ( board->selPlayer == newPlayer ) { + if ( reveal ) { + checkRevealTray( board ); + } + } else { + PerTurnInfo* newInfo = &board->pti[newPlayer]; + XP_U16 oldPlayer = board->selPlayer; + model_foreachPendingCell( board->model, newPlayer, + boardCellChanged, board ); + model_foreachPendingCell( board->model, oldPlayer, + boardCellChanged, board ); + + /* if there are pending cells on one view and not the other, then the + previous move will be drawn highlighted on one and not the other + and so needs to be invalidated so it'll get redrawn.*/ + if ( (0 == model_getCurrentMoveCount( board->model, newPlayer )) + != (0 == model_getCurrentMoveCount( board->model, oldPlayer )) ) { + model_foreachPrevCell( board->model, boardCellChanged, board ); + } + + /* Just in case somebody started a trade when it wasn't his turn and + there were plenty of tiles but now there aren't. */ + if ( newInfo->tradeInProgress && + server_countTilesInPool(board->server) < MIN_TRADE_TILES ) { + newInfo->tradeInProgress = XP_FALSE; + newInfo->traySelBits = 0x00; /* clear any selected */ + } + + invalTradeWindow( board, oldPlayer, newInfo->tradeInProgress ); + +#ifdef XWFEATURE_SEARCHLIMIT + if ( board->pti[oldPlayer].hasHintRect ) { + invalCurHintRect( board, oldPlayer ); + } + if ( newInfo->hasHintRect ) { + invalCurHintRect( board, newPlayer ); + } +#endif + + invalArrowCell( board ); + board->selPlayer = (XP_U8)newPlayer; + board->selInfo = newInfo; + invalArrowCell( board ); + + board_invalTrayTiles( board, ALLTILES ); + board->dividerInvalid = XP_TRUE; + + setTrayVisState( board, TRAY_REVERSED ); + } + board->scoreBoardInvalid = XP_TRUE; /* if only one player, number of + tiles remaining may have changed*/ +} /* selectPlayerImpl */ + +void +board_selectPlayer( BoardCtxt* board, XP_U16 newPlayer ) +{ + selectPlayerImpl( board, newPlayer, XP_TRUE ); +} /* board_selectPlayer */ + +void +board_hiliteCellAt( BoardCtxt* board, XP_U16 col, XP_U16 row ) +{ + XP_Rect cellRect; + + flipIf( board, col, row, &col, &row ); + if ( getCellRect( board, col, row, &cellRect ) ) { + draw_invertCell( board->draw, &cellRect ); + invalCell( board, col, row ); + } + /* sleep(1); */ +} /* board_hiliteCellAt */ + +void +board_resetEngine( BoardCtxt* board ) +{ + server_resetEngine( board->server, board->selPlayer ); +} /* board_resetEngine */ + +/* Find a rectangle either centered on the board or pinned to the point at + * which the mouse went down. + */ +static void +positionMiniWRect( BoardCtxt* board, XP_Rect* rect, XP_Bool center ) +{ + if ( center ) { + XP_Rect boardBounds = board->boardBounds; + rect->top = boardBounds.top + ((boardBounds.height - rect->height)/2); + rect->left = boardBounds.left + ((boardBounds.width - rect->width)/2); + } else { + XP_S16 x = board->penDownX; + XP_S16 y = board->penDownY; + + if ( board->leftHanded ) { + rect->left = (x + rect->width <= board->boardBounds.width)? + x : x - rect->width; + } else { + rect->left = x - rect->width > 0? x - rect->width : x; + } + rect->left = XP_MAX( board->boardBounds.left + 1, rect->left ); + rect->top = XP_MAX( board->boardBounds.top + 1, y - rect->height ); + } + forceRectToBoard( board, rect ); +} /*positionMiniWRect */ + +static void +timerFiredForPen( BoardCtxt* board ) +{ + const XP_UCHAR* text = (XP_UCHAR*)NULL; + XP_UCHAR buf[80]; + + if ( board->penDownObject == OBJ_BOARD ) { + if ( !dragDropInProgress( board ) || !dragDropHasMoved( board ) ) { + XP_U16 col, row; + XWBonusType bonus; + + coordToCell( board, board->penDownX, board->penDownY, &col, + &row ); + bonus = util_getSquareBonus(board->util, board->model, col, row); + if ( bonus != BONUS_NONE ) { + text = draw_getMiniWText( board->draw, (XWMiniTextType)bonus ); + } + board->penTimerFired = XP_TRUE; + } + } else if ( board->penDownObject == OBJ_SCORE ) { + LocalPlayer* lp; + XP_S16 scoreIndex = figureScoreRectTapped( board, board->penDownX, + board->penDownY ); + /* I've seen this assert fire on simulator. No log is kept so I can't + tell why, but might want to test and do nothing in this case. */ + /* XP_ASSERT( player >= 0 ); */ + if ( scoreIndex > CURSOR_LOC_REM ) { + XP_U16 player = scoreIndex - 1; + const XP_UCHAR* format; + XP_UCHAR scoreExpl[48]; + XP_U16 explLen; + + lp = &board->gi->players[player]; + format = util_getUserString( board->util, lp->isLocal? + STR_LOCAL_NAME: STR_NONLOCAL_NAME ); + XP_SNPRINTF( buf, sizeof(buf), format, emptyStringIfNull(lp->name) ); + + explLen = sizeof(scoreExpl); + if ( model_getPlayersLastScore( board->model, player, scoreExpl, + &explLen ) ) { + XP_STRCAT( buf, XP_CR ); + XP_ASSERT( XP_STRLEN(buf) + explLen < sizeof(buf) ); + XP_STRCAT( buf, scoreExpl ); + } + + text = buf; + } + + board->penTimerFired = XP_TRUE; + } + + if ( !!text ) { + MiniWindowStuff* stuff = &board->miniWindowStuff[MINIWINDOW_VALHINT]; + makeMiniWindowForText( board, text, MINIWINDOW_VALHINT ); + XP_ASSERT( stuff->text == text ); + draw_drawMiniWindow(board->draw, text, &stuff->rect, + &stuff->closure); + } +} /* timerFiredForPen */ + +static void +setTimerIf( BoardCtxt* board ) +{ + XP_Bool timerWanted = board->gi->timerEnabled && !board->gameOver; + + if ( timerWanted && !board->timerPending ) { + util_setTimer( board->util, TIMER_TIMERTICK, 0, + p_board_timerFired, board ); + board->timerPending = XP_TRUE; + } +} /* setTimerIf */ + +static void +timerFiredForTimer( BoardCtxt* board ) +{ + board->timerPending = XP_FALSE; + if ( !board->gameOver ) { + XP_S16 turn = server_getCurrentTurn( board->server ); + + if ( turn >= 0 ) { + ++board->gi->players[turn].secondsUsed; + + if ( turn == board->selPlayer ) { + drawTimer( board ); + } + } + } + setTimerIf( board ); +} /* timerFiredForTimer */ + +static XP_Bool +p_board_timerFired( void* closure, XWTimerReason why ) +{ + BoardCtxt* board = (BoardCtxt*)closure; + if ( why == TIMER_PENDOWN ) { + timerFiredForPen( board ); + } else { + XP_ASSERT( why == TIMER_TIMERTICK ); + timerFiredForTimer( board ); + } + return XP_FALSE; +} /* board_timerFired */ + +void +board_pushTimerSave( BoardCtxt* board ) +{ + if ( board->gi->timerEnabled ) { + if ( board->timerSaveCount++ == 0 ) { + board->timerStoppedTime = util_getCurSeconds( board->util ); +#ifdef DEBUG + board->timerStoppedTurn = server_getCurrentTurn( board->server ); +#endif + } + } +} /* board_pushTimerSave */ + +void +board_popTimerSave( BoardCtxt* board ) +{ + if ( board->gi->timerEnabled ) { + + /* it's possible for the count to be 0, if e.g. the timer was enabled + between calls to board_pushTimerSave and this call, as can happen on + franklin. So that's not an error. */ + if ( board->timerSaveCount > 0 ) { + XP_S16 turn = server_getCurrentTurn( board->server ); + + XP_ASSERT( board->timerStoppedTurn == turn ); + + if ( --board->timerSaveCount == 0 && turn >= 0 ) { + XP_U32 curTime = util_getCurSeconds( board->util ); + XP_U32 elapsed; + + XP_ASSERT( board->timerStoppedTime != 0 ); + elapsed = curTime - board->timerStoppedTime; + XP_LOGF( "board_popTimerSave: elapsed=%ld", elapsed ); + XP_ASSERT( elapsed <= 0xFFFF ); + board->gi->players[turn].secondsUsed += (XP_U16)elapsed; + } + } + } +} /* board_popTimerSave */ + +/* Figure out if the current player's tiles should be excluded, then call + * server to format. + */ +void +board_formatRemainingTiles( BoardCtxt* board, XWStreamCtxt* stream ) +{ + XP_S16 curPlayer = board->selPlayer; + if ( board->trayVisState != TRAY_REVEALED ) { + curPlayer = -1; + } + server_formatRemainingTiles( board->server, stream, curPlayer ); +} /* board_formatRemainingTiles */ + +static void +board_invalAllTiles( BoardCtxt* board ) +{ + XP_U16 lastRow = model_numRows( board->model ); + while ( lastRow-- ) { + board->redrawFlags[lastRow] = ~0; + } + board->tradingMiniWindowInvalid = XP_TRUE; + + board->needsDrawing = XP_TRUE; +} /* board_invalAllTiles */ + +#ifdef KEYBOARD_NAV +#ifdef PERIMETER_FOCUS +static void +invalPerimeter( BoardCtxt* board ) +{ + XP_U16 lastCol = model_numCols( board->model ) - 1; + XP_U16 firstAndLast = (1 << lastCol) | 1; + XP_U16 firstRow = board->yOffset; + XP_U16 lastRow = board->lastVisibleRow; + + /* top and bottom rows */ + board->redrawFlags[firstRow] = ~0; + board->redrawFlags[lastRow] = ~0; + + while ( --lastRow > firstRow ) { + board->redrawFlags[lastRow] |= firstAndLast; + } +} /* invalPerimeter */ +#endif +#endif + +void +board_invalAll( BoardCtxt* board ) +{ + board_invalAllTiles( board ); + + board_invalTrayTiles( board, ALLTILES ); + board->dividerInvalid = XP_TRUE; + board->scoreBoardInvalid = XP_TRUE; +} /* board_invalAll */ + +void +flipIf( const BoardCtxt* board, XP_U16 col, XP_U16 row, + XP_U16* fCol, XP_U16* fRow ) +{ + XP_U16 tmp = col; /* might be the same */ + if ( board->isFlipped ) { + *fCol = row; + *fRow = tmp; + } else { + *fRow = row; + *fCol = tmp; + } +} /* flipIf */ + +#ifdef XWFEATURE_SEARCHLIMIT +static void +flipLimits( BdHintLimits* lim ) +{ + XP_U16 tmp = lim->left; + lim->left = lim->top; + lim->top = tmp; + tmp = lim->right; + lim->right = lim->bottom; + lim->bottom = tmp; +} + +static void +flipAllLimits( BoardCtxt* board ) +{ + XP_U16 nPlayers = board->gi->nPlayers; + XP_U16 i; + for ( i = 0; i < nPlayers; ++i ) { + flipLimits( &board->pti[i].limits ); + } +} +#endif + +/* + * invalidate all cells that contain a tile. Return TRUE if any invalidation + * actually happens. + */ +static XP_Bool +invalCellsWithTiles( BoardCtxt* board ) +{ + ModelCtxt* model = board->model; + XP_S16 turn = board->selPlayer; + XP_Bool includePending = board->trayVisState == TRAY_REVEALED; + XP_S16 col, row; + + /* For each col,row pair, invalidate it if it holds a tile. Previously we + * optimized by checking that the tile was actually visible, but with + * flipping and now boards obscured by more than just the tray that's hard + * to get right. The cell drawing code is pretty good about exiting + * quickly when its cell is off the visible board. We'll count on that + * for now. + */ + for ( row = model_numRows( model )-1; row >= 0; --row ) { + for ( col = model_numCols( model )-1; col >= 0; --col ) { + Tile tile; + XP_Bool ignore; + if ( model_getTile( model, col, row, includePending, + turn, &tile, &ignore, &ignore, &ignore ) ) { + XP_U16 boardCol, boardRow; + flipIf( board, col, row, &boardCol, &boardRow ); + invalCell( board, boardCol, boardRow ); + } + } + } + return board->needsDrawing; +} /* invalCellsWithTiles */ + +XP_Bool +checkScrollCell( BoardCtxt* board, XP_U16 col, XP_U16 row ) +{ + XP_Rect rect; + XP_Bool moved = XP_FALSE; + + if ( board->boardObscuresTray && board->trayVisState != TRAY_HIDDEN ) { + /* call getCellRect until the cell's on the board. */ + while ( !getCellRect( board, col, row, &rect ) ) { + XP_S16 moveBy = 1; + if ( rect.top < board->boardBounds.top ) { + /* do nothing; set to 1 above to prevent warning */ + } else if ( rect.top + rect.height > + board->boardBounds.top + board->boardBounds.height ) { + moveBy = -1; + } else { + XP_ASSERT( 0 ); + } + moved = adjustYOffset( board, moveBy ); + XP_ASSERT( moved ); + } + } + return moved; +} /* checkScrollCell */ + +XP_Bool +onBorderCanScroll( const BoardCtxt* board, XP_U16 row, XP_S16* changeP ) +{ + XP_Bool result; + XP_S16 change = 0; + XP_U16 yOffset = board_getYOffset( board ); + + if ( yOffset > 0 && row == yOffset ) { + change = -yOffset; + } else if ( row == board->lastVisibleRow ) { + XP_U16 lastRow = model_numRows(board->model) - 1; + change = lastRow - row; + } + + result = change != 0; + if ( result ) { + *changeP = change; + } + return result; +} + +void +board_setTrayLoc( BoardCtxt* board, XP_U16 trayLeft, XP_U16 trayTop, + XP_U16 trayWidth, XP_U16 trayHeight, + XP_U16 minDividerWidth ) +{ + XP_U16 dividerWidth; + XP_U16 boardBottom, boardRight; + XP_Bool boardHidesTray; + board->trayBounds.left = trayLeft; + board->trayBounds.top = trayTop; + /* what's this +1 for? */ + + board->trayBounds.width = trayWidth; + board->trayBounds.height = trayHeight; + + dividerWidth = minDividerWidth + + ((trayWidth - minDividerWidth) % MAX_TRAY_TILES); + + board->trayScaleH = (trayWidth - dividerWidth) / MAX_TRAY_TILES; + board->trayScaleV = trayHeight; + + board->dividerWidth = dividerWidth; + + /* boardObscuresTray is about whether they *can* overlap, not just about + * they do given the current scroll position of the board. Remember + * (e.g. for curses version) that vertical intersection isn't enough.*/ + boardBottom = board->boardBounds.top + + (board->boardVScale * model_numRows( board->model )); + boardRight = board->boardBounds.left + + (board->boardHScale * model_numCols( board->model )); + board->boardObscuresTray = (trayTop < boardBottom) + && (trayLeft < boardRight); + + boardHidesTray = board->boardObscuresTray; + if ( boardHidesTray ) { /* can't hide if doesn't obscure */ + if ( (trayTop + trayHeight) > boardBottom ) { + boardHidesTray = XP_FALSE; + } else if ( (trayLeft + trayWidth) > boardRight ) { + boardHidesTray = XP_FALSE; + } + } + board->boardHidesTray = boardHidesTray; + + if ( board->trayVisState == TRAY_HIDDEN ) { + if ( !board->boardHidesTray ) { + XW_TrayVisState state = TRAY_REVERSED; + setTrayVisState( board, state ); + } + } + figureBoardRect( board ); +} /* board_setTrayLoc */ + +void +invalCellsUnderRect( BoardCtxt* board, const XP_Rect* rect ) +{ + if ( rectsIntersect( rect, &board->boardBounds ) ) { + XP_Rect lr = *rect; + XP_U16 left, top, right, bottom; + XP_U16 col, row; + + if ( !coordToCell( board, lr.left, lr.top, &left, &top ) ) { + left = top = 0; + } + if ( !coordToCell( board, lr.left+lr.width, lr.top+lr.height, + &right, &bottom ) ) { + right = bottom = model_numCols( board->model ); + } + + for ( row = top; row <= bottom; ++row ) { + for ( col = left; col <= right; ++col ) { + invalCell( board, col, row ); + } + } + } +} /* invalCellsUnderRect */ + +void +board_invalRect( BoardCtxt* board, XP_Rect* rect ) +{ + if ( rectsIntersect( rect, &board->boardBounds ) ) { + invalCellsUnderRect( board, rect ); + } + + if ( rectsIntersect( rect, &board->trayBounds ) ) { + invalTilesUnderRect( board, rect ); + } + + if ( rectsIntersect( rect, &board->scoreBdBounds ) ) { + board->scoreBoardInvalid = XP_TRUE; + } +} /* board_invalRect */ + +/* When the tray's hidden, check if it overlaps where the board wants to be, + * and adjust the board's rect if needed so that hit-testing will work + * "through" where the tray used to be. + * + * Visible here means different things depending on whether the tray can + * overlap the board. If not, then we never "hide" the tray; rather, we turn + * it over. So figure out what hidden means and then change the state if it's + * not already there. + * + * But remember that revealing the tray in an overlapping situation is a + * two-step process. First anyone can cause the tray to be drawn over the top + * of the board. But then a second gesture is required to cause the tray to + * be drawn with tiles face-up. + */ +XP_Bool +board_hideTray( BoardCtxt* board ) +{ + XW_TrayVisState soughtState; + if ( board->boardObscuresTray ) { + soughtState = TRAY_HIDDEN; + } else { + soughtState = TRAY_REVERSED; + } + return setTrayVisState( board, soughtState ); +} /* board_hideTray */ + +static XP_S16 +chooseBestSelPlayer( BoardCtxt* board ) +{ + ServerCtxt* server = board->server; + + if ( board->gameOver ) { + return board->selPlayer; + } else { + + XP_S16 curTurn = server_getCurrentTurn( server ); + + if ( curTurn >= 0 ) { + XP_U16 nPlayers = board->gi->nPlayers; + XP_U16 i; + + for ( i = 0; i < nPlayers; ++i ) { + LocalPlayer* lp = &board->gi->players[curTurn]; + + if ( !lp->isRobot && lp->isLocal ) { + return curTurn; + } + curTurn = (curTurn + 1) % nPlayers; + } + } + } + + return -1; +} /* chooseBestSelPlayer */ + +/* Reveal the tray of the currently selected player. If that's a robot other + * code should flag the error. + */ +XP_Bool +board_showTray( BoardCtxt* board ) +{ + return checkRevealTray( board ); +} /* board_showTray */ + +static XP_Bool +trayOnTop( const BoardCtxt* board ) +{ + /* The tray should be drawn on top of the board IFF it's not HIDDEN or if + it has non-dived focus. */ + return (board->trayVisState != TRAY_HIDDEN) + || ( (board->focussed == OBJ_TRAY) + && (board->focusHasDived == XP_FALSE)); +} /* trayOnTop */ + +XW_TrayVisState +board_getTrayVisState( const BoardCtxt* board ) +{ + return board->trayVisState; +} /* board_getTrayVisible */ + +static XP_Bool +setTrayVisState( BoardCtxt* board, XW_TrayVisState newState ) +{ + XP_Bool changed; + + if ( newState == TRAY_REVERSED && board->gameOver ) { + newState = TRAY_REVEALED; + } + + changed = board->trayVisState != newState; + if ( changed ) { + XP_Bool nowHidden = newState == TRAY_HIDDEN; + XP_U16 selPlayer = board->selPlayer; + XP_U16 nVisible; + + /* redraw cells that are pending; whether tile is visible may + change */ + model_foreachPendingCell( board->model, selPlayer, + boardCellChanged, board ); + /* ditto -- if there's a pending move */ + model_foreachPrevCell( board->model, boardCellChanged, board ); + + board_invalTrayTiles( board, ALLTILES ); + board->dividerInvalid = XP_TRUE; + + board->trayVisState = newState; + + invalSelTradeWindow( board ); + (void)invalFocusOwner( board ); /* must be done before and after rect + recalculated */ + figureBoardRect( board ); /* comes before setYOffset since that + uses rects to calc scroll */ + (void)invalFocusOwner( board ); + + if ( board->boardObscuresTray ) { + if ( nowHidden && !trayOnTop(board) ) { + board->preHideYOffset = board_getYOffset( board ); + board_setYOffset( board, 0 ); + } else { + board_setYOffset( board, board->preHideYOffset ); + } + } + + if ( nowHidden ) { + /* Can't always set this to TRUE since in obscuring case tray will + * get erased after the cells that are supposed to be revealed + * get drawn. */ + board->eraseTray = !board->boardHidesTray; + invalCellsUnderRect( board, &board->trayBounds ); + } + invalArrowCell( board ); + board->scoreBoardInvalid = XP_TRUE; /* b/c what's bold may change */ +#ifdef XWFEATURE_SEARCHLIMIT + invalCurHintRect( board, selPlayer ); +#endif + + nVisible = board->lastVisibleRow - board->yOffset + 1; + util_trayHiddenChange( board->util, board->trayVisState, nVisible ); + } + return changed; +} /* setTrayVisState */ + +static void +invalReflection( BoardCtxt* board ) +{ + XP_U16 nRows = model_numRows( board->model ); + XP_U16 saveCols = model_numCols( board->model ); + + while ( nRows-- ) { + XP_U16 nCols; + XP_U16 redrawFlag = board->redrawFlags[nRows]; + if ( !redrawFlag ) { + continue; /* nothing set this row */ + } + nCols = saveCols; + while ( nCols-- ) { + if ( 0 != (redrawFlag & (1<isFlipped; +} + +XP_Bool +board_flip( BoardCtxt* board ) +{ + invalArrowCell( board ); + flipArrow( board ); +#ifdef KEYBOARD_NAV + invalCursorCell( board ); +#endif + + if ( board->boardObscuresTray ) { + invalCellsUnderRect( board, &board->trayBounds ); + } + invalCellsWithTiles( board ); + +#ifdef XWFEATURE_SEARCHLIMIT + invalCurHintRect( board, board->selPlayer ); + flipAllLimits( board ); +#endif + + board->isFlipped = !board->isFlipped; + + invalReflection( board ); /* For every x,y set, also set y,x */ + + return board->needsDrawing; +} /* board_flip */ + +XP_Bool +board_get_showValues( const BoardCtxt* board ) +{ + return board->showCellValues; +} + +XP_Bool +board_toggle_showValues( BoardCtxt* board ) +{ + XP_Bool changed; + board->showCellValues = !board->showCellValues; + + /* We show the tile values when showCellValues is set even if + hideValsInTray is set. So inval the tray if there will be a change. + And set changed to true in case there are no tiles on the baord yet. + */ + changed = board->hideValsInTray && (board->trayVisState == TRAY_REVEALED); + if ( changed ) { + board_invalTrayTiles( board, ALLTILES ); + } + return invalCellsWithTiles( board ) || changed; +} /* board_toggle_showValues */ + +XP_Bool +board_setShowColors( BoardCtxt* board, XP_Bool showColors ) +{ + board->showColors = showColors; + board->scoreBoardInvalid = XP_TRUE; + return invalCellsWithTiles( board ); +} /* board_setShowColors */ + +XP_Bool +board_replaceTiles( BoardCtxt* board ) +{ + XP_Bool result = XP_FALSE; + while ( replaceLastTile( board ) ) { + result = XP_TRUE; + } + + if ( result ) { + (void)setArrowVisible( board, XP_FALSE ); + } + + return result; +} /* board_replaceTiles */ + +/* There are a few conditions that must be true for any of several actions + to be allowed. Check them here. */ +static XP_Bool +preflight( BoardCtxt* board ) +{ + return !board->gameOver && !TRADE_IN_PROGRESS(board) + && server_getCurrentTurn(board->server) >= 0 + && checkRevealTray( board ); +} /* preflight */ + +/* Refuse with error message if any tiles are currently on board in this turn. + * Then call the engine, and display the first move. Return true if there's + * any redrawing to be done. + */ +XP_Bool +board_requestHint( BoardCtxt* board, +#ifdef XWFEATURE_SEARCHLIMIT + XP_Bool useTileLimits, +#endif + XP_Bool* workRemainsP ) +{ + XP_Bool result = XP_FALSE; + XP_Bool redraw = XP_FALSE; + + *workRemainsP = XP_FALSE; /* in case we exit without calling engine */ + + if ( board->gi->hintsNotAllowed ) { + util_userError( board->util, ERR_CANT_HINT_WHILE_DISABLED ); + } else { + MoveInfo newMove; + XP_S16 nTiles; + const Tile* tiles; + XP_Bool searchComplete = XP_TRUE; + const XP_U16 selPlayer = board->selPlayer; + PerTurnInfo* pti = board->selInfo; + EngineCtxt* engine = server_getEngineFor( board->server, selPlayer ); + const TrayTileSet* tileSet; + ModelCtxt* model = board->model; + + result = !!engine && preflight( board ); + + /* undo any current move. otherwise we won't pass the full tray to + the engine. Would it be better, though, to pass the whole tray + regardless where its contents are? */ + if ( model_getCurrentMoveCount( model, selPlayer ) > 0 ) { + model_resetCurrentTurn( model, selPlayer ); + /* Draw's a no-op on Wince with a null hdc, but it'll draw again. + Should probably define OS_INITS_DRAW on Wince...*/ +#ifdef OS_INITS_DRAW + /* On symbian, it's illegal to draw except from inside the Draw + method. But the move search will probably be so fast that it's + ok to wait until we've found the move anyway. */ + redraw = XP_TRUE; +#else + board_draw( board ); +#endif + } + + tileSet = model_getPlayerTiles( model, selPlayer ); + nTiles = tileSet->nTiles - pti->dividerLoc; + result = nTiles > 0; + if ( result ) { +#ifdef XWFEATURE_SEARCHLIMIT + BdHintLimits limits; + BdHintLimits* lp = NULL; +#endif + XP_Bool wasVisible; + XP_Bool canMove; + + wasVisible = setArrowVisible( board, XP_FALSE ); + + (void)board_replaceTiles( board ); + + tiles = tileSet->tiles + pti->dividerLoc; + + board_pushTimerSave( board ); + +#ifdef XWFEATURE_SEARCHLIMIT + XP_ASSERT( board->gi->allowHintRect || !pti->hasHintRect ); + if ( board->gi->allowHintRect && pti->hasHintRect ) { + limits = pti->limits; + lp = &limits; + if ( board->isFlipped ) { + flipLimits( lp ); + } + } +#endif + searchComplete = engine_findMove(engine, model, + model_getDictionary(model), + tiles, nTiles, +#ifdef XWFEATURE_SEARCHLIMIT + lp, useTileLimits, +#endif + NO_SCORE_LIMIT, + &canMove, &newMove ); + board_popTimerSave( board ); + + if ( searchComplete && canMove ) { + model_makeTurnFromMoveInfo( model, selPlayer, &newMove); + } else { + XP_STATUSF( "unable to complete hint request\n" ); + } + *workRemainsP = !searchComplete; + + /* hide the cursor if it's been obscured by the new move */ + if ( wasVisible ) { + XP_U16 col, row; + + getArrow( board, &col, &row ); + if ( cellOccupied( board, col, row, XP_TRUE ) ) { + wasVisible = XP_FALSE; + } + setArrowVisible( board, wasVisible ); + } + } + } + return result || redraw; +} /* board_requestHint */ + +static void +figureBoardRect( BoardCtxt* board ) +{ + XP_U16 boardVScale = board->boardVScale; + if ( boardVScale > 0 ) { + XP_Rect boardBounds = board->boardBounds; + XP_U16 nVisible; + + boardBounds.width = model_numCols( board->model ) * board->boardHScale; + boardBounds.height = (model_numRows( board->model ) - board->yOffset) + * board->boardVScale; + + if ( board->boardObscuresTray ) { + if ( trayOnTop( board ) ) { + boardBounds.height = board->trayBounds.top - boardBounds.top; + } else { + XP_U16 trayBottom; + trayBottom = board->trayBounds.top + board->trayBounds.height; + if ( trayBottom < boardBounds.top + boardBounds.height ) { + boardBounds.height = trayBottom - boardBounds.top; + } + } + } + /* round down */ + nVisible = boardBounds.height / boardVScale; + boardBounds.height = nVisible * boardVScale; + board->lastVisibleRow = nVisible + board->yOffset - 1; + + board->boardBounds = boardBounds; + } +} /* figureBoardRect */ + +XP_Bool +coordToCell( BoardCtxt* board, XP_S16 xx, XP_S16 yy, XP_U16* colP, + XP_U16* rowP ) +{ + XP_U16 col, row, max; + XP_Bool onBoard = XP_TRUE; + + xx -= board->boardBounds.left; + + yy -= board->boardBounds.top; + yy += board->boardVScale * board->yOffset; + + col = xx / board->boardHScale; + row = yy / board->boardVScale; + + max = model_numCols( board->model ) - 1; + /* I don't deal with non-square boards yet. */ + XP_ASSERT( max + 1 == model_numRows( board->model ) ); + if ( col > max ) { + col = max; + onBoard = XP_FALSE; + } + if ( row > max ) { + row = max; + onBoard = XP_FALSE; + } + + *colP = col; + *rowP = row; + return onBoard; +} /* coordToCell */ + +XP_Bool +getCellRect( const BoardCtxt* board, XP_U16 col, XP_U16 row, XP_Rect* rect ) +{ + XP_S16 top; + XP_Bool onBoard = XP_TRUE; + + if ( row < board->yOffset ) { + onBoard = XP_FALSE; + } + + rect->left = board->boardBounds.left + (col * board->boardHScale); + top = board->boardBounds.top + + ((row - board->yOffset) * board->boardVScale); + if ( top >= (board->boardBounds.top + board->boardBounds.height) ) { + onBoard = XP_FALSE; + } + rect->top = top; + + rect->width = board->boardHScale; + rect->height = board->boardVScale; + return onBoard; +} /* getCellRect */ + +void +invalCell( BoardCtxt* board, XP_U16 col, XP_U16 row ) +{ + board->redrawFlags[row] |= 1 << col; + + XP_ASSERT( col < MAX_ROWS ); + XP_ASSERT( row < MAX_ROWS ); + + /* if the trade window is up and this cell intersects it, set up to draw + it again */ + if ( (board->trayVisState != TRAY_HIDDEN) && TRADE_IN_PROGRESS(board) ) { + XP_Rect rect; + if ( getCellRect( board, col, row, &rect ) ) { /* cell's visible */ + XP_Rect windowR = board->miniWindowStuff[MINIWINDOW_TRADING].rect; + if ( rectsIntersect( &rect, &windowR ) ) { + board->tradingMiniWindowInvalid = XP_TRUE; + } + } + } + + board->needsDrawing = XP_TRUE; +} /* invalCell */ + +#if defined POINTER_SUPPORT || defined KEYBOARD_NAV +XP_Bool +pointOnSomething( BoardCtxt* board, XP_U16 xx, XP_U16 yy, BoardObjectType* wp ) +{ + XP_Bool result = XP_TRUE; + + /* Test the board first in case it overlaps. When tray is visible + boardBounds is shortened so it does not overlap. */ + if ( rectContainsPt( &board->boardBounds, xx, yy ) ) { + *wp = OBJ_BOARD; + } else if ( rectContainsPt( &board->trayBounds, xx, yy ) ) { + *wp = OBJ_TRAY; + } else if ( rectContainsPt( &board->scoreBdBounds, xx, yy ) ) { + *wp = OBJ_SCORE; + } else { + result = XP_FALSE; + } + + return result; +} /* pointOnSomething */ + +/* Move the given tile to the board. If it's a blank, we need to ask the user + * what to call it first. + */ +XP_Bool +moveTileToArrowLoc( BoardCtxt* board, XP_U8 index ) +{ + XP_Bool result; + BoardArrow* arrow = &board->selInfo->boardArrow; + if ( arrow->visible ) { + result = moveTileToBoard( board, arrow->col, arrow->row, + (XP_U16)index, EMPTY_TILE ); + if ( result ) { + XP_Bool moved = advanceArrow( board ); + if ( !moved ) { + /* If the arrow didn't move, we can't leave it in place or + it'll get drawn over the new tile. */ + setArrowVisible( board, XP_FALSE ); + } + } + } else { + result = XP_FALSE; + } + return result; +} /* moveTileToArrowLoc */ +#endif + +void +makeMiniWindowForText( BoardCtxt* board, const XP_UCHAR* text, + MiniWindowType winType ) +{ + XP_Rect rect; + MiniWindowStuff* stuff = &board->miniWindowStuff[winType]; + + draw_measureMiniWText( board->draw, text, + (XP_U16*)&rect.width, + (XP_U16*)&rect.height ); + positionMiniWRect( board, &rect, winType == MINIWINDOW_TRADING ); + stuff->rect = rect; + stuff->text = text; +} /* makeMiniWindowForText */ + +XP_Bool +board_beginTrade( BoardCtxt* board ) +{ + XP_Bool result; + + result = preflight( board ); + if ( result ) { + /* check turn before tradeInProgress so I can't tell my opponent's in a + trade */ + if ( 0 != model_getCurrentMoveCount( board->model, board->selPlayer )){ + util_userError( board->util, ERR_CANT_TRADE_MID_MOVE ); + } else if ( server_countTilesInPool(board->server) < MIN_TRADE_TILES){ + util_userError( board->util, ERR_TOO_FEW_TILES_LEFT_TO_TRADE ); + } else { + board->tradingMiniWindowInvalid = XP_TRUE; + board->needsDrawing = XP_TRUE; + board->selInfo->tradeInProgress = XP_TRUE; + setArrowVisible( board, XP_FALSE ); + result = XP_TRUE; + } + } + return result; +} /* board_beginTrade */ + +#if defined POINTER_SUPPORT || defined KEYBOARD_NAV +static XP_Bool +ptOnTradeWindow( BoardCtxt* board, XP_U16 x, XP_U16 y ) +{ + XP_Rect* windowR = &board->miniWindowStuff[MINIWINDOW_TRADING].rect; + return rectContainsPt( windowR, x, y ); +} /* ptOnTradeWindow */ + +#ifdef XWFEATURE_SEARCHLIMIT + +void +invalCellRegion( BoardCtxt* board, XP_U16 colA, XP_U16 rowA, XP_U16 colB, + XP_U16 rowB ) +{ + XP_U16 col, row; + XP_U16 firstCol, lastCol, firstRow, lastRow; + + if ( colA <= colB ) { + firstCol = colA; + lastCol = colB; + } else { + firstCol = colB; + lastCol = colA; + } + if ( rowA <= rowB ) { + firstRow = rowA; + lastRow = rowB; + } else { + firstRow = rowB; + lastRow = rowA; + } + + for ( row = firstRow; row <= lastRow; ++row ) { + for ( col = firstCol; col <= lastCol; ) { + invalCell( board, col, row ); + ++col; +#ifndef XWFEATURE_SEARCHLIMIT_DOCENTERS + if ( row > firstRow && row < lastRow && (col < lastCol) ) { + col = lastCol; + } +#endif + } + } +} /* invalCellRegion */ + +void +invalCurHintRect( BoardCtxt* board, XP_U16 player ) +{ + BdHintLimits* limits = &board->pti[player].limits; + invalCellRegion( board, limits->left, limits->top, + limits->right, limits->bottom ); +} /* invalCurHintRect */ + +static void +clearCurHintRect( BoardCtxt* board ) +{ + invalCurHintRect( board, board->selPlayer ); + board->selInfo->hasHintRect = XP_FALSE; +} /* clearCurHintRect */ +#endif /* XWFEATURE_SEARCHLIMIT */ + +static XP_Bool +handlePenDownOnBoard( BoardCtxt* board, XP_U16 xx, XP_U16 yy ) +{ + XP_Bool result = XP_FALSE; + + if ( TRADE_IN_PROGRESS(board) && ptOnTradeWindow( board, xx, yy ) ) { + /* do nothing */ + } else { + util_setTimer( board->util, TIMER_PENDOWN, 0, + p_board_timerFired, board ); + + if ( !board->selInfo->tradeInProgress ) { + result = dragDropStart( board, OBJ_BOARD, xx, yy ); + } + } + + return result; +} /* handlePenDownOnBoard */ + +/* If there's a password, ask it; if they match, change the state of the tray + * to TRAY_REVEALED (unless we're not supposed to show the tiles). Return + * value talks about whether the tray needs to be redrawn, not the success of + * the password compare. + */ +static XP_Bool +askRevealTray( BoardCtxt* board ) +{ + XP_Bool revealed = XP_FALSE; + XP_Bool reversed = board->trayVisState == TRAY_REVERSED; + XP_U16 selPlayer = board->selPlayer; + LocalPlayer* lp = &board->gi->players[selPlayer]; + XP_Bool justReverse = XP_FALSE; + + if ( board->gameOver ) { + revealed = XP_TRUE; + } else if ( server_getCurrentTurn( board->server ) < 0 ) { + revealed = XP_FALSE; +#ifndef XWFEATURE_STANDALONE_ONLY + } else if ( !lp->isLocal ) { + util_userError( board->util, ERR_NO_PEEK_REMOTE_TILES ); +#endif + } else if ( lp->isRobot ) { + if ( reversed ) { + util_userError( board->util, ERR_NO_PEEK_ROBOT_TILES ); + } else { + justReverse = XP_TRUE; + } + } else { + revealed = !player_hasPasswd( lp ); + + if ( !revealed ) { + const XP_UCHAR* name = emptyStringIfNull(lp->name); + + /* repeat until player gets passwd right or hits cancel */ + for ( ; ; ) { + XP_UCHAR buf[16]; + XP_U16 buflen = sizeof(buf); + if ( !util_askPassword( board->util, name, buf, &buflen ) ) { + break; + } + if ( buflen > 0 ) { + if ( player_passwordMatches( lp, (XP_U8*)buf, buflen ) ) { + revealed = XP_TRUE; + break; + } + } + } + } + } + + if ( revealed ) { + setTrayVisState( board, TRAY_REVEALED ); + } else if ( justReverse ) { + setTrayVisState( board, TRAY_REVERSED ); + } + return justReverse || revealed; +} /* askRevealTray */ + +XP_Bool +checkRevealTray( BoardCtxt* board ) +{ + XP_Bool result = board->trayVisState == TRAY_REVEALED; + if ( !result ) { + result = askRevealTray( board ); + } + return result; +} /* checkRevealTray */ + +static XP_Bool +handleLikeDown( BoardCtxt* board, BoardObjectType onWhich, XP_U16 x, XP_U16 y ) +{ + XP_Bool result = XP_FALSE; + + switch ( onWhich ) { + case OBJ_BOARD: + result = handlePenDownOnBoard( board, x, y ) || result; + break; + + case OBJ_TRAY: + if ( (board->trayVisState == TRAY_REVEALED) + && !board->selInfo->tradeInProgress ) { + result = dragDropStart( board, OBJ_TRAY, x, y ) || result; + } + break; + + case OBJ_SCORE: + if ( figureScoreRectTapped( board, x, y ) > CURSOR_LOC_REM ) { + util_setTimer( board->util, TIMER_PENDOWN, 0, + p_board_timerFired, board ); + } + break; + default: + break; + } + + board->penDownX = x; + board->penDownY = y; + board->penDownObject = onWhich; + + return result; +} /* handleLikeDown */ + +#ifdef POINTER_SUPPORT +XP_Bool +board_handlePenDown( BoardCtxt* board, XP_U16 x, XP_U16 y, XP_Bool* handled ) +{ + XP_Bool result = XP_FALSE; + XP_Bool penDidSomething; + BoardObjectType onWhich; + + board->srcIsPen = XP_TRUE; + + penDidSomething = pointOnSomething( board, x, y, &onWhich ); + + if ( !penDidSomething ) { + board->penDownObject = OBJ_NONE; + } else { + +#ifdef KEYBOARD_NAV + /* clear focus as soon as pen touches board */ + result = invalFocusOwner( board ); + board->hideFocus = XP_TRUE; + if ( board->boardObscuresTray ) { + figureBoardRect( board ); + } +#endif + + result = handleLikeDown( board, onWhich, x, y ) || result; + } + *handled = penDidSomething; + + return result; +} /* board_handlePenDown */ +#endif + +XP_Bool +board_handlePenMove( BoardCtxt* board, XP_U16 xx, XP_U16 yy ) +{ + XP_Bool result = dragDropInProgress(board) + && dragDropContinue( board, xx, yy ); + return result; +} /* board_handlePenMove */ + +/* Called when user taps on the board and a tray tile's selected. + */ +static XP_Bool +moveSelTileToBoardXY( BoardCtxt* board, XP_U16 col, XP_U16 row ) +{ + XP_Bool result; + XP_U16 selPlayer = board->selPlayer; + TileBit bits = board->selInfo->traySelBits; + XP_U16 tileIndex; + + if ( bits == NO_TILES ) { + return XP_FALSE; + } + + tileIndex = indexForBits( bits ); + + result = tileIndex < model_getNumTilesInTray( board->model, selPlayer ) + && moveTileToBoard( board, col, row, tileIndex, EMPTY_TILE ); + + if ( result ) { + XP_U16 leftInTray; + + leftInTray = model_getNumTilesInTray( board->model, selPlayer ); + if ( leftInTray == 0 ) { + bits = NO_TILES; + } else if ( leftInTray <= tileIndex ) { + bits = 1 << (tileIndex-1); + board_invalTrayTiles( board, bits ); + } + board->selInfo->traySelBits = bits; + } + + return result; +} /* moveSelTileToBoardXY */ + +XP_Bool +cellOccupied( const BoardCtxt* board, XP_U16 col, XP_U16 row, + XP_Bool inclPending ) +{ + Tile tile; + XP_Bool ignr; + XP_Bool result; + + flipIf( board, col, row, &col, &row ); + result = model_getTile( board->model, col, row, inclPending, + board->selPlayer, &tile, + &ignr, &ignr, &ignr ); + return result; +} /* cellOccupied */ + +/* If I tap on the arrow, transform it. If I tap in an empty square where + * it's not, move it there, making it visible if it's not already. + */ +static XP_Bool +tryMoveArrow( BoardCtxt* board, XP_U16 col, XP_U16 row ) +{ + XP_Bool result = XP_FALSE; + + if ( !cellOccupied( board, col, row, + board->trayVisState == TRAY_REVEALED ) ) { + + BoardArrow* arrow = &board->selInfo->boardArrow; + + if ( arrow->visible && arrow->col == col && arrow->row == row ) { + /* change it; if vertical, hide; else if horizontal make + vertical */ + if ( arrow->vert ) { + arrow->visible = XP_FALSE; + } else { + arrow->vert = XP_TRUE; + } + } else { + /* move it/reveal it */ + if ( !arrow->visible && !board->disableArrow ) { + arrow->visible = XP_TRUE; + arrow->vert = XP_FALSE; + } else { + invalArrowCell( board ); + } + arrow->col = (XP_U8)col; + arrow->row = (XP_U8)row; + } + invalCell( board, col, row ); + result = XP_TRUE; + } + return result; +} /* tryMoveArrow */ + +XP_Bool +holdsPendingTile( BoardCtxt* board, XP_U16 pencol, XP_U16 penrow ) +{ + Tile tile; + XP_Bool ignore, isPending; + XP_U16 modcol, modrow; + flipIf( board, pencol, penrow, &modcol, &modrow ); + + return model_getTile( board->model, modcol, modrow, XP_TRUE, + board->selPlayer, &tile, &ignore, &isPending, + (XP_Bool*)NULL ) + && isPending; +} /* holdsPendingTile */ + +/* Did I tap on a tile on the board that I have not yet committed? If so, + * return it to the tray. But don't do this in drag-and-drop case since it's + * too easy to accidentally tap and there are better ways. + */ +static XP_Bool +tryReplaceTile( BoardCtxt* board, XP_U16 pencol, XP_U16 penrow ) +{ + XP_Bool result = XP_FALSE; + + if ( holdsPendingTile( board, pencol, penrow ) ) { + XP_U16 modcol, modrow; + flipIf( board, pencol, penrow, &modcol, &modrow ); + + model_moveBoardToTray( board->model, board->selPlayer, + modcol, modrow, -1 ); + setArrow( board, pencol, penrow ); + result = XP_TRUE; + + } + return result; +} /* tryReplaceTile */ + +static XP_Bool +handleActionInCell( BoardCtxt* board, XP_U16 col, XP_U16 row, XP_Bool isPen ) +{ + return moveSelTileToBoardXY( board, col, row ) + || tryMoveArrow( board, col, row ) + || (!isPen && tryReplaceTile( board, col, row )) + ; +} /* handleActionInCell */ +#endif /* POINTER_SUPPORT || KEYBOARD_NAV */ + +static XP_Bool +exitTradeMode( BoardCtxt* board ) +{ + PerTurnInfo* pti = board->selInfo; + invalSelTradeWindow( board ); + pti->tradeInProgress = XP_FALSE; + board_invalTrayTiles( board, pti->traySelBits ); + pti->traySelBits = 0x00; + return XP_TRUE; +} /* exitTradeMode */ + +#if defined POINTER_SUPPORT || defined KEYBOARD_NAV +static XP_Bool +handlePenUpInternal( BoardCtxt* board, XP_U16 xx, XP_U16 yy, XP_Bool isPen ) +{ + XP_Bool draw = XP_FALSE; + XP_Bool dragged = XP_FALSE; + BoardObjectType prevObj = board->penDownObject; + + /* prevent timer from firing after pen lifted. Set now rather than later + in case we put up a modal alert/dialog that must be dismissed before + exiting this function (which might give timer time to fire. */ + board->penDownObject = OBJ_NONE; + + if ( dragDropInProgress(board) ) { + draw = dragDropEnd( board, xx, yy, &dragged ); + } + if ( dragged ) { + /* do nothing further */ + } else if ( board->penTimerFired ) { + if ( valHintMiniWindowActive( board ) ) { + hideMiniWindow( board, XP_TRUE, MINIWINDOW_VALHINT ); + } + draw = XP_TRUE; /* might have cancelled a drag */ + /* Need to clean up if there's been any dragging happening */ + board->penTimerFired = XP_FALSE; + } else { + BoardObjectType onWhich; + if ( pointOnSomething( board, xx, yy, &onWhich ) ) { + + switch( onWhich ) { + case OBJ_SCORE: + if ( prevObj == OBJ_SCORE ) { + draw = handlePenUpScore( board, xx, yy ) || draw; + } + break; + case OBJ_BOARD: + if ( prevObj == OBJ_BOARD && checkRevealTray(board) ) { + + if ( TRADE_IN_PROGRESS(board) ) { + if ( ptOnTradeWindow( board, xx, yy )) { + draw = exitTradeMode( board ) || draw; + } + } else { + XP_U16 col, row; + coordToCell( board, board->penDownX, board->penDownY, + &col, &row ); + draw = handleActionInCell( board, col, row, + isPen ) || draw; + } + } + break; + case OBJ_TRAY: + if ( board->trayVisState != TRAY_REVEALED ) { + draw = askRevealTray( board ) || draw; + } else { + draw = handlePenUpTray( board, xx, yy ) || draw; + } + break; + default: + XP_ASSERT( XP_FALSE ); + } + } + } + + return draw; +} /* handlePenUpInternal */ + +XP_Bool +board_handlePenUp( BoardCtxt* board, XP_U16 x, XP_U16 y ) +{ + return handlePenUpInternal( board, x, y, XP_TRUE ); +} +#endif /* #ifdef POINTER_SUPPORT */ + +#ifdef KEYBOARD_NAV +void +getRectCenter( const XP_Rect* rect, XP_U16* xp, XP_U16* yp ) +{ + *xp = rect->left + ( rect->width >> 1 ); + *yp = rect->top + ( rect->height >> 1 ); +} + +static void +getFocussedCellCenter( BoardCtxt* board, XP_U16* xp, XP_U16* yp ) +{ + XP_Rect rect; + BdCursorLoc* cursorLoc = &board->selInfo->bdCursor; + + getCellRect( board, cursorLoc->col, cursorLoc->row, &rect ); + getRectCenter( &rect, xp, yp ); +} + +static void +getFocussedScoreCenter( BoardCtxt* board, XP_U16* xp, XP_U16* yp ) +{ + XP_Rect* rectPtr; + XP_S16 scoreCursorLoc = board->scoreCursorLoc; + if ( CURSOR_LOC_REM == scoreCursorLoc ) { + rectPtr = &board->remRect; + } else { + rectPtr = &board->pti[scoreCursorLoc-1].scoreRects; + } + getRectCenter( rectPtr, xp, yp ); +} + +static XP_Bool +focusToCoords( BoardCtxt* board, XP_U16* xp, XP_U16* yp ) +{ + XP_Bool result = board->focusHasDived; + if ( result ) { + switch( board->focussed ) { + case OBJ_NONE: + result = XP_FALSE; + break; + case OBJ_BOARD: + getFocussedCellCenter( board, xp, yp ); + break; + case OBJ_TRAY: + getFocussedTileCenter( board, xp, yp ); + break; + case OBJ_SCORE: + getFocussedScoreCenter( board, xp, yp ); + break; + } + } + + return result; +} /* focusToCoords */ + +/* The focus keys are special because they can cause focus to leave one object + * and move to another (which the platform needs to be involved with). On + * palm, that only works if the keyDown handler claims not to have handled the + * event. So we must preflight, determining if the keyUp handler will handle + * the event should it get to it. If it wouldn't, then the platform wants a + * chance not to generate a keyUp event at all. + */ +static XP_Bool +handleFocusKeyUp( BoardCtxt* board, XP_Key key, XP_Bool preflightOnly, + XP_Bool* pHandled ) +{ + XP_Bool redraw = XP_FALSE; + if ( board->focusHasDived ) { + XP_Bool up = XP_FALSE; + if ( board->focussed == OBJ_BOARD ) { + redraw = board_moveCursor( board, key, preflightOnly, &up ); + } else if ( board->focussed == OBJ_SCORE ) { + redraw = moveScoreCursor( board, key, preflightOnly, &up ); + } else if ( board->focussed == OBJ_TRAY/* && checkRevealTray(board)*/ ) { + redraw = tray_moveCursor( board, key, preflightOnly, &up ); + } + if ( up ) { + if ( !preflightOnly ) { + (void)invalFocusOwner( board ); + board->focusHasDived = XP_FALSE; + (void)invalFocusOwner( board ); + } + } else { + *pHandled = redraw; + } + } else { + /* Do nothing. We don't handle transition among top-level + focussed objects. Platform must. */ + } + return redraw; +} /* handleFocusKeyUp */ + +XP_Bool +board_handleKeyRepeat( BoardCtxt* board, XP_Key key, XP_Bool* handled ) +{ + XP_Bool draw; + + if ( key == XP_RETURN_KEY ) { + *handled = XP_FALSE; + draw = XP_FALSE; + } else { + XP_Bool upHandled, downHandled; + draw = board_handleKeyUp( board, key, &upHandled ); + draw = board_handleKeyDown( board, key, &downHandled ) || draw; + *handled = upHandled || downHandled; + } + return draw; +} + +static XP_Bool +unhideFocus( BoardCtxt* board ) +{ + XP_Bool changing = board->hideFocus; + if ( changing ) { + board->hideFocus = XP_FALSE; + (void)invalFocusOwner( board ); + } + return changing; +} +#endif /* KEYBOARD_NAV */ + +#ifdef KEY_SUPPORT +XP_Bool +board_handleKeyDown( BoardCtxt* board, XP_Key key, XP_Bool* pHandled ) +{ + XP_Bool draw = XP_FALSE; +#ifdef KEYBOARD_NAV + XP_U16 x, y; + + board->srcIsPen = XP_FALSE; + + *pHandled = XP_FALSE; + + if ( key == XP_RETURN_KEY ) { + if ( focusToCoords( board, &x, &y ) ) { + draw = handleLikeDown( board, board->focussed, x, y ); + *pHandled = draw; + } + } else if ( board->focussed != OBJ_NONE ) { + if ( board->focusHasDived && (key == XP_RAISEFOCUS_KEY) ) { + *pHandled = XP_TRUE; + } else { + draw = handleFocusKeyUp( board, key, XP_TRUE, pHandled ) || draw; + } + } +#endif + return draw; +} /* board_handleKeyDown */ + +XP_Bool +board_handleKeyUp( BoardCtxt* board, XP_Key key, XP_Bool* pHandled ) +{ + XP_Bool redraw = XP_FALSE; + XP_Bool handled = XP_FALSE; + XP_Bool trayVisible = board->trayVisState == TRAY_REVEALED; + + switch( key ) { +#ifdef KEYBOARD_NAV + case XP_CURSOR_KEY_DOWN: + case XP_CURSOR_KEY_ALTDOWN: + case XP_CURSOR_KEY_UP: + case XP_CURSOR_KEY_ALTUP: + case XP_CURSOR_KEY_LEFT: + case XP_CURSOR_KEY_ALTLEFT: + case XP_CURSOR_KEY_RIGHT: + case XP_CURSOR_KEY_ALTRIGHT: + /* If focus is hidden, all we do is show it */ + if ( unhideFocus( board ) ) { + redraw = handled = XP_TRUE; + } else { + redraw = handleFocusKeyUp( board, key, XP_FALSE, &handled ); + } + break; +#endif + + case XP_CURSOR_KEY_DEL: + if ( trayVisible ) { + handled = redraw = replaceLastTile( board ); + } + break; + +#ifdef KEYBOARD_NAV + case XP_RAISEFOCUS_KEY: + if ( unhideFocus( board ) ) { + /* do nothing */ + } else if ( board->focussed != OBJ_NONE && board->focusHasDived ) { + (void)invalFocusOwner( board ); + board->focusHasDived = XP_FALSE; + (void)invalFocusOwner( board ); + } else { + break; /* skip setting handled */ + } + handled = redraw = XP_TRUE; + break; + + case XP_RETURN_KEY: + if ( unhideFocus( board ) ) { + handled = XP_TRUE; + } else if ( board->focussed != OBJ_NONE ) { + if ( board->focusHasDived ) { + XP_U16 xx, yy; + if ( focusToCoords( board, &xx, &yy ) ) { + redraw = handlePenUpInternal( board, xx, yy, XP_FALSE ); + handled = XP_TRUE; + } + } else { + (void)invalFocusOwner( board ); + board->focusHasDived = XP_TRUE; + redraw = invalFocusOwner( board ); + handled = XP_TRUE; + } + } + break; +#endif + + default: + XP_ASSERT( key >= XP_KEY_LAST ); + + if ( trayVisible ) { + if ( TRADE_IN_PROGRESS( board ) ) { + XP_S16 tileIndex = keyToIndex( board, key, NULL ); + handled = (tileIndex >= 0) + && handleTrayDuringTrade( board, tileIndex ); + } else { + XP_Bool gotArrow; + handled = moveKeyTileToBoard( board, key, &gotArrow ); + if ( handled && gotArrow && !advanceArrow( board ) ) { + setArrowVisible( board, XP_FALSE ); + } + } + } + redraw = handled; + } /* switch */ + + if ( !!pHandled ) { + *pHandled = handled; + } + return redraw; +} /* board_handleKeyUp */ + +XP_Bool +board_handleKey( BoardCtxt* board, XP_Key key, XP_Bool* handled ) +{ + XP_Bool handled1; + XP_Bool handled2; + XP_Bool draw; + + draw = board_handleKeyDown( board, key, &handled1 ); + draw = board_handleKeyUp( board, key, &handled2 ) || draw; + if ( !!handled ) { + *handled = handled1 || handled2; + } + + return draw; +} /* board_handleKey */ +#endif /* KEY_SUPPORT */ + +#ifdef KEYBOARD_NAV +static XP_Bool +invalFocusOwner( BoardCtxt* board ) +{ + XP_Bool draw = XP_TRUE; + PerTurnInfo* pti = board->selInfo; + switch( board->focussed ) { + case OBJ_SCORE: + board->scoreBoardInvalid = XP_TRUE; + break; + case OBJ_BOARD: + if ( board->focusHasDived ) { + BdCursorLoc loc = pti->bdCursor; + invalCell( board, loc.col, loc.row ); + checkScrollCell( board, loc.col, loc.row ); + } else { +#ifdef PERIMETER_FOCUS + invalPerimeter( board ); +#else + board_invalAllTiles( board ); +#endif + } + break; + case OBJ_TRAY: + if ( board->focusHasDived ) { + XP_S16 loc = pti->trayCursorLoc; + if ( loc == pti->dividerLoc ) { + board->dividerInvalid = XP_TRUE; + } else { + adjustForDivider( board, &loc ); + board_invalTrayTiles( board, 1 << loc ); + } + } else { + board_invalTrayTiles( board, ALLTILES ); + invalCellsUnderRect( board, &board->trayBounds ); + board->dividerInvalid = XP_TRUE; + } + break; + case OBJ_NONE: + draw = XP_FALSE; + break; + } + board->needsDrawing = draw || board->needsDrawing; + return draw; +} /* invalFocusOwner */ + +XP_Bool +board_focusChanged( BoardCtxt* board, BoardObjectType typ, XP_Bool gained ) +{ + XP_Bool draw = XP_FALSE; + /* Called when there's been a decision to advance the focus to a new + object, or when an object will lose it. Need to update internal data + structures, but also to communicate to client draw code in a way that + doesn't assume how it's representing focus. + + Should pop focus to top on gaining it. Calling code should not be + seeing internal movement as focus events, but as key events we handle + + One rule: each object must draw focus indicator entirely within its own + space. No interdependencies. So handling updating of focus indication + within the tray drawing process, for example, is ok. + + Hidden tray: there's no such thing as a hidden, focussed tray. It's + made TRAY_REVERSED when it gets focus. + + Problem: on palm at least take and lost are inverted: you get a take on + the new object before a lose on the previous one. So we want to ignore + lost events *except* when it's a loss of something we have currently -- + meaning the focus is moving to soemthing we don't control (a + platform-specific object) + */ + + if ( gained ) { + /* prefer to get !gained followed by gained. If caller doesn't do + that, do it for 'em. */ + if ( board->focussed != OBJ_NONE ) { + draw = board_focusChanged( board, board->focussed, XP_FALSE ); + } + + /* Are we losing focus we currently have elsewhere? */ + if ( typ != board->focussed ) { + draw = invalFocusOwner( board ) || draw; + } + board->focussed = typ; + board->focusHasDived = XP_FALSE; + if ( OBJ_TRAY == typ) { + board->trayHiddenPreFocus = board->trayVisState == TRAY_HIDDEN; + if ( board->trayHiddenPreFocus ) { + setTrayVisState( board, TRAY_REVERSED ); + } + } + draw = invalFocusOwner( board ) || draw; + } else { + /* we're losing it; inval and clear IFF we currently have same focus, + otherwise ignore */ + if ( typ == board->focussed ) { + draw = invalFocusOwner( board ) || draw; + board->focussed = OBJ_NONE; + + if ( (OBJ_TRAY == typ) && (board->trayVisState == TRAY_REVERSED) + && board->trayHiddenPreFocus ) { + setTrayVisState( board, TRAY_HIDDEN ); + } + } + } + + if ( draw ) { + figureBoardRect( board ); + } + + return draw; +} /* board_focusChanged */ + +XP_Bool +board_toggle_arrowDir( BoardCtxt* board ) +{ + BoardArrow* arrow = &board->selInfo->boardArrow; + if ( arrow->visible ) { + arrow->vert = !arrow->vert; + invalArrowCell( board ); + return XP_TRUE; + } else { + return XP_FALSE; + } +} /* board_toggle_cursorDir */ + +#endif /* KEYBOARD_NAV */ + +static XP_Bool +advanceArrow( BoardCtxt* board ) +{ + XP_Key key = board->selInfo->boardArrow.vert ? + XP_CURSOR_KEY_DOWN : XP_CURSOR_KEY_RIGHT; + + XP_ASSERT( board->trayVisState == TRAY_REVEALED ); + + return board_moveArrow( board, key ); +} /* advanceArrow */ + +static XP_Bool +figureNextLoc( const BoardCtxt* board, XP_Key cursorKey, + XP_Bool inclPending, XP_Bool forceFirst, + XP_U16* colP, XP_U16* rowP, + XP_Bool* XP_UNUSED_KEYBOARD_NAV(pUp) ) +{ + XP_S16 max; + XP_S16* useWhat = NULL; /* make compiler happy */ + XP_S16 end = 0; + XP_S16 incr = 0; + XP_U16 numCols, numRows; + XP_Bool result = XP_FALSE; + + /* XP_ASSERT( board->focussed == OBJ_BOARD ); */ + /* don't allow cursor's jumps to reveal hidden tiles */ + if ( cursorKey != XP_KEY_NONE ) { + + numRows = model_numRows( board->model ); + numCols = model_numCols( board->model ); + + switch ( cursorKey ) { + + case XP_CURSOR_KEY_DOWN: + incr = 1; + useWhat = (XP_S16*)rowP; + max = numRows - 1; + end = max; + break; + case XP_CURSOR_KEY_UP: + incr = -1; + useWhat = (XP_S16*)rowP; + max = numRows - 1; + end = 0; + break; + case XP_CURSOR_KEY_LEFT: + incr = -1; + useWhat = (XP_S16*)colP; + max = numCols - 1; + end = 0; + break; + case XP_CURSOR_KEY_RIGHT: + incr = 1; + useWhat = (XP_S16*)colP; + max = numCols - 1; + end = max; + break; + default: + XP_LOGF( "%s: odd cursor key: %d", __func__, cursorKey ); + } + + if ( incr != 0 ) { + for ( ; ; ) { + if ( *useWhat == end ) { +#ifdef KEYBOARD_NAV + if ( !!pUp ) { + *pUp = XP_TRUE; + } +#endif + break; + } + *useWhat += incr; + if ( forceFirst + || !cellOccupied( board, *colP, *rowP, inclPending ) ) { + result = XP_TRUE; + break; + } + } + } + } + + return result; +} /* figureNextLoc */ + +static XP_Bool +board_moveArrow( BoardCtxt* board, XP_Key cursorKey ) +{ + XP_U16 col, row; + XP_Bool changed; + + setArrowVisible( board, XP_TRUE ); + (void)getArrow( board, &col, &row ); + changed = figureNextLoc( board, cursorKey, XP_TRUE, XP_FALSE, + &col, &row, NULL ); + if ( changed ) { + (void)setArrow( board, col, row ); + } + return changed; +} /* board_moveArrow */ + +#ifdef KEYBOARD_NAV +static XP_Key +stripAlt( XP_Key key, XP_Bool* wasAlt ) +{ + XP_Bool alt = XP_FALSE; + switch ( key ) { + case XP_CURSOR_KEY_ALTDOWN: + case XP_CURSOR_KEY_ALTRIGHT: + case XP_CURSOR_KEY_ALTUP: + case XP_CURSOR_KEY_ALTLEFT: + alt = XP_TRUE; + --key; + default: + break; + } + + if ( !!wasAlt ) { + *wasAlt = alt; + } + return key; +} /* stripAlt */ + +static XP_Bool +board_moveCursor( BoardCtxt* board, XP_Key cursorKey, XP_Bool preflightOnly, + XP_Bool* up ) +{ + PerTurnInfo* pti = board->selInfo; + BdCursorLoc loc = pti->bdCursor; + XP_U16 col = loc.col; + XP_U16 row = loc.row; + XP_Bool changed; + + XP_Bool altSet; + cursorKey = stripAlt( cursorKey, &altSet ); + + changed = figureNextLoc( board, cursorKey, XP_FALSE, !altSet, + &col, &row, up ); + if ( changed && !preflightOnly ) { + invalCell( board, loc.col, loc.row ); + invalCell( board, col, row ); + loc.col = col; + loc.row = row; + pti->bdCursor = loc; + checkScrollCell( board, col, row ); + } + return changed; +} /* board_moveCursor */ +#endif + +XP_Bool +rectContainsPt( const XP_Rect* rect, XP_S16 x, XP_S16 y ) +{ + /* 7/4 Made <= into <, etc., because a tap on the right boundary of the + board was still mapped onto the board but dividing by scale put it in + the 15th column. If this causes other problems and the '=' chars have + to be put back then deal with that, probably by forcing an + out-of-bounds col/row to the nearest possible. */ + return ( rect->top <= y + && rect->left <= x + && (rect->top + rect->height) >= y + && (rect->left + rect->width) >= x ); +} /* rectContainsPt */ + +XP_Bool +rectsIntersect( const XP_Rect* rect1, const XP_Rect* rect2 ) +{ + XP_Bool intersect = XP_TRUE; + if ( rect1->top >= rect2->top + rect2->height ) { + intersect = XP_FALSE; + } else if ( rect1->left >= rect2->left + rect2->width ) { + intersect = XP_FALSE; + } else if ( rect2->top >= rect1->top + rect1->height ) { + intersect = XP_FALSE; + } else if ( rect2->left >= rect1->left + rect1->width ) { + intersect = XP_FALSE; + } + return intersect; +} /* rectsIntersect */ + +static XP_Bool +replaceLastTile( BoardCtxt* board ) +{ + XP_Bool result = XP_FALSE; + XP_U16 tilesInMove = model_getCurrentMoveCount( board->model, + board->selPlayer ); + + if ( tilesInMove > 0 ) { + XP_U16 col, row; + Tile tile; + XP_Bool isBlank; + XP_S16 index; + + index = -1; + model_getCurrentMoveTile( board->model, board->selPlayer, &index, + &tile, &col, &row, &isBlank ); + model_moveBoardToTray( board->model, board->selPlayer, col, row, -1 ); + + flipIf( board, col, row, &col, &row ); + setArrow( board, col, row ); + + result = XP_TRUE; + } + + return result; +} /* replaceLastTile */ + +XP_Bool +moveTileToBoard( BoardCtxt* board, XP_U16 col, XP_U16 row, XP_U16 tileIndex, + Tile blankFace ) +{ + if ( cellOccupied( board, col, row, XP_TRUE ) ) { + return XP_FALSE; + } + + flipIf( board, col, row, &col, &row ); + model_moveTrayToBoard( board->model, board->selPlayer, col, row, + tileIndex, blankFace ); + + return XP_TRUE; +} /* moveTileToBoard */ + +#ifdef KEY_SUPPORT +/* Return number between 0 and MAX_TRAY_TILES-1 for valid index, < 0 otherwise */ +static XP_S16 +keyToIndex( BoardCtxt* board, XP_Key key, Tile* blankFace ) +{ + /* Map numbers 1-7 to tiles in tray. This is a hack to workaround + temporary lack of key input on smartphone. */ + ModelCtxt* model = board->model; + XP_S16 tileIndex = -1; +# ifdef NUMBER_KEY_AS_INDEX + tileIndex = key - '0' - 1; /* user's model is 1-based; ours is 0-based */ + if (tileIndex >= model_getNumTilesInTray( model, board->selPlayer ) ) { + tileIndex = -1; /* error */ + } +# endif + + if ( tileIndex < 0 ) { + DictionaryCtxt* dict = model_getDictionary( model ); + Tile tile; + XP_UCHAR buf[2] = { key, '\0' }; + + /* Figure out if we have the tile in the tray */ + tile = dict_tileForString( dict, buf ); + if ( tile != EMPTY_TILE ) { /* in dict? */ + XP_S16 turn = board->selPlayer; + tileIndex = model_trayContains( model, turn, tile ); + if ( tileIndex < 0 ) { + Tile blankTile = dict_getBlankTile( dict ); + tileIndex = model_trayContains( model, turn, blankTile ); + if ( tileIndex >= 0 && !!blankFace ) { /* there's a blank for it */ + *blankFace = tile; + } + } + } + } + + return tileIndex; +} /* keyToIndex */ + +static XP_Bool +moveKeyTileToBoard( BoardCtxt* board, XP_Key cursorKey, XP_Bool* gotArrow ) +{ + XP_U16 col, row; + XP_Bool haveDest; + + XP_ASSERT( !TRADE_IN_PROGRESS( board ) ); + + /* Is there a cursor at all? */ + haveDest = getArrow( board, &col, &row ); + *gotArrow = haveDest; +#ifdef KEYBOARD_NAV + if ( !haveDest && (board->focussed == OBJ_BOARD) && board->focusHasDived ) { + BdCursorLoc loc = board->selInfo->bdCursor; + col = loc.col; + row = loc.row; + haveDest = XP_TRUE; + } +#endif + + if ( haveDest ) { + Tile blankFace = EMPTY_TILE; + XP_S16 tileIndex = keyToIndex( board, cursorKey, &blankFace ); + + haveDest = (tileIndex >= 0) + && moveTileToBoard( board, col, row, tileIndex, blankFace ); + } + + return haveDest; +} /* moveKeyTileToBoard */ +#endif /* #ifdef KEY_SUPPORT */ + +static void +setArrowFor( BoardCtxt* board, XP_U16 player, XP_U16 col, XP_U16 row ) +{ + BoardArrow* arrow = &board->pti[player].boardArrow; + invalCell( board, arrow->col, arrow->row ); + invalCell( board, col, row ); + + arrow->col = (XP_U8)col; + arrow->row = (XP_U8)row; + + checkScrollCell( board, col, row ); +} /* setArrowFor */ + +static void +setArrow( BoardCtxt* board, XP_U16 col, XP_U16 row ) +{ + setArrowFor( board, board->selPlayer, col, row ); +} /* setArrow */ + +static XP_Bool +getArrowFor( BoardCtxt* board, XP_U16 player, XP_U16* col, XP_U16* row ) +{ + BoardArrow* arrow = &board->pti[player].boardArrow; + *col = arrow->col; + *row = arrow->row; + return arrow->visible; +} /* getArrowFor */ + +static XP_Bool +getArrow( BoardCtxt* board, XP_U16* col, XP_U16* row ) +{ + return getArrowFor( board, board->selPlayer, col, row ); +} /* getArrow */ + +static XP_Bool +setArrowVisible( BoardCtxt* board, XP_Bool visible ) +{ + return setArrowVisibleFor( board, board->selPlayer, visible ); +} /* setArrowVisible */ + +static XP_Bool +setArrowVisibleFor( BoardCtxt* board, XP_U16 player, XP_Bool visible ) +{ + BoardArrow* arrow = &board->pti[player].boardArrow; + XP_Bool result = arrow->visible; + if ( arrow->visible != visible ) { + arrow->visible = visible; + invalArrowCell( board ); + } + return result; +} /* setArrowVisibleFor */ + +/***************************************************************************** + * Listener callbacks + ****************************************************************************/ +static void +boardCellChanged( void* p_board, XP_U16 turn, XP_U16 modelCol, XP_U16 modelRow, + XP_Bool added ) +{ + BoardCtxt* board = (BoardCtxt*)p_board; + XP_Bool pending, found, ignoreBlank; + Tile ignoreTile; + XP_U16 col, row; + + flipIf( board, modelCol, modelRow, &col, &row ); + + /* for each player, check if the tile overwrites the cursor */ + found = model_getTile( board->model, modelCol, modelRow, XP_TRUE, turn, + &ignoreTile, &ignoreBlank, &pending, + (XP_Bool*)NULL ); + + XP_ASSERT( !added || found ); /* if added is true so must found be */ + + if ( !added && !found ) { + /* nothing to do */ + } else { + XP_U16 ccol, crow; + XP_U16 player, nPlayers; + + nPlayers = board->gi->nPlayers; + + /* This is a bit gross. It's an attempt to combine two loops. In one + case, we've added a tile (tentative move, say) and need to hide the + cursor for the player who added the tile. In the second, we're + changing the state of a tile (from tentative to permanent, say) and + now need to worry about the players who turn it _isn't_ but whose + cursor may be in the wrong place. This latter case happens a lot + in multi-device games when a turn shows up from elsewhere and plops + down on the board. */ + for ( player = 0; player < nPlayers; ++player ) { + if ( (added && (!pending || turn == board->selPlayer)) + || (found && turn != board->selPlayer) ) { + if ( getArrowFor( board, player, &ccol, &crow ) + && ( (ccol == col) && (crow == row) ) ) { + setArrowVisibleFor( board, player, XP_FALSE ); + } + } + } + + checkScrollCell( board, col, row ); + } + + invalCell( (BoardCtxt*)p_board, col, row ); +} /* boardCellChanged */ + +static void +boardTilesChanged( void* p_board, XP_U16 turn, XP_S16 index1, XP_S16 index2 ) +{ + BoardCtxt* board = (BoardCtxt*)p_board; + if ( turn == board->selPlayer ) { + invalTrayTilesBetween( board, index1, index2 ); + } +} /* boardTilesChanged */ + +static void +dictChanged( void* p_board, const DictionaryCtxt* oldDict, + const DictionaryCtxt* newDict ) +{ + BoardCtxt* board = (BoardCtxt*)p_board; + XP_ASSERT( !!board->draw ); + if ( (NULL == oldDict) || (oldDict != newDict) ) { + draw_dictChanged( board->draw, newDict ); + } +} + +static void +boardTurnChanged( void* p_board ) +{ + BoardCtxt* board = (BoardCtxt*)p_board; + XP_S16 nextPlayer; + + XP_ASSERT( board->timerSaveCount == 0 ); + + board->gameOver = XP_FALSE; + + nextPlayer = chooseBestSelPlayer( board ); + if ( nextPlayer >= 0 ) { + XP_U16 nHumans = gi_countLocalHumans( board->gi ); + selectPlayerImpl( board, nextPlayer, nHumans <= 1 ); + } + + setTimerIf( board ); + + board->scoreBoardInvalid = XP_TRUE; + + util_turnChanged( board->util ); +} /* boardTurnChanged */ + +static void +boardGameOver( void* closure ) +{ + BoardCtxt* board = (BoardCtxt*)closure; + board->scoreBoardInvalid = XP_TRUE; /* not sure if this will do it. */ + board->gameOver = XP_TRUE; + util_notifyGameOver( board->util ); +} /* boardGameOver */ + +static void +forceRectToBoard( const BoardCtxt* board, XP_Rect* rect ) +{ + XP_Rect bounds = board->boardBounds; + if ( rect->left < bounds.left ) { + rect->left = bounds.left; + } + if ( rect->top < bounds.top ) { + rect->top = bounds.top; + } + if ( (rect->left + rect->width) > (bounds.left + bounds.width) ) { + rect->left -= (rect->left+rect->width) - (bounds.left+bounds.width); + } + if ( rect->top + rect->height > bounds.top + bounds.height ) { + rect->top -= (rect->top+rect->height) - (bounds.top+bounds.height); + } +} /* forceRectToBoard */ + +void +getDragCellRect( BoardCtxt* board, XP_U16 col, XP_U16 row, XP_Rect* rectP ) +{ + XP_Rect rect; + XP_U16 tmp; + + getCellRect( board, col, row, &rect ); + + tmp = rect.width; + rect.width = board->trayScaleH; + rect.left -= (rect.width - tmp) / 2; + + tmp = rect.height; + rect.height = board->trayScaleV; + rect.top -= (rect.height - tmp) / 2; + + *rectP = rect; + forceRectToBoard( board, rectP ); +} + +void +invalDragObj( BoardCtxt* board, const DragObjInfo* di ) +{ + if ( OBJ_BOARD == di->obj ) { + XP_Rect rect; + getDragCellRect( board, di->u.board.col, di->u.board.row, &rect ); + invalCellsUnderRect( board, &rect ); + } else if ( OBJ_TRAY == di->obj ) { + board_invalTrayTiles( board, 1 << di->u.tray.index ); + } +} /* invalCurObj */ + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/board.h b/xwords4/common/board.h new file mode 100644 index 000000000..88091fb46 --- /dev/null +++ b/xwords4/common/board.h @@ -0,0 +1,167 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __BOARD_H__ +#define __BOARD_H__ + +#include "comtypes.h" +#include "model.h" +#include "server.h" +#include "draw.h" +#include "xwstream.h" + +/* typedef struct BoardVTable { */ +/* } BoardVTable; */ + +#ifdef CPLUS +extern "C" { +#endif + +typedef enum { + /* keep these three together: for the cursor */ + XP_KEY_NONE = 0, + + XP_CURSOR_KEY_DOWN, + XP_CURSOR_KEY_ALTDOWN, + XP_CURSOR_KEY_RIGHT, + XP_CURSOR_KEY_ALTRIGHT, + XP_CURSOR_KEY_UP, + XP_CURSOR_KEY_ALTUP, + XP_CURSOR_KEY_LEFT, + XP_CURSOR_KEY_ALTLEFT, + + XP_CURSOR_KEY_DEL, + XP_RAISEFOCUS_KEY, + XP_RETURN_KEY, + + XP_KEY_LAST +} XP_Key; + +#define BONUS_HINT_INTERVAL 15 /* stolen from xwords.c */ + +/* typedef struct BoardCtxt BoardCtxt; */ + + +BoardCtxt* board_make( MPFORMAL ModelCtxt* model, ServerCtxt* server, + DrawCtx* draw, XW_UtilCtxt* util ); +BoardCtxt* board_makeFromStream( MPFORMAL XWStreamCtxt* stream, + ModelCtxt* model, ServerCtxt* server, + DrawCtx* draw, XW_UtilCtxt* util, + XP_U16 nPlayers ); + +void board_destroy( BoardCtxt* board ); + +void board_writeToStream( BoardCtxt* board, XWStreamCtxt* stream ); + +void board_setPos( BoardCtxt* board, XP_U16 left, XP_U16 top, + XP_Bool leftHanded ); +void board_reset( BoardCtxt* board ); + +/* Vertical scroll support; offset is in rows, not pixels */ +XP_Bool board_setYOffset( BoardCtxt* board, XP_U16 newOffset ); +XP_U16 board_getYOffset( const BoardCtxt* board ); + +void board_setScoreboardLoc( BoardCtxt* board, + XP_U16 scoreLeft, XP_U16 scoreTop, + XP_U16 scoreWidth, XP_U16 scoreHeight, + XP_Bool divideHorizontally ); +void board_setTimerLoc( BoardCtxt* board, + XP_U16 timerLeft, XP_U16 timerTop, + XP_U16 timerWidth, XP_U16 timerHeight ); +void board_invalAll( BoardCtxt* board ); +void board_invalRect( BoardCtxt* board, XP_Rect* rect ); + +XP_Bool board_draw( BoardCtxt* board ); + +XP_Bool board_get_flipped( const BoardCtxt* board ); +XP_Bool board_flip( BoardCtxt* board ); +XP_Bool board_get_showValues( const BoardCtxt* board ); +XP_Bool board_toggle_showValues( BoardCtxt* board ); +XP_Bool board_getShowColors( BoardCtxt* board ); +XP_Bool board_setShowColors( BoardCtxt* board, XP_Bool showColors ); +XP_Bool board_replaceTiles( BoardCtxt* board ); + +XP_Bool board_requestHint( BoardCtxt* board, +#ifdef XWFEATURE_SEARCHLIMIT + XP_Bool useTileLimits, +#endif + XP_Bool* workRemainsP ); + +void board_setScale( BoardCtxt* board, XP_U16 hScale, XP_U16 vScale ); +void board_getScale( BoardCtxt* board, XP_U16* hScale, XP_U16* vScale ); + +XP_Bool board_prefsChanged( BoardCtxt* board, CommonPrefs* cp ); + +BoardObjectType board_getFocusOwner( BoardCtxt* board ); + +void board_hiliteCellAt( BoardCtxt* board, XP_U16 col, XP_U16 row ); + +void board_resetEngine( BoardCtxt* board ); + +XP_Bool board_commitTurn( BoardCtxt* board ); + +void board_pushTimerSave( BoardCtxt* board ); +void board_popTimerSave( BoardCtxt* board ); + +void board_formatRemainingTiles( BoardCtxt* board, XWStreamCtxt* stream ); + +#ifdef POINTER_SUPPORT +XP_Bool board_handlePenDown( BoardCtxt* board, XP_U16 x, XP_U16 y, + XP_Bool* handled ); +XP_Bool board_handlePenMove( BoardCtxt* board, XP_U16 x, XP_U16 y ); +XP_Bool board_handlePenUp( BoardCtxt* board, XP_U16 x, XP_U16 y ); +#endif + +#ifdef KEY_SUPPORT +XP_Bool board_handleKey( BoardCtxt* board, XP_Key key, XP_Bool* handled ); + +# ifdef KEYBOARD_NAV +XP_Bool board_handleKeyUp( BoardCtxt* board, XP_Key key, XP_Bool* handled ); +XP_Bool board_handleKeyDown( BoardCtxt* board, XP_Key key, XP_Bool* handled ); +XP_Bool board_handleKeyRepeat( BoardCtxt* board, XP_Key key, XP_Bool* handled ); +XP_Bool board_focusChanged( BoardCtxt* board, BoardObjectType typ, + XP_Bool gained ); +XP_Bool board_toggle_arrowDir( BoardCtxt* board ); +# endif +#endif + +/******************** Tray methods ********************/ +#define NO_TILES ((TileBit)0) + +void board_setTrayLoc( BoardCtxt* board, XP_U16 trayLeft, XP_U16 trayTop, + XP_U16 trayWidth, XP_U16 trayHeight, + XP_U16 minDividerWidth ); +XP_Bool board_hideTray( BoardCtxt* board ); +XP_Bool board_showTray( BoardCtxt* board ); +XW_TrayVisState board_getTrayVisState( const BoardCtxt* board ); + +void board_invalTrayTiles( BoardCtxt* board, TileBit what ); +XP_Bool board_juggleTray( BoardCtxt* board ); +XP_Bool board_beginTrade( BoardCtxt* board ); + +#if defined FOR_GREMLINS +XP_Bool board_moveDivider( BoardCtxt* board, XP_Bool right ); +#endif + + +#ifdef CPLUS +} +#endif + +#endif diff --git a/xwords4/common/boarddrw.c b/xwords4/common/boarddrw.c new file mode 100644 index 000000000..32da4583a --- /dev/null +++ b/xwords4/common/boarddrw.c @@ -0,0 +1,568 @@ +/* -*-mode: C; fill-column: 78; compile-command: "cd ../linux && make MEMDEBUG=TRUE"; -*- */ +/* + * Copyright 1997 - 2008 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Re: boards that can't fit on the screen. Let's have an assumption, that + * the tray is always either below the board or overlapping its bottom. There + * is never any board visible below the tray. But it's possible to have a + * board small enough that scrolling is necessary even with the tray hidden. + * + * Currently we don't specify the board bounds. We give top,left and the size + * of cells, and the board figures out the bounds. That's probably a mistake. + * Better to give bounds, and maybe a min scale, and let it figure out how + * many cells can be visible. Could it also decide if the tray should overlap + * or be below? Some platforms have to own that decision since the tray is + * narrower than the board. So give them separate bounds-setting functions, + * and let the board code figure out if they overlap. + * + * Problem: the board size must always be a multiple of the scale. The + * platform-specific code has an easy time doing that math. The board can't: + * it'd have to take bounds, then spit them back out slightly modified. It'd + * also have to refuse to work (maybe just assert) if asked to take bounds + * before it had a min_scale. + * + * Another way of looking at it closer to the current: the board's position + * and the tray's bounds determine the board's bounds. If the board's vScale + * times the number of rows places its would-be bottom at or above the bottom + * of the tray, then it's potentially visible. If its would-be bottom is + * above the top of the tray, no scrolling is needed. But if it's below the + * tray entirely then scrolling will happen even with the tray hidden. As + * above, we assume the board never appears below the tray. + */ + +#include "comtypes.h" +#include "board.h" +#include "scorebdp.h" +#include "game.h" +#include "server.h" +#include "comms.h" /* for CHANNEL_NONE */ +#include "dictnry.h" +#include "draw.h" +#include "engine.h" +#include "util.h" +#include "mempool.h" /* debug only */ +#include "memstream.h" +#include "strutils.h" +#include "LocalizedStrIncludes.h" + +#include "boardp.h" +#include "dragdrpp.h" +#include "dbgutil.h" + +#ifdef CPLUS +extern "C" { +#endif + +static XP_Bool drawCell( BoardCtxt* board, XP_U16 col, XP_U16 row, + XP_Bool skipBlanks ); +static void drawBoard( BoardCtxt* board ); +static void scrollIfCan( BoardCtxt* board ); +static XP_Bool cellFocused( const BoardCtxt* board, XP_U16 col, XP_U16 row ); +static void drawTradeWindowIf( BoardCtxt* board ); + + +#ifdef XWFEATURE_SEARCHLIMIT +static HintAtts figureHintAtts( BoardCtxt* board, XP_U16 col, XP_U16 row ); +#else +# define figureHintAtts(b,c,r) HINT_BORDER_NONE +#endif + + +#ifdef POINTER_SUPPORT +static void drawDragTileIf( BoardCtxt* board ); +#endif + +#ifdef KEYBOARD_NAV +#ifdef PERIMETER_FOCUS +static void +invalOldPerimeter( BoardCtxt* board ) +{ + /* We need to inval the center of the row that's moving into the center + from a border (at which point it got borders drawn on it.) */ + XP_S16 diff = board->yOffset - board->prevYScrollOffset; + XP_U16 firstRow, lastRow; + XP_ASSERT( diff != 0 ); + if ( diff < 0 ) { + /* moving up; inval row previously on bottom */ + firstRow = board->yOffset + 1; + lastRow = board->prevYScrollOffset; + } else { + XP_U16 nVisible = board->lastVisibleRow - board->yOffset + 1; + lastRow = board->prevYScrollOffset + nVisible - 1; + firstRow = lastRow - diff + 1; + } + XP_ASSERT( firstRow <= lastRow ); + while ( firstRow <= lastRow ) { + board->redrawFlags[firstRow] |= ~0; + ++firstRow; + } +} /* invalOldPerimeter */ +#endif +#endif + +/* if any of a blank's neighbors is invalid, so must the blank become (since + * they share a border and drawing the neighbor will redraw the blank's border + * too) We'll want to redraw only those blanks that are themselves already + * invalid *OR* that become invalid this way, and so we'll build a new + * BlankQueue of them and replace the old. + * + * I'm not sure what happens if two blanks are neighbors. + */ +#define INVAL_BIT_SET(b,c,r) (((b)->redrawFlags[(r)] & (1 <<(c))) != 0) +static void +invalBlanksWithNeighbors( BoardCtxt* board, BlankQueue* bqp ) +{ + XP_U16 i; + XP_U16 lastCol, lastRow; + BlankQueue invalBlanks; + XP_U16 nInvalBlanks = 0; + + lastCol = model_numCols(board->model) - 1; + lastRow = model_numRows(board->model) - 1; + + for ( i = 0; i < bqp->nBlanks; ++i ) { + XP_U16 modelCol = bqp->col[i]; + XP_U16 modelRow = bqp->row[i]; + XP_U16 col, row; + + flipIf( board, modelCol, modelRow, &col, &row ); + + if ( INVAL_BIT_SET( board, col, row ) + || (col > 0 && INVAL_BIT_SET( board, col-1, row )) + || (col < lastCol && INVAL_BIT_SET( board, col+1, row )) + || (row > 0 && INVAL_BIT_SET( board, col, row-1 )) + || (row < lastRow && INVAL_BIT_SET( board, col, row+1 )) ) { + + invalCell( board, col, row ); + + invalBlanks.col[nInvalBlanks] = (XP_U8)col; + invalBlanks.row[nInvalBlanks] = (XP_U8)row; + ++nInvalBlanks; + } + } + invalBlanks.nBlanks = nInvalBlanks; + XP_MEMCPY( bqp, &invalBlanks, sizeof(*bqp) ); +} /* invalBlanksWithNeighbors */ + + +#ifdef XWFEATURE_SEARCHLIMIT +static HintAtts +figureHintAtts( BoardCtxt* board, XP_U16 col, XP_U16 row ) +{ + HintAtts result = HINT_BORDER_NONE; + + /* while lets us break to exit... */ + while ( board->trayVisState == TRAY_REVEALED + && !board->gi->hintsNotAllowed + && board->gi->allowHintRect ) { + BdHintLimits limits; + if ( dragDropGetHintLimits( board, &limits ) ) { + /* do nothing */ + } else if ( board->selInfo->hasHintRect ) { + limits = board->selInfo->limits; + } else { + break; + } + + if ( col < limits.left ) break; + if ( row < limits.top ) break; + if ( col > limits.right ) break; + if ( row > limits.bottom ) break; + + if ( col == limits.left ) { + result |= HINT_BORDER_LEFT; + } + if ( col == limits.right ) { + result |= HINT_BORDER_RIGHT; + } + if ( row == limits.top) { + result |= HINT_BORDER_TOP; + } + if ( row == limits.bottom ) { + result |= HINT_BORDER_BOTTOM; + } +#ifndef XWFEATURE_SEARCHLIMIT_DOCENTERS + if ( result == HINT_BORDER_NONE ) { + result = HINT_BORDER_CENTER; + } +#endif + break; + } + + return result; +} /* figureHintAtts */ +#endif + +static XP_Bool +rectContainsRect( XP_Rect* rect1, XP_Rect* rect2 ) +{ + return ( rect1->top <= rect2->top + && rect1->left <= rect2->left + && rect1->top + rect1->height >= rect2->top + rect2->height + && rect1->left + rect1->width >= rect2->left + rect2->width ); +} /* rectContainsRect */ + +static void +makeMiniWindowForTrade( BoardCtxt* board ) +{ + const XP_UCHAR* text; + + text = draw_getMiniWText( board->draw, INTRADE_MW_TEXT ); + + makeMiniWindowForText( board, text, MINIWINDOW_TRADING ); +} /* makeMiniWindowForTrade */ + +static void +drawBoard( BoardCtxt* board ) +{ + if ( board->needsDrawing + && draw_boardBegin( board->draw, + &board->boardBounds, + dfsFor( board, OBJ_BOARD ) ) ) { + + XP_Bool allDrawn = XP_TRUE; + XP_S16 lastCol, i; + XP_S16 row; + ModelCtxt* model = board->model; + BlankQueue bq; + XP_Rect arrowRect; + + scrollIfCan( board ); /* this must happen before we count blanks + since it invalidates squares */ + + /* This is freaking expensive!!!! PENDING FIXME Can't we start from + what's invalid rather than scanning the entire model every time + somebody dirties a single cell? */ + model_listPlacedBlanks( model, board->selPlayer, + board->trayVisState == TRAY_REVEALED, &bq ); + invalBlanksWithNeighbors( board, &bq ); + + for ( row = board->yOffset; row <= board->lastVisibleRow; ++row ) { + XP_U16 rowFlags = board->redrawFlags[row]; + if ( rowFlags != 0 ) { + XP_U16 colMask; + XP_U16 failedBits = 0; + lastCol = model_numCols( model ); + for ( colMask = 1<<(lastCol-1); lastCol--; colMask >>= 1 ) { + if ( (rowFlags & colMask) != 0 ) { + if ( !drawCell( board, lastCol, row, XP_TRUE )) { + failedBits |= colMask; + allDrawn = XP_FALSE; + } + } + } + board->redrawFlags[row] = failedBits; + } + } + + /* draw the blanks we skipped before */ + for ( i = 0; i < bq.nBlanks; ++i ) { + if ( !drawCell( board, bq.col[i], bq.row[i], XP_FALSE ) ) { + allDrawn = XP_FALSE; + } + } + + if ( board->trayVisState == TRAY_REVEALED ) { + BoardArrow* arrow = &board->selInfo->boardArrow; + + if ( arrow->visible ) { + XP_U16 col = arrow->col; + XP_U16 row = arrow->row; + if ( getCellRect( board, col, row, &arrowRect ) ) { + XWBonusType bonus; + HintAtts hintAtts; + CellFlags flags = CELL_NONE; + bonus = util_getSquareBonus( board->util, model, + col, row ); + hintAtts = figureHintAtts( board, col, row ); +#ifdef KEYBOARD_NAV + if ( cellFocused( board, col, row ) ) { + flags |= CELL_ISCURSOR; + } +#endif + draw_drawBoardArrow( board->draw, &arrowRect, bonus, + arrow->vert, hintAtts, flags ); + } + } + } + + /* I doubt the two of these can happen at the same time */ + drawTradeWindowIf( board ); +#ifdef POINTER_SUPPORT + drawDragTileIf( board ); +#endif + draw_objFinished( board->draw, OBJ_BOARD, &board->boardBounds, + dfsFor( board, OBJ_BOARD ) ); + + board->needsDrawing = !allDrawn; + } +} /* drawBoard */ + + +static XP_Bool +drawCell( BoardCtxt* board, XP_U16 col, XP_U16 row, XP_Bool skipBlanks ) +{ + XP_Bool success = XP_TRUE; + XP_Rect cellRect; + Tile tile; + XP_Bool isBlank, isEmpty, recent, pending = XP_FALSE; + XWBonusType bonus; + ModelCtxt* model = board->model; + DictionaryCtxt* dict = model_getDictionary( model ); + XP_U16 modelCol, modelRow; + + if ( dict != NULL && getCellRect( board, col, row, &cellRect ) ) { + + /* We want to invert EITHER the current pending tiles OR the most recent + * move. So if the tray is visible AND there are tiles missing from it, + * show them. Otherwise show the most recent move. + */ + XP_U16 selPlayer = board->selPlayer; + XP_U16 curCount = model_getCurrentMoveCount( model, selPlayer ); + XP_Bool showPending = board->trayVisState == TRAY_REVEALED + && curCount > 0; + + flipIf( board, col, row, &modelCol, &modelRow ); + + /* This 'while' is only here so I can 'break' below */ + while ( board->trayVisState == TRAY_HIDDEN || + !rectContainsRect( &board->trayBounds, &cellRect ) ) { + XP_UCHAR ch[4] = {'\0'}; + XP_S16 owner = -1; + XP_Bool invert = XP_FALSE; + XP_Bitmap bitmap = NULL; + XP_UCHAR* textP = NULL; + HintAtts hintAtts; + CellFlags flags = CELL_NONE; + XP_Bool isOrigin; + + isEmpty = !model_getTile( model, modelCol, modelRow, showPending, + selPlayer, &tile, &isBlank, + &pending, &recent ); + if ( dragDropIsBeingDragged( board, col, row, &isOrigin ) ) { + flags |= isOrigin? CELL_DRAGSRC : CELL_DRAGCUR; + if ( isEmpty && !isOrigin ) { + dragDropTileInfo( board, &tile, &isBlank ); + pending = XP_TRUE; + recent = XP_FALSE; + isEmpty = XP_FALSE; + } + } + + if ( isEmpty ) { + isBlank = XP_FALSE; + flags |= CELL_ISEMPTY; + } else if ( isBlank && skipBlanks ) { + break; + } else { + if ( board->showColors ) { + owner = (XP_S16)model_getCellOwner( model, modelCol, + modelRow ); + } + + invert = showPending? pending : recent; + + if ( board->showCellValues ) { + Tile valTile = isBlank? dict_getBlankTile( dict ) : tile; + XP_U16 val = dict_getTileValue( dict, valTile ); + XP_SNPRINTF( ch, sizeof(ch), (XP_UCHAR*)"%d", val ); + textP = ch; + } else { + if ( dict_faceIsBitmap( dict, tile ) ) { + bitmap = dict_getFaceBitmap( dict, tile, XP_FALSE ); + XP_ASSERT( !!bitmap ); + } + (void)dict_tilesToString( dict, &tile, 1, ch, sizeof(ch) ); + textP = ch; + } + } + bonus = util_getSquareBonus( board->util, model, col, row ); + hintAtts = figureHintAtts( board, col, row ); + + if ( (col==board->star_row) && (row==board->star_row) ) { + flags |= CELL_ISSTAR; + } + if ( invert ) { + flags |= CELL_HIGHLIGHT; + } + if ( isBlank ) { + flags |= CELL_ISBLANK; + } +#ifdef KEYBOARD_NAV + if ( cellFocused( board, col, row ) ) { + flags |= CELL_ISCURSOR; + } +#endif + + success = draw_drawCell( board->draw, &cellRect, textP, bitmap, + tile, owner, bonus, hintAtts, flags ); + break; + } + } + return success; +} /* drawCell */ + +#ifdef KEYBOARD_NAV +DrawFocusState +dfsFor( BoardCtxt* board, BoardObjectType obj ) +{ + DrawFocusState dfs; + if ( (board->focussed == obj) && !board->hideFocus ) { + if ( board->focusHasDived ) { + dfs = DFS_DIVED; + } else { + dfs = DFS_TOP; + } + } else { + dfs = DFS_NONE; + } + return dfs; +} /* dfsFor */ + +static XP_Bool +cellFocused( const BoardCtxt* board, XP_U16 col, XP_U16 row ) +{ + XP_Bool focussed = XP_FALSE; + + if ( (board->focussed == OBJ_BOARD) && !board->hideFocus ) { + if ( board->focusHasDived ) { + if ( (col == board->selInfo->bdCursor.col) + && (row == board->selInfo->bdCursor.row) ) { + focussed = XP_TRUE; + } + } else { +#ifdef PERIMETER_FOCUS + focussed = (col == 0) + || (col == model_numCols(board->model) - 1) + || (row == board->yOffset) + || (row == board->lastVisibleRow); +#else + focussed = XP_TRUE; +#endif + } + } + return focussed; +} /* cellFocused */ +#endif + +#ifdef POINTER_SUPPORT +static void +drawDragTileIf( BoardCtxt* board ) +{ + if ( dragDropInProgress( board ) ) { + XP_U16 col, row; + if ( dragDropGetBoardTile( board, &col, &row ) ) { + XP_Rect rect; + Tile tile; + XP_Bool isBlank; + XP_UCHAR buf[4]; + XP_UCHAR* face; + XP_Bitmap bitmap = NULL; + XP_S16 value; + CellFlags flags; + + getDragCellRect( board, col, row, &rect ); + + dragDropTileInfo( board, &tile, &isBlank ); + + face = getTileDrawInfo( board, tile, isBlank, &bitmap, + &value, buf, sizeof(buf) ); + + flags = CELL_DRAGCUR; + if ( isBlank ) { + flags |= CELL_ISBLANK; + } + if ( board->hideValsInTray && !board->showCellValues ) { + flags |= CELL_VALHIDDEN; + } + draw_drawTileMidDrag( board->draw, &rect, face, bitmap, value, + board->selPlayer, flags ); + } + } +} /* drawDragTileIf */ +#endif + +static void +scrollIfCan( BoardCtxt* board ) +{ + if ( board->yOffset != board->prevYScrollOffset ) { + XP_Rect scrollR = board->boardBounds; + XP_Bool scrolled; + XP_S16 dist; + +#ifdef PERIMETER_FOCUS + if ( (board->focussed == OBJ_BOARD) + && !board->focusHasDived + && !board->hideFocus ) { + invalOldPerimeter( board ); + } +#endif + invalSelTradeWindow( board ); + dist = (board->yOffset - board->prevYScrollOffset) + * board->boardVScale; + + scrolled = draw_vertScrollBoard( board->draw, &scrollR, dist, + dfsFor( board, OBJ_BOARD ) ); + + if ( scrolled ) { + /* inval the rows that have been scrolled into view. I'm cheating + making the client figure the inval rect, but Palm's the first + client and it does it so well.... */ + invalCellsUnderRect( board, &scrollR ); + } else { + board_invalAll( board ); + } + board->prevYScrollOffset = board->yOffset; + } +} /* scrollIfCan */ + +static void +drawTradeWindowIf( BoardCtxt* board ) +{ + if ( board->tradingMiniWindowInvalid && + TRADE_IN_PROGRESS(board) && board->trayVisState == TRAY_REVEALED ) { + MiniWindowStuff* stuff; + + makeMiniWindowForTrade( board ); + + stuff = &board->miniWindowStuff[MINIWINDOW_TRADING]; + draw_drawMiniWindow( board->draw, stuff->text, + &stuff->rect, (void**)NULL ); + + board->tradingMiniWindowInvalid = XP_FALSE; + } +} /* drawTradeWindowIf */ + +XP_Bool +board_draw( BoardCtxt* board ) +{ + if ( board->boardBounds.width > 0 ) { + + drawScoreBoard( board ); + + drawTray( board ); + + drawBoard( board ); + } + return !board->needsDrawing; +} /* board_draw */ + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/boardp.h b/xwords4/common/boardp.h new file mode 100644 index 000000000..228b3264b --- /dev/null +++ b/xwords4/common/boardp.h @@ -0,0 +1,300 @@ +/* -*-mode: C; fill-column: 78; -*- */ +/* + * Copyright 1997 - 2007 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _BOARDP_H_ +#define _BOARDP_H_ + +#include "comtypes.h" +#include "model.h" +#include "board.h" +#include "engine.h" +#include "mempool.h" /* debug only */ + +#ifdef CPLUS +extern "C" { +#endif + +typedef struct _DragObjInfo { + BoardObjectType obj; + union { + struct { + XP_U16 col; + XP_U16 row; + } board; + struct { + XP_U16 index; + } tray; + } u; +} DragObjInfo; + +typedef enum { + DT_NONE + ,DT_DIVIDER + ,DT_TILE +#ifdef XWFEATURE_SEARCHLIMIT + ,DT_HINTRGN +#endif + ,DT_BOARD +} DragType; + + +typedef struct _DragState { + DragType dtype; + XP_Bool didMove; /* there was change during the drag; not a + tap */ + XP_Bool scrollTimerSet; + XP_Bool isBlank; /* cache rather than lookup in model */ + Tile tile; /* cache rather than lookup in model */ + DragObjInfo start; + DragObjInfo cur; +} DragState; + +typedef struct _BoardArrow { /* gets flipped along with board */ + XP_U8 col; + XP_U8 row; + XP_Bool vert; + XP_Bool visible; +} BoardArrow; + +#ifdef KEYBOARD_NAV +typedef struct _BdCursorLoc { + XP_U8 col; + XP_U8 row; +} BdCursorLoc; +#endif + +/* We only need two of these, one for the value hint and the other for the + trading window. There's never more than of the former since it lives only + as long as the pen is down. There are, in theory, as many trading windows + as there are (local) players, but they can all use the same window. */ +typedef struct _MiniWindowStuff { + void* closure; + const XP_UCHAR* text; + XP_Rect rect; +} MiniWindowStuff; + +enum { MINIWINDOW_VALHINT, MINIWINDOW_TRADING }; +typedef XP_U16 MiniWindowType; /* one of the two above */ + +typedef struct _PerTurnInfo { +#ifdef KEYBOARD_NAV + XP_Rect scoreRects; + BdCursorLoc bdCursor; +#endif + BoardArrow boardArrow; + XP_U16 scoreDims; + XP_U8 dividerLoc; /* 0 means left of 0th tile, etc. */ + TileBit traySelBits; +#ifdef XWFEATURE_SEARCHLIMIT + BdHintLimits limits; +#endif +#ifdef KEYBOARD_NAV + XP_U8 trayCursorLoc; /* includes divider!!! */ +#endif + XP_Bool dividerSelected; /* probably need to save this */ + XP_Bool tradeInProgress; +#ifdef XWFEATURE_SEARCHLIMIT + XP_Bool hasHintRect; +#endif +} PerTurnInfo; + + +struct BoardCtxt { +/* BoardVTable* vtable; */ + ModelCtxt* model; + ServerCtxt* server; + DrawCtx* draw; + XW_UtilCtxt* util; + + struct CurGameInfo* gi; + + XP_U16 boardHScale; + XP_U16 boardVScale; + XP_U16 yOffset; + XP_U16 lastVisibleRow; + XP_U16 preHideYOffset; + XP_U16 prevYScrollOffset; /* represents where the last draw took place; + used to see if bit scrolling can be used */ + XP_U16 penDownX; + XP_U16 penDownY; + + XP_U32 timerStoppedTime; + XP_U16 timerSaveCount; +#ifdef DEBUG + XP_S16 timerStoppedTurn; +#endif + + XP_U16 redrawFlags[MAX_ROWS]; + + XP_Rect boardBounds; + + BoardObjectType penDownObject; + + XP_Bool needsDrawing; + XP_Bool isFlipped; + XP_Bool showGrid; + XP_Bool gameOver; + XP_Bool leftHanded; + XP_Bool badWordRejected; + XP_Bool timerPending; + XP_Bool disableArrow; + XP_Bool hideValsInTray; + + XP_Bool eraseTray; + XP_Bool boardObscuresTray; + XP_Bool boardHidesTray; + XP_Bool scoreSplitHor;/* how to divide the scoreboard? */ + XP_Bool srcIsPen; /* We're processing a pen event, not a key event */ + + XP_U16 star_row; + + /* Unless KEYBOARD_NAV is defined, this does not change */ + BoardObjectType focussed; + +#ifdef KEYBOARD_NAV + XP_Bool focusHasDived; + XP_Bool hideFocus; /* not saved */ + XP_Bool trayHiddenPreFocus; /* not saved */ + XP_Rect remRect; /* on scoreboard */ +#endif + + /* scoreboard state */ + XP_Rect scoreBdBounds; + XP_Rect timerBounds; + XP_U8 selPlayer; /* which player is selected (!= turn) */ + + PerTurnInfo pti[MAX_NUM_PLAYERS]; + PerTurnInfo* selInfo; + + /* tray state */ + XP_U8 trayScaleH; + XP_U8 trayScaleV; + XP_Rect trayBounds; + XP_U16 remDim; /* width (or ht) of the "rem:" string in scoreboard */ + XP_U8 dividerWidth; /* 0 would mean invisible */ + XP_Bool dividerInvalid; + + XP_Bool scoreBoardInvalid; + DragState dragState; + + MiniWindowStuff miniWindowStuff[2]; + XP_Bool tradingMiniWindowInvalid; + + TileBit trayInvalBits; +#ifdef KEYBOARD_NAV + XP_U8 scoreCursorLoc; +#endif + + XW_TrayVisState trayVisState; + XP_Bool penTimerFired; + XP_Bool showCellValues; + XP_Bool showColors; + + + MPSLOT +}; + +#define CURSOR_LOC_REM 0 + +#define valHintMiniWindowActive( board ) \ + ((XP_Bool)((board)->miniWindowStuff[MINIWINDOW_VALHINT].text != NULL)) +#define MY_TURN(b) ((b)->selPlayer == server_getCurrentTurn( (b)->server )) +#define TRADE_IN_PROGRESS(b) ((b)->selInfo->tradeInProgress==XP_TRUE) + +/* tray-related functions */ +XP_Bool handlePenUpTray( BoardCtxt* board, XP_U16 x, XP_U16 y ); +void drawTray( BoardCtxt* board ); +XP_Bool moveTileToArrowLoc( BoardCtxt* board, XP_U8 index ); +XP_U16 indexForBits( XP_U8 bits ); +XP_Bool rectContainsPt( const XP_Rect* rect1, XP_S16 x, XP_S16 y ); +XP_Bool checkRevealTray( BoardCtxt* board ); +void figureTrayTileRect( BoardCtxt* board, XP_U16 index, XP_Rect* rect ); +XP_Bool rectsIntersect( const XP_Rect* rect1, const XP_Rect* rect2 ); +XP_S16 pointToTileIndex( BoardCtxt* board, XP_U16 x, XP_U16 y, + XP_Bool* onDividerP ); +void board_selectPlayer( BoardCtxt* board, XP_U16 newPlayer ); +void flipIf( const BoardCtxt* board, XP_U16 col, XP_U16 row, + XP_U16* fCol, XP_U16* fRow ); +XP_Bool pointOnSomething( BoardCtxt* board, XP_U16 x, XP_U16 y, + BoardObjectType* wp ); +XP_Bool coordToCell( BoardCtxt* board, XP_S16 xx, XP_S16 yy, XP_U16* colP, + XP_U16* rowP ); +XP_Bool cellOccupied( const BoardCtxt* board, XP_U16 col, XP_U16 row, + XP_Bool inclPending ); +XP_Bool holdsPendingTile( BoardCtxt* board, XP_U16 pencol, XP_U16 penrow ); + +XP_Bool moveTileToBoard( BoardCtxt* board, XP_U16 col, XP_U16 row, + XP_U16 tileIndex, Tile blankFace ); + +void invalTilesUnderRect( BoardCtxt* board, const XP_Rect* rect ); +void invalCellRegion( BoardCtxt* board, XP_U16 colA, XP_U16 rowA, XP_U16 colB, + XP_U16 rowB ); +void invalCell( BoardCtxt* board, XP_U16 col, XP_U16 row ); +void invalDragObj( BoardCtxt* board, const DragObjInfo* di ); +void invalTrayTilesAbove( BoardCtxt* board, XP_U16 tileIndex ); +void invalTrayTilesBetween( BoardCtxt* board, XP_U16 tileIndex1, + XP_U16 tileIndex2 ); +void makeMiniWindowForText( BoardCtxt* board, const XP_UCHAR* text, + MiniWindowType winType ); +XP_Bool getCellRect( const BoardCtxt* board, XP_U16 col, XP_U16 row, + XP_Rect* rect); +void getDragCellRect( BoardCtxt* board, XP_U16 col, XP_U16 row, + XP_Rect* rectP ); +void invalSelTradeWindow( BoardCtxt* board ); +void invalCellsUnderRect( BoardCtxt* board, const XP_Rect* rect ); + +#ifdef XWFEATURE_SEARCHLIMIT +void invalCurHintRect( BoardCtxt* board, XP_U16 player ); +#endif + +void hideMiniWindow( BoardCtxt* board, XP_Bool destroy, + MiniWindowType winType ); + +void moveTileInTray( BoardCtxt* board, XP_U16 moveTo, XP_U16 moveFrom ); +XP_Bool handleTrayDuringTrade( BoardCtxt* board, XP_S16 index ); + +XP_UCHAR* getTileDrawInfo( const BoardCtxt* board, Tile tile, XP_Bool isBlank, + XP_Bitmap* bitmap, XP_S16* value, + XP_UCHAR* buf, XP_U16 len ); +XP_Bool dividerMoved( BoardCtxt* board, XP_U8 newLoc ); + +XP_Bool checkScrollCell( BoardCtxt* board, XP_U16 col, XP_U16 row ); +XP_Bool onBorderCanScroll( const BoardCtxt* board, XP_U16 row, XP_S16* change ); +XP_Bool adjustYOffset( BoardCtxt* board, XP_S16 moveBy ); + + + +#ifdef KEYBOARD_NAV +XP_Bool tray_moveCursor( BoardCtxt* board, XP_Key cursorKey, + XP_Bool preflightOnly, XP_Bool* up ); +void adjustForDivider( const BoardCtxt* board, XP_S16* index ); +XP_Bool tray_keyAction( BoardCtxt* board ); +DrawFocusState dfsFor( BoardCtxt* board, BoardObjectType obj ); +void shiftFocusUp( BoardCtxt* board, XP_Key key ); +void getFocussedTileCenter( BoardCtxt* board, XP_U16* xp, XP_U16* yp ); +void getRectCenter( const XP_Rect* rect, XP_U16* xp, XP_U16* yp ); +#else +# define dfsFor( board, obj ) DFS_NONE +#endif + +#ifdef CPLUS +} +#endif + +#endif diff --git a/xwords4/common/commmgr.h b/xwords4/common/commmgr.h new file mode 100644 index 000000000..7e858aca0 --- /dev/null +++ b/xwords4/common/commmgr.h @@ -0,0 +1,54 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* The Communications manager brokers messages between controllers It + * maps players to devices. + * + * Messages for players go through it. If the player is remote, then the + * message is queued. If local, it's a simple function call executed + * immediately. When the caller is finished, it calls comms_okToSend() + * or somesuch, and all the queued messages are combined into a single + * message for each device represented, and sent. + * + * The problem of duplicate messages: Say there are two players A and B on + * remote device D. A has just made a move {{7,6,'A'},{7,7,'T'}}. The + * server wants to tell each player about A's move. It will want to send the + * same message to every player but A, yet there's no point in sending to B's + * device since the information is already there. + * + * There are three possiblities: put message codes into classes -- e.g. + */ + +#ifndef _COMMMGR_H_ +#define _COMMMGR_H_ + +#include "comtypes.h" /* that's *common* types */ +#include "xwstream.h" +#include "server.h" + +/* typedef struct CommMgrCtxt CommMgrCtxt; */ + +CommMgrCtxt* commmgr_make( XWStreamCtxt* serverStream ); +void commmgr_setServer( CommMgrCtxt* commMgr, ServerCtxt* server ); + +XWStreamCtxt* commmgr_getServerStream( CommMgrCtxt* commmgr ); + +void commmgr_receiveMessage( CommMgrCtxt* commmgr, XWStreamCtxt* incomming ); + +#endif diff --git a/xwords4/common/comms.c b/xwords4/common/comms.c new file mode 100644 index 000000000..cddb1fcf4 --- /dev/null +++ b/xwords4/common/comms.c @@ -0,0 +1,1643 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001-2007 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef USE_STDIO +# include +#endif + +#include "comms.h" + +#include "util.h" +#include "game.h" +#include "xwstream.h" +#include "memstream.h" +#include "xwrelay.h" +#include "strutils.h" + +#define cEND 0x65454e44 +#define HEARTBEAT_NONE 0 + +#ifndef XWFEATURE_STANDALONE_ONLY + +#if defined RELAY_HEARTBEAT && defined COMMS_HEARTBEAT +compilation_error_here( "Choose one or the other or none." ); +#endif + +#ifdef COMMS_HEARTBEAT +/* It might make sense for this to be a parameter or somehow tied to the + platform and transport. But in that case it'd have to be passed across + since all devices must agree. */ +# ifndef HB_INTERVAL +# define HB_INTERVAL 5 +# endif +#endif + +EXTERN_C_START + +typedef struct MsgQueueElem { + struct MsgQueueElem* next; + XP_U8* msg; + XP_U16 len; + XP_PlayerAddr channelNo; + XP_U16 sendCount; /* how many times sent? */ + MsgID msgID; /* saved for ease of deletion */ +} MsgQueueElem; + +typedef struct AddressRecord { + struct AddressRecord* next; + CommsAddrRec addr; +#ifdef DEBUG + XP_U16 lastACK; + XP_U16 nUniqueBytes; +#endif + MsgID nextMsgID; /* on a per-channel basis */ + MsgID lastMsgRcd; /* on a per-channel basis */ + /* only used if COMMS_HEARTBEAT set except for serialization (to_stream) */ + XP_PlayerAddr channelNo; + struct { + XWHostID hostID; /* used for relay case */ + } r; +#ifdef COMMS_HEARTBEAT + XP_Bool initialSeen; +#endif +} AddressRecord; + +#define ADDRESSRECORD_SIZE_68K 20 + +typedef enum { + COMMS_RELAYSTATE_UNCONNECTED + , COMMS_RELAYSTATE_CONNECT_PENDING + , COMMS_RELAYSTATE_CONNECTED + , COMMS_RELAYSTATE_ALLCONNECTED +} CommsRelayState; + +struct CommsCtxt { + XW_UtilCtxt* util; + + XP_U32 connID; /* 0 means ignore; otherwise must match */ + XP_PlayerAddr nextChannelNo; + + AddressRecord* recs; /* return addresses */ + + TransportSend sendproc; +#ifdef COMMS_HEARTBEAT + TransportReset resetproc; + XP_U32 lastMsgRcd; +#endif + void* sendClosure; + + MsgQueueElem* msgQueueHead; + MsgQueueElem* msgQueueTail; + XP_U16 queueLen; + +#ifdef COMMS_HEARTBEAT + XP_Bool doHeartbeat; + XP_Bool hbTimerPending; + XP_U32 lastMsgRcvdTime; +#endif + + /* The following fields, down to isServer, are only used if + XWFEATURE_RELAY is defined, but I'm leaving them in here so apps built + both ways can open each other's saved games files.*/ + CommsAddrRec addr; + + /* Stuff for relays */ + struct { + XWHostID myHostID; /* 0 if unset, 1 if acting as server, + random for client */ + CommsRelayState relayState; /* not saved: starts at UNCONNECTED */ + CookieID cookieID; /* not saved; temp standin for cookie; set + by relay */ + /* permanent globally unique name, set by relay and forever after + associated with this game. Used to reconnect. */ + XP_UCHAR connName[MAX_CONNNAME_LEN+1]; + + /* heartbeat: for periodic pings if relay thinks the network the + device is on requires them. Not saved since only valid when + connected, and we reconnect for every game and after restarting. */ + XP_U16 heartbeat; + XP_U16 nPlayersHere; + XP_U16 nPlayersTotal; + XP_Bool connecting; + } r; + + XP_Bool isServer; +#ifdef DEBUG + XP_U16 nUniqueBytes; +#endif + MPSLOT +}; + +#if defined XWFEATURE_BLUETOOTH || defined XWFEATURE_IP_DIRECT +typedef enum { + BTIPMSG_NONE = 0 + ,BTIPMSG_DATA + ,BTIPMSG_RESET + ,BTIPMSG_HB +} BTIPMsgType; +#endif + +/**************************************************************************** + * prototypes + ****************************************************************************/ +static AddressRecord* rememberChannelAddress( CommsCtxt* comms, + XP_PlayerAddr channelNo, + XWHostID id, + const CommsAddrRec* addr ); +static void updateChannelAddress( AddressRecord* rec, const CommsAddrRec* addr ); +static XP_Bool channelToAddress( CommsCtxt* comms, XP_PlayerAddr channelNo, + const CommsAddrRec** addr ); +static AddressRecord* getRecordFor( CommsCtxt* comms, const CommsAddrRec* addr, + XP_PlayerAddr channelNo ); +static XP_S16 sendMsg( CommsCtxt* comms, MsgQueueElem* elem ); +static void addToQueue( CommsCtxt* comms, MsgQueueElem* newMsgElem ); +static XP_U16 countAddrRecs( const CommsCtxt* comms ); +static void sendConnect( CommsCtxt* comms ); + +#ifdef XWFEATURE_RELAY +static void relayConnect( CommsCtxt* comms ); +static void relayDisconnect( CommsCtxt* comms ); +static XP_Bool send_via_relay( CommsCtxt* comms, XWRELAY_Cmd cmd, + XWHostID destID, void* data, int dlen ); +static XWHostID getDestID( CommsCtxt* comms, XP_PlayerAddr channelNo ); +#endif +#if defined RELAY_HEARTBEAT || defined COMMS_HEARTBEAT +static void setHeartbeatTimer( CommsCtxt* comms ); +#else +# define setHeartbeatTimer( comms ) +#endif +#if defined XWFEATURE_BLUETOOTH || defined XWFEATURE_IP_DIRECT +static XP_S16 send_via_bt_or_ip( CommsCtxt* comms, BTIPMsgType typ, + XP_PlayerAddr channelNo, + void* data, int dlen ); +#endif + +/**************************************************************************** + * implementation + ****************************************************************************/ +CommsCtxt* +comms_make( MPFORMAL XW_UtilCtxt* util, XP_Bool isServer, + XP_U16 XP_UNUSED_RELAY(nPlayersHere), + XP_U16 XP_UNUSED_RELAY(nPlayersTotal), + TransportSend sendproc, IF_CH(TransportReset resetproc) + void* closure ) +{ + CommsCtxt* result = (CommsCtxt*)XP_MALLOC( mpool, sizeof(*result) ); + XP_MEMSET( result, 0, sizeof(*result) ); + + MPASSIGN(result->mpool, mpool); + + result->isServer = isServer; + result->sendproc = sendproc; +#ifdef COMMS_HEARTBEAT + result->resetproc = resetproc; +#endif + result->sendClosure = closure; + result->util = util; + +#ifdef XWFEATURE_RELAY + result->r.myHostID = isServer? HOST_ID_SERVER: HOST_ID_NONE; + XP_LOGF( "set myHostID to %d", result->r.myHostID ); + + result->r.relayState = COMMS_RELAYSTATE_UNCONNECTED; + result->r.nPlayersHere = nPlayersHere; + result->r.nPlayersTotal = nPlayersTotal; +#endif + return result; +} /* comms_make */ + +static void +cleanupInternal( CommsCtxt* comms ) +{ + MsgQueueElem* msg; + MsgQueueElem* next; + + for ( msg = comms->msgQueueHead; !!msg; msg = next ) { + next = msg->next; + XP_FREE( comms->mpool, msg->msg ); + XP_FREE( comms->mpool, msg ); + } + comms->queueLen = 0; + comms->msgQueueHead = comms->msgQueueTail = (MsgQueueElem*)NULL; +} /* cleanupInternal */ + +static void +cleanupAddrRecs( CommsCtxt* comms ) +{ + AddressRecord* recs; + AddressRecord* next; + + for ( recs = comms->recs; !!recs; recs = next ) { + next = recs->next; + XP_FREE( comms->mpool, recs ); + } + comms->recs = (AddressRecord*)NULL; +} /* cleanupAddrRecs */ + +void +comms_reset( CommsCtxt* comms, XP_Bool isServer, + XP_U16 XP_UNUSED_RELAY(nPlayersHere), + XP_U16 XP_UNUSED_RELAY(nPlayersTotal) ) +{ + LOG_FUNC(); +#ifdef XWFEATURE_RELAY + relayDisconnect( comms ); +#endif + + cleanupInternal( comms ); + comms->isServer = isServer; + + cleanupAddrRecs( comms ); + + comms->nextChannelNo = 0; + + comms->connID = CONN_ID_NONE; +#ifdef XWFEATURE_RELAY + comms->r.cookieID = COOKIE_ID_NONE; + comms->r.nPlayersHere = nPlayersHere; + comms->r.nPlayersTotal = nPlayersTotal; + relayConnect( comms ); +#endif + LOG_RETURN_VOID(); +} /* comms_reset */ + +void +comms_destroy( CommsCtxt* comms ) +{ + CommsAddrRec aNew; + aNew.conType = COMMS_CONN_NONE; + util_addrChange( comms->util, &comms->addr, &aNew ); + + cleanupInternal( comms ); + cleanupAddrRecs( comms ); + + XP_FREE( comms->mpool, comms ); +} /* comms_destroy */ + +void +comms_setConnID( CommsCtxt* comms, XP_U32 connID ) +{ + comms->connID = connID; + XP_STATUSF( "%s: set connID to %lx", __func__, connID ); +} /* comms_setConnID */ + +static void +addrFromStream( CommsAddrRec* addrP, XWStreamCtxt* stream ) +{ + CommsAddrRec addr; + + addr.conType = stream_getU8( stream ); + + switch( addr.conType ) { + case COMMS_CONN_NONE: + break; + case COMMS_CONN_BT: + stringFromStreamHere( stream, addr.u.bt.hostName, + sizeof(addr.u.bt.hostName) ); + stream_getBytes( stream, &addr.u.bt.btAddr.bits, + sizeof(addr.u.bt.btAddr.bits) ); + break; + case COMMS_CONN_IR: + /* nothing to save */ + break; + case COMMS_CONN_IP_DIRECT: + stringFromStreamHere( stream, addr.u.ip.hostName_ip, + sizeof(addr.u.ip.hostName_ip) ); + addr.u.ip.ipAddr_ip = stream_getU32( stream ); + addr.u.ip.port_ip = stream_getU16( stream ); + break; + case COMMS_CONN_RELAY: + stringFromStreamHere( stream, addr.u.ip_relay.cookie, + sizeof(addr.u.ip_relay.cookie) ); + stringFromStreamHere( stream, addr.u.ip_relay.hostName, + sizeof(addr.u.ip_relay.hostName) ); + addr.u.ip_relay.ipAddr = stream_getU32( stream ); + addr.u.ip_relay.port = stream_getU16( stream ); + break; + default: + /* shut up, compiler */ + break; + } + + XP_MEMCPY( addrP, &addr, sizeof(*addrP) ); +} /* addrFromStream */ + +CommsCtxt* +comms_makeFromStream( MPFORMAL XWStreamCtxt* stream, XW_UtilCtxt* util, + TransportSend sendproc, + IF_CH(TransportReset resetproc ) void* closure ) +{ + CommsCtxt* comms; + XP_Bool isServer; + XP_U16 nAddrRecs, nPlayersHere, nPlayersTotal; + AddressRecord** prevsAddrNext; + MsgQueueElem** prevsQueueNext; + XP_U16 version = stream_getVersion( stream ); + CommsAddrRec addr; + short i; + + isServer = stream_getU8( stream ); + if ( version < STREAM_VERS_RELAY ) { + XP_MEMSET( &addr, 0, sizeof(addr) ); + addr.conType = COMMS_CONN_IR; /* all there was back then */ + } else { + addrFromStream( &addr, stream ); + } + + if ( addr.conType == COMMS_CONN_RELAY ) { + nPlayersHere = (XP_U16)stream_getBits( stream, 4 ); + nPlayersTotal = (XP_U16)stream_getBits( stream, 4 ); + } else { + nPlayersHere = 0; + nPlayersTotal = 0; + } + comms = comms_make( MPPARM(mpool) util, isServer, + nPlayersHere, nPlayersTotal, + sendproc, IF_CH(resetproc) closure ); + XP_MEMCPY( &comms->addr, &addr, sizeof(comms->addr) ); + + comms->connID = stream_getU32( stream ); + comms->nextChannelNo = stream_getU16( stream ); + if ( addr.conType == COMMS_CONN_RELAY ) { + comms->r.myHostID = stream_getU8( stream ); + stringFromStreamHere( stream, comms->r.connName, + sizeof(comms->r.connName) ); + } + +#ifdef DEBUG + comms->nUniqueBytes = stream_getU16( stream ); +#endif + comms->queueLen = stream_getU8( stream ); + + nAddrRecs = stream_getU8( stream ); + prevsAddrNext = &comms->recs; + for ( i = 0; i < nAddrRecs; ++i ) { + AddressRecord* rec = (AddressRecord*)XP_MALLOC( mpool, sizeof(*rec)); + XP_MEMSET( rec, 0, sizeof(*rec) ); + + addrFromStream( &rec->addr, stream ); + + rec->nextMsgID = stream_getU16( stream ); + rec->lastMsgRcd = stream_getU16( stream ); + rec->channelNo = stream_getU16( stream ); + if ( rec->addr.conType == COMMS_CONN_RELAY ) { + rec->r.hostID = stream_getU8( stream ); + } + +#ifdef DEBUG + rec->lastACK = stream_getU16( stream ); + rec->nUniqueBytes = stream_getU16( stream ); +#endif + + *prevsAddrNext = rec; + prevsAddrNext = &rec->next; + } + + prevsQueueNext = &comms->msgQueueHead; + for ( i = 0; i < comms->queueLen; ++i ) { + MsgQueueElem* msg = (MsgQueueElem*)XP_MALLOC( mpool, sizeof(*msg) ); + + msg->channelNo = stream_getU16( stream ); + msg->msgID = stream_getU32( stream ); +#ifdef COMMS_HEARTBEAT + msg->sendCount = 0; +#endif + msg->len = stream_getU16( stream ); + msg->msg = (XP_U8*)XP_MALLOC( mpool, msg->len ); + stream_getBytes( stream, msg->msg, msg->len ); + + msg->next = (MsgQueueElem*)NULL; + *prevsQueueNext = comms->msgQueueTail = msg; + comms->msgQueueTail = msg; + prevsQueueNext = &msg->next; + } + +#ifdef DEBUG + XP_ASSERT( stream_getU32( stream ) == cEND ); +#endif + + return comms; +} /* comms_makeFromStream */ + +void +comms_start( CommsCtxt* comms ) +{ +#ifdef COMMS_HEARTBEAT + comms->doHeartbeat = comms->addr.conType != COMMS_CONN_IR; +#endif + + sendConnect( comms ); +} /* comms_start */ + +static void +sendConnect( CommsCtxt* comms ) +{ + switch( comms->addr.conType ) { +#ifdef XWFEATURE_RELAY + case COMMS_CONN_RELAY: + comms->r.relayState = COMMS_RELAYSTATE_UNCONNECTED; + relayConnect( comms ); + break; +#endif +#if defined XWFEATURE_BLUETOOTH || defined XWFEATURE_IP_DIRECT + case COMMS_CONN_BT: + case COMMS_CONN_IP_DIRECT: + /* This will only work on host side when there's a single guest! */ + (void)send_via_bt_or_ip( comms, BTIPMSG_RESET, CHANNEL_NONE, NULL, 0 ); + (void)comms_resendAll( comms ); + break; +#endif + default: + break; + } + + setHeartbeatTimer( comms ); +} /* comms_start */ + +static void +addrToStream( XWStreamCtxt* stream, const CommsAddrRec* addrP ) +{ + CommsAddrRec addr; + XP_MEMCPY( &addr, addrP, sizeof(addr) ); + + stream_putU8( stream, addr.conType ); + + switch( addr.conType ) { + case COMMS_CONN_NONE: + /* nothing to write */ + break; + case COMMS_CONN_BT: + stringToStream( stream, addr.u.bt.hostName ); + /* sizeof(.bits) below defeats ARM's padding. */ + stream_putBytes( stream, &addr.u.bt.btAddr.bits, + sizeof(addr.u.bt.btAddr.bits) ); + break; + case COMMS_CONN_IR: + /* nothing to save */ + break; + case COMMS_CONN_IP_DIRECT: + stringToStream( stream, addr.u.ip.hostName_ip ); + stream_putU32( stream, addr.u.ip.ipAddr_ip ); + stream_putU16( stream, addr.u.ip.port_ip ); + break; + case COMMS_CONN_RELAY: + stringToStream( stream, addr.u.ip_relay.cookie ); + stringToStream( stream, addr.u.ip_relay.hostName ); + stream_putU32( stream, addr.u.ip_relay.ipAddr ); + stream_putU16( stream, addr.u.ip_relay.port ); + break; + } +} /* addrToStream */ + +void +comms_writeToStream( const CommsCtxt* comms, XWStreamCtxt* stream ) +{ + XP_U16 nAddrRecs; + AddressRecord* rec; + MsgQueueElem* msg; + + stream_putU8( stream, (XP_U8)comms->isServer ); + addrToStream( stream, &comms->addr ); + if ( comms->addr.conType == COMMS_CONN_RELAY ) { + stream_putBits( stream, 4, comms->r.nPlayersHere ); + stream_putBits( stream, 4, comms->r.nPlayersTotal ); + } + + stream_putU32( stream, comms->connID ); + stream_putU16( stream, comms->nextChannelNo ); + if ( comms->addr.conType == COMMS_CONN_RELAY ) { + stream_putU8( stream, comms->r.myHostID ); + stringToStream( stream, comms->r.connName ); + } + +#ifdef DEBUG + stream_putU16( stream, comms->nUniqueBytes ); +#endif + + XP_ASSERT( comms->queueLen <= 255 ); + stream_putU8( stream, (XP_U8)comms->queueLen ); + + nAddrRecs = countAddrRecs(comms); + stream_putU8( stream, (XP_U8)nAddrRecs ); + + for ( rec = comms->recs; !!rec; rec = rec->next ) { + + CommsAddrRec* addr = &rec->addr; + addrToStream( stream, addr ); + + stream_putU16( stream, (XP_U16)rec->nextMsgID ); + stream_putU16( stream, (XP_U16)rec->lastMsgRcd ); + stream_putU16( stream, rec->channelNo ); + if ( rec->addr.conType == COMMS_CONN_RELAY ) { + stream_putU8( stream, rec->r.hostID ); /* unneeded unless RELAY */ + } +#ifdef DEBUG + stream_putU16( stream, rec->lastACK ); + stream_putU16( stream, rec->nUniqueBytes ); +#endif + } + + for ( msg = comms->msgQueueHead; !!msg; msg = msg->next ) { + stream_putU16( stream, msg->channelNo ); + stream_putU32( stream, msg->msgID ); + + stream_putU16( stream, msg->len ); + stream_putBytes( stream, msg->msg, msg->len ); + } + +#ifdef DEBUG + stream_putU32( stream, cEND ); +#endif +} /* comms_writeToStream */ + +void +comms_getAddr( const CommsCtxt* comms, CommsAddrRec* addr ) +{ + XP_ASSERT( !!comms ); + XP_MEMCPY( addr, &comms->addr, sizeof(*addr) ); +} /* comms_getAddr */ + +void +comms_setAddr( CommsCtxt* comms, const CommsAddrRec* addr ) +{ + XP_ASSERT( comms != NULL ); +#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH || defined XWFEATURE_IP_DIRECT + util_addrChange( comms->util, &comms->addr, addr ); +#endif + XP_MEMCPY( &comms->addr, addr, sizeof(comms->addr) ); + +#ifdef COMMS_HEARTBEAT + comms->doHeartbeat = comms->addr.conType != COMMS_CONN_IR; +#endif + sendConnect( comms ); + +} /* comms_setAddr */ + +void +comms_getInitialAddr( CommsAddrRec* addr ) +{ +#if defined XWFEATURE_RELAY + addr->conType = COMMS_CONN_RELAY; /* for temporary ease in debugging */ + addr->u.ip_relay.ipAddr = 0L; /* force 'em to set it */ + addr->u.ip_relay.port = 10999; + { + char* name = "eehouse.org"; + XP_MEMCPY( addr->u.ip_relay.hostName, name, XP_STRLEN(name)+1 ); + } + addr->u.ip_relay.cookie[0] = '\0'; +#elif defined PLATFORM_PALM + /* default values; default is still IR where there's a choice, at least on + Palm... */ + addr->conType = COMMS_CONN_IR; +#else + addr->conType = COMMS_CONN_BT; +#endif +} /* comms_getInitialAddr */ + +XP_Bool +comms_checkAddr( DeviceRole role, const CommsAddrRec* addr, XW_UtilCtxt* util ) +{ + XP_Bool ok = XP_TRUE; + /* make sure the user's given us enough information to make a connection */ + if ( role == SERVER_ISCLIENT ) { + if ( addr->conType == COMMS_CONN_BT ) { + XP_U32 empty = 0L; /* check four bytes to save some code */ + if ( !XP_MEMCMP( &empty, &addr->u.bt.btAddr, sizeof(empty) ) ) { + ok = XP_FALSE; + if ( !!util ) { + util_userError( util, STR_NEED_BT_HOST_ADDR ); + } + } + } + } + return ok; +} /* comms_checkAddr */ + +CommsConnType +comms_getConType( const CommsCtxt* comms ) +{ + XP_ASSERT( !!comms ); /* or: return COMMS_CONN_NONE */ + return comms->addr.conType; +} /* comms_getConType */ + +XP_Bool +comms_getIsServer( const CommsCtxt* comms ) +{ + XP_ASSERT( !!comms ); + return comms->isServer; +} + +static MsgQueueElem* +makeElemWithID( CommsCtxt* comms, MsgID msgID, AddressRecord* rec, + XP_PlayerAddr channelNo, XWStreamCtxt* stream ) +{ + XP_U16 headerLen; + XP_U16 streamSize = NULL == stream? 0 : stream_getSize( stream ); + MsgID lastMsgRcd = (!!rec)? rec->lastMsgRcd : 0; + MsgQueueElem* newMsgElem; + XWStreamCtxt* msgStream; + +#ifdef DEBUG + if ( !!rec ) { + rec->nUniqueBytes += streamSize; + } else { + comms->nUniqueBytes += streamSize; + } +#endif + + newMsgElem = (MsgQueueElem*)XP_MALLOC( comms->mpool, + sizeof( *newMsgElem ) ); + newMsgElem->channelNo = channelNo; + newMsgElem->msgID = msgID; +#ifdef COMMS_HEARTBEAT + newMsgElem->sendCount = 0; +#endif + + msgStream = mem_stream_make( MPPARM(comms->mpool) + util_getVTManager(comms->util), + NULL, 0, + (MemStreamCloseCallback)NULL ); + stream_open( msgStream ); + XP_LOGF( "%s: putting connID %ld", __func__, comms->connID ); + stream_putU32( msgStream, comms->connID ); + + stream_putU16( msgStream, channelNo ); + stream_putU32( msgStream, msgID ); + XP_LOGF( "put lastMsgRcd: %ld", lastMsgRcd ); + stream_putU32( msgStream, lastMsgRcd ); + + headerLen = stream_getSize( msgStream ); + newMsgElem->len = streamSize + headerLen; + newMsgElem->msg = (XP_U8*)XP_MALLOC( comms->mpool, newMsgElem->len ); + + stream_getBytes( msgStream, newMsgElem->msg, headerLen ); + stream_destroy( msgStream ); + + if ( 0 < streamSize ) { + stream_getBytes( stream, newMsgElem->msg + headerLen, streamSize ); + } + + return newMsgElem; +} /* makeElemWithID */ + +/* Send a message using the sequentially next MsgID. Save the message so + * resend can work. */ +XP_S16 +comms_send( CommsCtxt* comms, XWStreamCtxt* stream ) +{ + XP_PlayerAddr channelNo = stream_getAddress( stream ); + AddressRecord* rec = getRecordFor( comms, NULL, channelNo ); + MsgID msgID = (!!rec)? ++rec->nextMsgID : 0; + MsgQueueElem* elem; + XP_S16 result = -1; + + + XP_DEBUGF( "%s: assigning msgID=" XP_LD " on chnl %d", __func__, + msgID, channelNo ); + + elem = makeElemWithID( comms, msgID, rec, channelNo, stream ); + if ( NULL != elem ) { + addToQueue( comms, elem ); + result = sendMsg( comms, elem ); + } + return result; +} /* comms_send */ + +/* Add new message to the end of the list. The list needs to be kept in order + * by ascending msgIDs within each channel since if there's a resend that's + * the order in which they need to be sent. + */ +static void +addToQueue( CommsCtxt* comms, MsgQueueElem* newMsgElem ) +{ + newMsgElem->next = (MsgQueueElem*)NULL; + if ( !comms->msgQueueHead ) { + comms->msgQueueHead = comms->msgQueueTail = newMsgElem; + XP_ASSERT( comms->queueLen == 0 ); + comms->queueLen = 1; + } else { + XP_ASSERT( !!comms->msgQueueTail ); + comms->msgQueueTail->next = newMsgElem; + comms->msgQueueTail = newMsgElem; + + XP_ASSERT( comms->queueLen > 0 ); + ++comms->queueLen; + } + XP_STATUSF( "addToQueue: queueLen now %d", comms->queueLen ); +} /* addToQueue */ + +#ifdef DEBUG +static void +printQueue( CommsCtxt* comms ) +{ + MsgQueueElem* elem; + short i; + + for ( elem = comms->msgQueueHead, i = 0; i < comms->queueLen; + elem = elem->next, ++i ) { + XP_STATUSF( "\t%d: channel: %d; msgID=" XP_LD, + i+1, elem->channelNo, elem->msgID ); + } +} +#endif + +static void +freeElem( const CommsCtxt* comms, MsgQueueElem* elem ) +{ + XP_FREE( comms->mpool, elem->msg ); + XP_FREE( comms->mpool, elem ); +} + +/* We've received on some channel a message with a certain ID. This means + * that all messages sent on that channel with lower IDs have been received + * and can be removed from our queue. BUT: if this ID is higher than any + * we've sent, don't remove. We may be starting a new game but have a server + * that's still on the old one. + */ +static void +removeFromQueue( CommsCtxt* comms, XP_PlayerAddr channelNo, MsgID msgID ) +{ + XP_STATUSF( "%s: remove msgs <= " XP_LD " for channel %d (queueLen: %d)", + __func__, msgID, channelNo, comms->queueLen ); + + if ( (channelNo == 0) || !!getRecordFor(comms, NULL, channelNo) ) { + MsgQueueElem dummy; + MsgQueueElem* keep = &dummy; + MsgQueueElem* elem; + MsgQueueElem* next; + + for ( elem = comms->msgQueueHead; !!elem; elem = next ) { + XP_Bool knownGood = XP_FALSE; + next = elem->next; + + /* remove the 0-channel message if we've established a channel + number. Only clients should have any 0-channel messages in the + queue, and receiving something from the server is an implicit + ACK -- IFF it isn't left over from the last game. */ + + if ( (elem->channelNo == 0) && (channelNo != 0) ) { + XP_ASSERT( !comms->isServer ); + XP_ASSERT( elem->msgID == 0 ); + } else if ( elem->channelNo != channelNo ) { + knownGood = XP_TRUE; + } + + if ( !knownGood && (elem->msgID <= msgID) ) { + freeElem( comms, elem ); + --comms->queueLen; + } else { + keep->next = elem; + keep = elem; + } + } + + keep->next = NULL; + comms->msgQueueHead = dummy.next; + } + + XP_STATUSF( "%s: queueLen now %d", __func__, comms->queueLen ); + + XP_ASSERT( comms->queueLen > 0 || comms->msgQueueHead == NULL ); + +#ifdef DEBUG + printQueue( comms ); +#endif +} /* removeFromQueue */ + +static XP_S16 +sendMsg( CommsCtxt* comms, MsgQueueElem* elem ) +{ + XP_S16 result = -1; + XP_PlayerAddr channelNo; +#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH + CommsConnType conType = comms_getConType( comms ); +#endif + + channelNo = elem->channelNo; + + if ( 0 ) { +#ifdef XWFEATURE_RELAY + } else if ( conType == COMMS_CONN_RELAY ) { + if ( comms->r.relayState == COMMS_RELAYSTATE_ALLCONNECTED ) { + XWHostID destID = getDestID( comms, channelNo ); + result = send_via_relay( comms, XWRELAY_MSG_TORELAY, destID, + elem->msg, elem->len ); + } else { + XP_LOGF( "%s: skipping message: not connected", __func__ ); + } +#endif +#if defined XWFEATURE_BLUETOOTH || defined XWFEATURE_IP_DIRECT + } else if ( conType == COMMS_CONN_BT || conType == COMMS_CONN_IP_DIRECT ) { + result = send_via_bt_or_ip( comms, BTIPMSG_DATA, channelNo, + elem->msg, elem->len ); +#ifdef COMMS_HEARTBEAT + setHeartbeatTimer( comms ); +#endif +#endif + } else { + const CommsAddrRec* addr; + (void)channelToAddress( comms, channelNo, &addr ); + + XP_ASSERT( !!comms->sendproc ); + result = (*comms->sendproc)( elem->msg, elem->len, addr, + comms->sendClosure ); + } + + if ( result == elem->len ) { + ++elem->sendCount; + XP_LOGF( "sendCount now %d", elem->sendCount ); + } + + return result; +} /* sendMsg */ + +XP_S16 +comms_resendAll( CommsCtxt* comms ) +{ + MsgQueueElem* msg; + XP_S16 result = 0; + + XP_ASSERT( !!comms ); + + for ( msg = comms->msgQueueHead; !!msg; msg = msg->next ) { + XP_S16 oneResult = sendMsg( comms, msg ); + if ( result == 0 && oneResult != 0 ) { + result = oneResult; + } + XP_STATUSF( "resend: msgID=" XP_LD "; rslt=%d", + msg->msgID, oneResult ); + } + + return result; +} /* comms_resend */ + +#ifdef XWFEATURE_RELAY +static XP_Bool +relayPreProcess( CommsCtxt* comms, XWStreamCtxt* stream, XWHostID* senderID ) +{ + XP_Bool consumed = XP_TRUE; + XWHostID destID, srcID; + CookieID cookieID; + XP_U8 relayErr; + XP_U8 hasName; + + /* nothing for us to do here if not using relay */ + XWRELAY_Cmd cmd = stream_getU8( stream ); + switch( cmd ) { + + case XWRELAY_CONNECT_RESP: + case XWRELAY_RECONNECT_RESP: + comms->r.relayState = COMMS_RELAYSTATE_CONNECTED; + comms->r.heartbeat = stream_getU16( stream ); + comms->r.cookieID = stream_getU16( stream ); + comms->r.myHostID = (XWHostID)stream_getU8( stream ); + XP_LOGF( "got XWRELAY_CONNECTRESP; set cookieID = %d; " + "set hostid: %x", + comms->r.cookieID, comms->r.myHostID ); + setHeartbeatTimer( comms ); + break; + + case XWRELAY_ALLHERE: + comms->r.relayState = COMMS_RELAYSTATE_ALLCONNECTED; + hasName = stream_getU8( stream ); + if ( hasName ) { + stringFromStreamHere( stream, comms->r.connName, + sizeof(comms->r.connName) ); + XP_LOGF( "read connName: %s", comms->r.connName ); + } else { + XP_ASSERT( comms->r.connName[0] != '\0' ); + } + + /* We're [re-]connected now. Send any pending messages. This may + need to be done later since we're inside the platform's socket + read proc now. */ + comms_resendAll( comms ); + break; + case XWRELAY_MSG_FROMRELAY: + cookieID = stream_getU16( stream ); + srcID = stream_getU8( stream ); + destID = stream_getU8( stream ); + XP_LOGF( "cookieID: %d; srcID: %x; destID: %x", + cookieID, srcID, destID ); + /* If these values don't check out, drop it */ + consumed = cookieID != comms->r.cookieID + || destID != comms->r.myHostID; + if ( consumed ) { + XP_LOGF( "rejecting data message" ); + } else { + *senderID = srcID; + } + break; + + case XWRELAY_DISCONNECT_OTHER: + relayErr = stream_getU8( stream ); + srcID = stream_getU8( stream ); + XP_LOGF( "host id %x disconnected", srcID ); + /* we will eventually want to tell the user which player's gone */ + util_userError( comms->util, ERR_RELAY_BASE + relayErr ); + break; + + case XWRELAY_DISCONNECT_YOU: /* Close socket for this? */ + case XWRELAY_CONNECTDENIED: /* Close socket for this? */ + XP_LOGF( "XWRELAY_DISCONNECT_YOU|XWRELAY_CONNECTDENIED" ); + relayErr = stream_getU8( stream ); + util_userError( comms->util, ERR_RELAY_BASE + relayErr ); + comms->r.relayState = COMMS_RELAYSTATE_UNCONNECTED; + /* fallthru */ + default: + XP_LOGF( "dropping relay msg with cmd %d", (XP_U16)cmd ); + } + + return consumed; +} /* relayPreProcess */ +#endif + +#ifdef COMMS_HEARTBEAT +static void +noteHBReceived( CommsCtxt* comms/* , const CommsAddrRec* addr */ ) +{ + comms->lastMsgRcvdTime = util_getCurSeconds( comms->util ); + setHeartbeatTimer( comms ); +} +#else +# define noteHBReceived(a) +#endif + +#if defined XWFEATURE_BLUETOOTH || defined XWFEATURE_IP_DIRECT +static XP_Bool +btIpPreProcess( CommsCtxt* comms, XWStreamCtxt* stream ) +{ + BTIPMsgType typ = (BTIPMsgType)stream_getU8( stream ); + XP_Bool consumed = typ != BTIPMSG_DATA; + + if ( consumed ) { + /* This is all there is so far */ + if ( typ == BTIPMSG_RESET ) { + (void)comms_resendAll( comms ); + } else if ( typ == BTIPMSG_HB ) { +/* noteHBReceived( comms, addr ); */ + } else { + XP_ASSERT( 0 ); + } + } + + return consumed; +} /* btIpPreProcess */ +#endif + +static XP_Bool +preProcess( CommsCtxt* comms, XWStreamCtxt* stream, + XP_Bool* XP_UNUSED_RELAY(usingRelay), XWHostID* XP_UNUSED_RELAY(senderID) ) +{ + XP_Bool consumed = XP_FALSE; + switch ( comms->addr.conType ) { +#ifdef XWFEATURE_RELAY + /* relayPreProcess returns true if consumes the message. May just eat the + header and leave a regular message to be processed below. */ + case COMMS_CONN_RELAY: + consumed = relayPreProcess( comms, stream, senderID ); + if ( !consumed ) { + *usingRelay = comms->addr.conType == COMMS_CONN_RELAY; + } + break; +#endif +#if defined XWFEATURE_BLUETOOTH || defined XWFEATURE_IP_DIRECT + case COMMS_CONN_BT: + case COMMS_CONN_IP_DIRECT: + consumed = btIpPreProcess( comms, stream ); + break; +#endif + default: + break; + } + return consumed; +} /* preProcess */ + +static AddressRecord* +getRecordFor( CommsCtxt* comms, const CommsAddrRec* addr, + XP_PlayerAddr channelNo ) +{ + CommsConnType conType; + AddressRecord* rec; + XP_Bool matched = XP_FALSE; + + /* Use addr if we have it. Otherwise use channelNo if non-0 */ + conType = !!addr? addr->conType : COMMS_CONN_NONE; + + for ( rec = comms->recs; !!rec; rec = rec->next ) { + XP_ASSERT( !addr || (conType == rec->addr.conType) ); + switch( conType ) { + case COMMS_CONN_RELAY: + if ( (addr->u.ip_relay.ipAddr == rec->addr.u.ip_relay.ipAddr) + && (addr->u.ip_relay.port == rec->addr.u.ip_relay.port ) ) { + matched = XP_TRUE; + } + break; + case COMMS_CONN_BT: + if ( 0 == XP_MEMCMP( &addr->u.bt.btAddr, &rec->addr.u.bt.btAddr, + sizeof(addr->u.bt.btAddr) ) ) { + matched = XP_TRUE; + } + break; + case COMMS_CONN_IP_DIRECT: + if ( (addr->u.ip.ipAddr_ip == rec->addr.u.ip.ipAddr_ip) + && (addr->u.ip.port_ip == rec->addr.u.ip.port_ip) ) { + matched = XP_TRUE; + } + break; + case COMMS_CONN_IR: /* no way to test */ + break; + case COMMS_CONN_NONE: + matched = channelNo == rec->channelNo; + break; + default: + XP_ASSERT(0); + break; + } + if ( matched ) { + break; + } + } + return rec; +} /* addrToRecord */ + +/* An initial message comes only from a client to a server, and from the + * server in response to that initial message. Once the inital messages are + * exchanged there's a connID associated. The greatest danger is that it's a + * dup, resent for whatever reason. To detect that we check that the address + * is unknown. But addresses can change, e.g. if a reset of a socket-based + * transport causes the local socket to change. How to deal with this? + * Likely a boolean set when we call comms->resetproc that causes us to accept + * changed addresses. + * + * But: before we're connected heartbeats will also come here, but with + * hasPayload false. We want to remember their address, but not give them a + * channel ID. So if we have a payload we insist that it's the first we've + * seen on this channel. + * + * If it's a HB, then we want to add a rec/channel if there's none, but mark + * it invalid + */ +static AddressRecord* +validateInitialMessage( CommsCtxt* comms, XP_Bool hasPayload, + const CommsAddrRec* addr, XWHostID senderID, + XP_PlayerAddr* channelNo ) +{ +#ifdef COMMS_HEARTBEAT + XP_Bool addRec = XP_FALSE; + AddressRecord* rec = getRecordFor( comms, addr, *channelNo ); + LOG_FUNC(); + + if ( hasPayload ) { + if ( rec ) { + if ( rec->initialSeen ) { + rec = NULL; /* reject it! */ + } + } else { + addRec = XP_TRUE; + } + } else { + /* This is a heartbeat */ + if ( !rec && comms->isServer ) { + addRec = XP_TRUE; + } + } + + if ( addRec ) { + if ( comms->isServer ) { + XP_ASSERT( *channelNo == 0 ); + *channelNo = ++comms->nextChannelNo; + } + rec = rememberChannelAddress( comms, *channelNo, senderID, addr ); + if ( hasPayload ) { + rec->initialSeen = XP_TRUE; + } else { + rec = NULL; + } + } + LOG_RETURNF( XP_P, rec ); + return rec; +#else + AddressRecord* rec = getRecordFor( comms, addr, *channelNo ); + if ( !!rec ) { + rec = NULL; /* reject: we've already seen init message on channel */ + } else { + if ( comms->isServer ) { + XP_ASSERT( *channelNo == 0 ); + *channelNo = ++comms->nextChannelNo; + } + rec = rememberChannelAddress( comms, *channelNo, senderID, addr ); + } + return rec; +#endif +} /* validateInitialMessage */ + +/* Messages with established connIDs are valid only if they have the msgID + * that's expected on that channel. Their addresses need to match what we + * have for that channel, and in fact we'll overwrite what we have in case a + * reset has changed the address. The danger is that somebody might sneak in + * with a forged message, but this isn't internet banking. + */ +static AddressRecord* +validateChannelMessage( CommsCtxt* comms, const CommsAddrRec* addr, + XP_PlayerAddr channelNo, MsgID msgID, MsgID lastMsgRcd ) + +{ + AddressRecord* rec; + LOG_FUNC(); + + rec = getRecordFor( comms, NULL, channelNo ); + if ( !!rec ) { + removeFromQueue( comms, channelNo, lastMsgRcd ); + if ( msgID == rec->lastMsgRcd + 1 ) { + updateChannelAddress( rec, addr ); +#ifdef DEBUG + rec->lastACK = (XP_U16)lastMsgRcd; +#endif + } else { + XP_LOGF( "%s: expected %ld, got %ld", __func__, + rec->lastMsgRcd + 1, msgID ); + rec = NULL; + } + } else { + XP_LOGF( "%s: no rec for addr", __func__ ); + } + + LOG_RETURNF( XP_P, rec ); + return rec; +} /* validateChannelMessage */ + +XP_Bool +comms_checkIncomingStream( CommsCtxt* comms, XWStreamCtxt* stream, + const CommsAddrRec* addr ) +{ + XP_Bool validMessage = XP_FALSE; + XWHostID senderID = 0; /* unset; default for non-relay cases */ + XP_Bool usingRelay = XP_FALSE; + AddressRecord* rec = NULL; + + XP_ASSERT( addr == NULL || comms->addr.conType == addr->conType ); + + if ( !preProcess( comms, stream, &usingRelay, &senderID ) ) { + XP_U32 connID; + XP_PlayerAddr channelNo; + MsgID msgID; + MsgID lastMsgRcd; + + /* reject too-small message */ + if ( stream_getSize( stream ) >= + (sizeof(connID) + sizeof(channelNo) + + sizeof(msgID) + sizeof(lastMsgRcd)) ) { + XP_U16 payloadSize; + + connID = stream_getU32( stream ); + XP_STATUSF( "%s: read connID of %lx", __func__, connID ); + channelNo = stream_getU16( stream ); + XP_STATUSF( "read channelNo %d", channelNo ); + msgID = stream_getU32( stream ); + lastMsgRcd = stream_getU32( stream ); + XP_DEBUGF( "rcd: msgID=" XP_LD ",lastMsgRcd=" XP_LD " on chnl %d", + msgID, lastMsgRcd, channelNo ); + + payloadSize = stream_getSize( stream ) > 0; /* anything left? */ + if ( connID == CONN_ID_NONE ) { + /* special case: initial message from client */ + rec = validateInitialMessage( comms, payloadSize > 0, addr, senderID, + &channelNo ); + } else if ( comms->connID == connID ) { + rec = validateChannelMessage( comms, addr, channelNo, msgID, + lastMsgRcd ); + } + + validMessage = NULL != rec; + if ( validMessage ) { + rec->lastMsgRcd = msgID; + XP_LOGF( "%s: set channel %d's lastMsgRcd to " XP_LD, + __func__, channelNo, msgID ); + stream_setAddress( stream, channelNo ); + validMessage = payloadSize > 0; + } + } else { + XP_LOGF( "%s: message too small", __func__ ); + } + } + + /* Call after we've had a chance to create rec for addr */ + noteHBReceived( comms/* , addr */ ); + + LOG_RETURNF( "%d", (XP_U16)validMessage ); + return validMessage; +} /* comms_checkIncomingStream */ + +#ifdef COMMS_HEARTBEAT +static void +sendEmptyMsg( CommsCtxt* comms, AddressRecord* rec ) +{ + MsgQueueElem* elem = makeElemWithID( comms, + 0 /*rec? rec->lastMsgRcd : 0*/, + rec, + rec? rec->channelNo : 0, NULL ); + sendMsg( comms, elem ); + freeElem( comms, elem ); +} /* sendEmptyMsg */ + +/* Heartbeat. + * + * Goal is to allow all participants to detect when another is gone quickly. + * Assumption is that transport is cheap: sending extra packets doesn't cost + * much money or bother (meaning: don't do this over IR! :-). + * + * Keep track of last time we heard from each channel and of when we last sent + * a packet. Run a timer, and when it fires: 1) check if we haven't heard + * since 2x the timer interval. If so, call alert function and reset the + * underlying (ip, bt) channel. If not, check how long since we last sent a + * packet on each channel. If it's been longer than since the last timer, and + * if there are not already packets in the queue on that channel, fire a HB + * packet. + * + * A HB packet is one whose msg ID is lower than the most recent ACK'd so that + * it's sure to be dropped on the other end and not to interfere with packets + * that might be resent. + */ +static void +heartbeat_checks( CommsCtxt* comms ) +{ + LOG_FUNC(); + + do { + if ( comms->lastMsgRcvdTime > 0 ) { + XP_U32 now = util_getCurSeconds( comms->util ); + XP_U32 tooLongAgo = now - (HB_INTERVAL * 2); + if ( comms->lastMsgRcvdTime < tooLongAgo ) { + XP_LOGF( "calling reset proc; last was %ld secs too long ago", + tooLongAgo - comms->lastMsgRcvdTime ); + (*comms->resetproc)(comms->sendClosure); + comms->lastMsgRcvdTime = 0; + break; /* outta here */ + } + } + + if ( comms->recs ) { + AddressRecord* rec; + for ( rec = comms->recs; !!rec; rec = rec->next ) { + sendEmptyMsg( comms, rec ); + } + } else if ( !comms->isServer ) { + /* Client still waiting for inital ALL_REG message */ + sendEmptyMsg( comms, NULL ); + } + } while ( XP_FALSE ); + + setHeartbeatTimer( comms ); +} /* heartbeat_checks */ +#endif + +#if defined RELAY_HEARTBEAT || defined COMMS_HEARTBEAT +static XP_Bool +p_comms_timerFired( void* closure, XWTimerReason XP_UNUSED_DBG(why) ) +{ + CommsCtxt* comms = (CommsCtxt*)closure; + XP_ASSERT( why == TIMER_HEARTBEAT ); + LOG_FUNC(); + comms->hbTimerPending = XP_FALSE; + if (0 ) { +#ifdef RELAY_HEARTBEAT + } else if ( (comms->addr.conType == COMMS_CONN_RELAY ) + && (comms->r.heartbeat != HEARTBEAT_NONE) ) { + send_via_relay( comms, XWRELAY_HEARTBEAT, HOST_ID_NONE, NULL, 0 ); + /* No need to reset timer. send_via_relay does that. */ +#elif defined COMMS_HEARTBEAT + } else { + XP_ASSERT( comms->doHeartbeat ); + heartbeat_checks( comms ); +#endif + } + return XP_FALSE; /* no need for redraw */ +} /* p_comms_timerFired */ + +static void +setHeartbeatTimer( CommsCtxt* comms ) +{ + LOG_FUNC(); + if ( !comms->hbTimerPending ) { + XP_U16 when = 0; +#ifdef RELAY_HEARTBEAT + if ( comms->addr.conType == COMMS_CONN_RELAY ) { + when = comms->r.heartbeat; + } +#elif defined COMMS_HEARTBEAT + if ( comms->doHeartbeat ) { + XP_LOGF( "%s: calling util_setTimer", __func__ ); + when = HB_INTERVAL; + } else { + XP_LOGF( "%s: doHeartbeat not set", __func__ ); + } +#endif + if ( when != 0 ) { + util_setTimer( comms->util, TIMER_HEARTBEAT, when, + p_comms_timerFired, comms ); + comms->hbTimerPending = XP_TRUE; + } + } else { + XP_LOGF( "%s: skipping b/c pending", __func__ ); + } +} /* setHeartbeatTimer */ +#endif + +#ifdef DEBUG +void +comms_getStats( CommsCtxt* comms, XWStreamCtxt* stream ) +{ + XP_UCHAR buf[100]; + AddressRecord* rec; + MsgQueueElem* elem; + XP_U32 now; + + XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf), + (XP_UCHAR*)"msg queue len: %d\n", comms->queueLen ); + stream_putString( stream, buf ); + + for ( elem = comms->msgQueueHead; !!elem; elem = elem->next ) { + XP_SNPRINTF( buf, sizeof(buf), + " - channelNo=%d; msgID=" XP_LD "; len=%d\n", + elem->channelNo, elem->msgID, elem->len ); + stream_putString( stream, buf ); + } + + XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf), + (XP_UCHAR*)"channel-less bytes sent: %d\n", + comms->nUniqueBytes ); + stream_putString( stream, buf ); + + now = util_getCurSeconds( comms->util ); + for ( rec = comms->recs; !!rec; rec = rec->next ) { + XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf), + (XP_UCHAR*)" Stats for channel: %d\n", + rec->channelNo ); + stream_putString( stream, buf ); + + XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf), + (XP_UCHAR*)"Last msg sent: " XP_LD "\n", + rec->nextMsgID ); + stream_putString( stream, buf ); + + XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf), + (XP_UCHAR*)"Unique bytes sent: %d\n", + rec->nUniqueBytes ); + stream_putString( stream, buf ); + + XP_SNPRINTF( (XP_UCHAR*)buf, sizeof(buf), + (XP_UCHAR*)"Last message acknowledged: %d\n", + rec->lastACK); + stream_putString( stream, buf ); + + } +} /* comms_getStats */ +#endif + +static AddressRecord* +rememberChannelAddress( CommsCtxt* comms, XP_PlayerAddr channelNo, + XWHostID hostID, const CommsAddrRec* addr ) +{ + AddressRecord* recs = NULL; + recs = getRecordFor( comms, NULL, channelNo ); + if ( !recs ) { + /* not found; add a new entry */ + recs = (AddressRecord*)XP_MALLOC( comms->mpool, sizeof(*recs) ); + XP_MEMSET( recs, 0, sizeof(*recs) ); + + recs->channelNo = channelNo; + recs->r.hostID = hostID; + recs->next = comms->recs; + comms->recs = recs; + } + + /* overwrite existing address with new one. I assume that's the right + move. */ + if ( !!recs ) { + if ( !!addr ) { + XP_MEMCPY( &recs->addr, addr, sizeof(recs->addr) ); + XP_ASSERT( recs->r.hostID == hostID ); + } else { + XP_MEMSET( &recs->addr, 0, sizeof(recs->addr) ); + recs->addr.conType = comms->addr.conType; + } + } + return recs; +} /* rememberChannelAddress */ + +static void +updateChannelAddress( AddressRecord* rec, const CommsAddrRec* addr ) +{ + XP_ASSERT( !!rec ); + XP_MEMCPY( &rec->addr, addr, sizeof(rec->addr) ); +} /* updateChannelAddress */ + +static XP_Bool +channelToAddress( CommsCtxt* comms, XP_PlayerAddr channelNo, + const CommsAddrRec** addr ) +{ + AddressRecord* recs = getRecordFor( comms, NULL, channelNo ); + XP_Bool found = !!recs; + *addr = found? &recs->addr : NULL; + return found; +} /* channelToAddress */ + +static XP_U16 +countAddrRecs( const CommsCtxt* comms ) +{ + short count = 0; + AddressRecord* recs; + for ( recs = comms->recs; !!recs; recs = recs->next ) { + ++count; + } + return count; +} /* countAddrRecs */ + +#ifdef XWFEATURE_RELAY +static XWHostID +getDestID( CommsCtxt* comms, XP_PlayerAddr channelNo ) +{ + XWHostID id = HOST_ID_NONE; + if ( channelNo == CHANNEL_NONE ) { + id = HOST_ID_SERVER; + } else { + AddressRecord* recs; + for ( recs = comms->recs; !!recs; recs = recs->next ) { + if ( recs->channelNo == channelNo ) { + id = recs->r.hostID; + } + } + } + XP_LOGF( "getDestID(%d) => %x", channelNo, id ); + return id; +} /* getDestID */ + +static XP_Bool +send_via_relay( CommsCtxt* comms, XWRELAY_Cmd cmd, XWHostID destID, + void* data, int dlen ) +{ + XP_Bool success = XP_FALSE; + XP_U16 len = 0; + CommsAddrRec addr; + XWStreamCtxt* tmpStream; + XP_U8* buf; + + comms_getAddr( comms, &addr ); + tmpStream = mem_stream_make( MPPARM(comms->mpool) + util_getVTManager(comms->util), + NULL, 0, + (MemStreamCloseCallback)NULL ); + if ( tmpStream != NULL ) { + stream_open( tmpStream ); + stream_putU8( tmpStream, cmd ); + + switch ( cmd ) { + case XWRELAY_MSG_TORELAY: + stream_putU16( tmpStream, comms->r.cookieID ); + stream_putU8( tmpStream, comms->r.myHostID ); + stream_putU8( tmpStream, destID ); + if ( data != NULL && dlen > 0 ) { + stream_putBytes( tmpStream, data, dlen ); + } + break; + case XWRELAY_GAME_CONNECT: + stream_putU8( tmpStream, XWRELAY_PROTO_VERSION ); + stringToStream( tmpStream, addr.u.ip_relay.cookie ); + stream_putU8( tmpStream, comms->r.myHostID ); + stream_putU8( tmpStream, comms->r.nPlayersHere ); + stream_putU8( tmpStream, comms->r.nPlayersTotal ); + + comms->r.relayState = COMMS_RELAYSTATE_CONNECT_PENDING; + break; + + case XWRELAY_GAME_RECONNECT: + stream_putU8( tmpStream, XWRELAY_PROTO_VERSION ); + stream_putU8( tmpStream, comms->r.myHostID ); + stream_putU8( tmpStream, comms->r.nPlayersHere ); + stream_putU8( tmpStream, comms->r.nPlayersTotal ); + stringToStream( tmpStream, comms->r.connName ); + + comms->r.relayState = COMMS_RELAYSTATE_CONNECT_PENDING; + break; + + case XWRELAY_GAME_DISCONNECT: + stream_putU16( tmpStream, comms->r.cookieID ); + stream_putU8( tmpStream, comms->r.myHostID ); + break; + +#ifdef RELAY_HEARTBEAT + case XWRELAY_HEARTBEAT: + /* Add these for grins. Server can assert they match the IP + address it expects 'em on. */ + stream_putU16( tmpStream, comms->r.cookieID ); + stream_putU8( tmpStream, comms->r.myHostID ); + break; +#endif + default: + XP_ASSERT(0); + } + + len = stream_getSize( tmpStream ); + buf = XP_MALLOC( comms->mpool, len ); + if ( buf != NULL ) { + stream_getBytes( tmpStream, buf, len ); + } + stream_destroy( tmpStream ); + if ( buf != NULL ) { + XP_U16 result; + XP_LOGF( "passing %d bytes to sendproc", len ); + result = (*comms->sendproc)( buf, len, &addr, comms->sendClosure ); + success = result == len; + if ( success ) { + setHeartbeatTimer( comms ); + } + } + XP_FREE( comms->mpool, buf ); + } + return success; +} /* send_via_relay */ + +/* Send a CONNECT message to the relay. This opens up a connection to the + * relay, and tells it our hostID and cookie so that it can associatate it + * with a socket. In the CONNECT_RESP we should get back what? + */ +static void +relayConnect( CommsCtxt* comms ) +{ + LOG_FUNC(); + if ( comms->addr.conType == COMMS_CONN_RELAY && !comms->r.connecting ) { + comms->r.connecting = XP_TRUE; + send_via_relay( comms, + comms->r.connName[0] == '\0' ? + XWRELAY_GAME_CONNECT:XWRELAY_GAME_RECONNECT, + comms->r.myHostID, NULL, 0 ); + comms->r.connecting = XP_FALSE; + } +} /* relayConnect */ +#endif + +#if defined XWFEATURE_BLUETOOTH || defined XWFEATURE_IP_DIRECT +static XP_S16 +send_via_bt_or_ip( CommsCtxt* comms, BTIPMsgType typ, XP_PlayerAddr channelNo, + void* data, int dlen ) +{ + XP_S16 nSent; + XP_U8* buf; + LOG_FUNC(); + nSent = -1; + buf = XP_MALLOC( comms->mpool, dlen + 1 ); + if ( !!buf ) { + const CommsAddrRec* addr; + (void)channelToAddress( comms, channelNo, &addr ); + + buf[0] = typ; + if ( dlen > 0 ) { + XP_MEMCPY( &buf[1], data, dlen ); + } + + nSent = (*comms->sendproc)( buf, dlen+1, addr, comms->sendClosure ); + XP_FREE( comms->mpool, buf ); + + setHeartbeatTimer( comms ); + } + LOG_RETURNF( "%d", nSent ); + return nSent; +} /* send_via_bt_or_ip */ + +#endif + +#ifdef XWFEATURE_RELAY +static void +relayDisconnect( CommsCtxt* comms ) +{ + XP_LOGF( "relayDisconnect called" ); + if ( comms->addr.conType == COMMS_CONN_RELAY ) { + if ( comms->r.relayState != COMMS_RELAYSTATE_UNCONNECTED ) { + comms->r.relayState = COMMS_RELAYSTATE_UNCONNECTED; + send_via_relay( comms, XWRELAY_GAME_DISCONNECT, HOST_ID_NONE, + NULL, 0 ); + } + } +} /* relayDisconnect */ +#endif + +EXTERN_C_END + +#endif /* #ifndef XWFEATURE_STANDALONE_ONLY */ diff --git a/xwords4/common/comms.h b/xwords4/common/comms.h new file mode 100644 index 000000000..32e547091 --- /dev/null +++ b/xwords4/common/comms.h @@ -0,0 +1,138 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _COMMS_H_ +#define _COMMS_H_ + +#include "comtypes.h" +#include "mempool.h" +#include "xwrelay.h" +#include "server.h" + +EXTERN_C_START + +#define CHANNEL_NONE ((XP_PlayerAddr)0) +#define CONN_ID_NONE 0L + +typedef XP_U32 MsgID; /* this is too big!!! PENDING */ +typedef XP_U8 XWHostID; + +typedef enum { + COMMS_CONN_NONE /* I want errors on uninited case */ + ,COMMS_CONN_IR + ,COMMS_CONN_IP_DIRECT + ,COMMS_CONN_RELAY + ,COMMS_CONN_BT +} CommsConnType; + +/* WHAT SHOULD THIS BE? Copied from Whiteboard.... PENDING */ +#define XW_BT_UUID \ + { 0x83, 0xe0, 0x87, 0xae, 0x4e, 0x18, 0x46, 0xbe, \ + 0x83, 0xe0, 0x7b, 0x3d, 0xe6, 0xa1, 0xc3, 0x3b } + +#define XW_BT_NAME "Crosswords" + +/* on Palm BtLibDeviceAddressType is a 48-bit quantity. Linux's typeis the + same size. Goal is something all platforms support */ +typedef struct XP_BtAddr { XP_U8 bits[6]; } XP_BtAddr; +typedef struct XP_BtAddrStr { XP_UCHAR chars[18]; } XP_BtAddrStr; + +#ifdef COMMS_HEARTBEAT +# define IF_CH(a) a, +#else +# define IF_CH(a) +#endif + +#define MAX_HOSTNAME_LEN 63 +typedef struct CommsAddrRec { + CommsConnType conType; + + union { + struct { + XP_UCHAR hostName_ip[MAX_HOSTNAME_LEN + 1]; + XP_U32 ipAddr_ip; /* looked up from above */ + XP_U16 port_ip; + } ip; + struct { + XP_UCHAR cookie[MAX_COOKIE_LEN + 1]; + XP_UCHAR hostName[MAX_HOSTNAME_LEN + 1]; + XP_U32 ipAddr; /* looked up from above */ + XP_U16 port; + } ip_relay; + struct { + /* nothing? */ + XP_UCHAR foo; /* wince doesn't like nothing here */ + } ir; + struct { + /* guests can browse for the host to connect to */ + XP_UCHAR hostName[MAX_HOSTNAME_LEN + 1]; + XP_BtAddr btAddr; + } bt; + } u; +} CommsAddrRec; + +typedef XP_S16 (*TransportSend)( const XP_U8* buf, XP_U16 len, + const CommsAddrRec* addr, + void* closure ); +typedef void (*TransportReset)( void* closure ); + +CommsCtxt* comms_make( MPFORMAL XW_UtilCtxt* util, + XP_Bool isServer, + XP_U16 nPlayersHere, XP_U16 nPlayersTotal, + TransportSend sendproc, IF_CH(TransportReset resetproc) + void* closure ); + +void comms_reset( CommsCtxt* comms, XP_Bool isServer, + XP_U16 nPlayersHere, XP_U16 nPlayersTotal ); +void comms_destroy( CommsCtxt* comms ); + +void comms_setConnID( CommsCtxt* comms, XP_U32 connID ); + +/* "static" methods work when no comms present */ +void comms_getInitialAddr( CommsAddrRec* addr ); +XP_Bool comms_checkAddr( DeviceRole role, const CommsAddrRec* addr, + XW_UtilCtxt* util ); + +void comms_getAddr( const CommsCtxt* comms, CommsAddrRec* addr ); +void comms_setAddr( CommsCtxt* comms, const CommsAddrRec* addr ); + +CommsConnType comms_getConType( const CommsCtxt* comms ); +XP_Bool comms_getIsServer( const CommsCtxt* comms ); + +CommsCtxt* comms_makeFromStream( MPFORMAL XWStreamCtxt* stream, + XW_UtilCtxt* util, TransportSend sendproc, + IF_CH(TransportReset resetproc) + void* closure ); +void comms_start( CommsCtxt* comms ); +void comms_writeToStream( const CommsCtxt* comms, XWStreamCtxt* stream ); + +XP_S16 comms_send( CommsCtxt* comms, XWStreamCtxt* stream ); +XP_S16 comms_resendAll( CommsCtxt* comms ); + + +XP_Bool comms_checkIncomingStream( CommsCtxt* comms, XWStreamCtxt* stream, + const CommsAddrRec* addr ); + +# ifdef DEBUG +void comms_getStats( CommsCtxt* comms, XWStreamCtxt* stream ); +# endif + +EXTERN_C_END + +#endif /* _COMMS_H_ */ diff --git a/xwords4/common/comtypes.h b/xwords4/common/comtypes.h new file mode 100644 index 000000000..0f8727958 --- /dev/null +++ b/xwords4/common/comtypes.h @@ -0,0 +1,241 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef _COMTYPES_H_ +#define _COMTYPES_H_ + +#include "xptypes.h" + +#ifndef EXTERN_C_START +# ifdef CPLUS +# define EXTERN_C_START extern "C" { +# else +# define EXTERN_C_START +# endif +#endif + +#ifndef EXTERN_C_END +# ifdef CPLUS +# define EXTERN_C_END } +# else +# define EXTERN_C_END +# endif +#endif + +#define VSIZE(arr) (sizeof(arr)/sizeof(arr[0])) + +typedef struct XP_Rect { + XP_S16 left; + XP_S16 top; + XP_S16 width; + XP_S16 height; +} XP_Rect; + +typedef XP_U16 CellTile; + +typedef XP_U8 Tile; + +typedef void* XP_Bitmap; + +typedef XP_UCHAR XP_UCHAR4[4]; + +typedef enum { + TRI_ENAB_NONE + ,TRI_ENAB_HIDDEN + ,TRI_ENAB_DISABLED + ,TRI_ENAB_ENABLED +} XP_TriEnable; + +typedef enum { + DFS_NONE + ,DFS_TOP /* focus is on the object */ + ,DFS_DIVED /* focus is inside the object */ +} DrawFocusState; + +typedef enum { + TRAY_HIDDEN, /* doesn't happen unless tray overlaps board */ + TRAY_REVERSED, + TRAY_REVEALED +} XW_TrayVisState; + +typedef enum { + OBJ_NONE, + OBJ_BOARD, + OBJ_SCORE, + OBJ_TRAY +} BoardObjectType; + +/* I'm going to try putting all forward "class" decls in the same file */ +typedef struct BoardCtxt BoardCtxt; +typedef struct CommMgrCtxt CommMgrCtxt; +typedef struct DictionaryCtxt DictionaryCtxt; +typedef struct DrawCtx DrawCtx; +typedef struct EngineCtxt EngineCtxt; +typedef struct ModelCtxt ModelCtxt; +typedef struct CommsCtxt CommsCtxt; +typedef struct PlayerSocket PlayerSocket; +typedef struct ScoreBdContext ScoreBdContext; +typedef struct ServerCtxt ServerCtxt; +typedef struct XWStreamCtxt XWStreamCtxt; +typedef struct TrayContext TrayContext; +typedef struct PoolContext PoolContext; +typedef struct XW_UtilCtxt XW_UtilCtxt; + +typedef XP_S16 XP_PlayerAddr; + +typedef enum { + TIMER_PENDOWN = 1, /* ARM doesn't like ids of 0... */ + TIMER_TIMERTICK, +#if defined RELAY_HEARTBEAT || defined COMMS_HEARTBEAT + TIMER_HEARTBEAT, +#endif + + NUM_TIMERS_PLUS_ONE /* must be last */ +} XWTimerReason; + +#define MAX_NUM_PLAYERS 4 +#define PLAYERNUM_NBITS 2 +#define NDEVICES_NBITS 2 /* 1-4, but reduced by 1 fits in 2 bits */ +#define NPLAYERS_NBITS 3 +#define EMPTIED_TRAY_BONUS 50 + +/* I need a way to communiate prefs to common/ code. For now, though, I'll + * leave storage of these values up to the platforms. First, because I don't + * want to deal with versioning in the common code. Second, becuase they + * already have the notion of per-game and all-game prefs. + */ +typedef struct CommonPrefs { + XP_Bool showBoardArrow; /* applies to all games */ + XP_Bool showRobotScores; /* applies to all games */ + XP_Bool hideTileValues; + XP_Bool reserved2; /* get to 32-bit for ARM... */ +} CommonPrefs; + +#ifdef XWFEATURE_BLUETOOTH +/* temporary debugging hack */ + +/* From BtLibTypes.h: Pre-assigned assigned PSM values are permitted; however, + * they must be odd, within the range of 0x1001 to 0xFFFF, and have the 9th + * bit (0x0100) set to zero. Passing in BT_L2CAP_RANDOM_PSM will automatically + * create a usable PSM for the channel. In this case the actual PSM value will + * be filled in by the call. */ + +# define XW_PSM 0x3031 +#endif + +/* used for all vtables */ +#define SET_VTABLE_ENTRY( vt, name, prefix ) \ + (vt)->m_##name = prefix##_##name + +#ifdef DRAW_LINK_DIRECT +# define DLSTATIC +#else +# define DLSTATIC static +#endif + +#ifdef DEBUG +# define DEBUG_ASSIGN(a,b) (a) = (b) +#else +# define DEBUG_ASSIGN(a,b) +#endif + +#define OFFSET_OF(typ,var) ((XP_U16)&(((typ*) 0)->var)) + + +#ifdef MEM_DEBUG +# define XP_MALLOC(pool,nbytes) \ + mpool_alloc((pool),(nbytes),__FILE__,__LINE__) +# define XP_REALLOC(pool,p,s) mpool_realloc((pool),(p),(s),__FILE__,__LINE__) +# define XP_FREE(pool,p) mpool_free((pool), (p),__FILE__,__LINE__) + +# define MPFORMAL_NOCOMMA MemPoolCtx* mpool +# define MPFORMAL MPFORMAL_NOCOMMA, +# define MPSLOT MPFORMAL_NOCOMMA; +# define MPPARM_NOCOMMA(p) (p) +# define MPPARM(p) MPPARM_NOCOMMA(p), +# define MPASSIGN(slot,val) (slot)=(val) + +#else + +# define MPFORMAL_NOCOMMA +# define MPFORMAL +# define MPSLOT +# define MPPARM_NOCOMMA(p) +# define MPPARM(p) +# define MPASSIGN(slot,val) + +#endif + +#define LOG_FUNC() XP_LOGF( "IN: %s", __func__ ) +#define LOG_RETURNF(fmt, ...) XP_LOGF( "%s => " fmt, __func__, __VA_ARGS__ ) +#define LOG_RETURN_VOID() LOG_RETURNF("%s","void") +#define XP_LOGLOC() XP_LOGF( "%s(), line %d", __func__, __LINE__ ) + +#ifndef XP_UNUSED +# if defined __GNUC__ +# define XP_UNUSED(x) UNUSED__ ## x __attribute__((unused)) +# else +# define XP_UNUSED(x) x +# endif +#endif + +#ifdef DEBUG +# define XP_UNUSED_DBG(x) x +#else +# define XP_UNUSED_DBG(x) XP_UNUSED(x) +#endif + +#ifdef ENABLE_LOGGING +# define XP_UNUSED_LOG(x) x +#else +# define XP_UNUSED_LOG(x) XP_UNUSED(x) +#endif + +#ifdef XWFEATURE_RELAY +# define XP_UNUSED_RELAY(x) x +#else +# define XP_UNUSED_RELAY(x) UNUSED__ ## x __attribute__((unused)) +#endif + +#ifdef XWFEATURE_BLUETOOTH +# define XP_UNUSED_BT(x) x +#else +# define XP_UNUSED_BT(x) UNUSED__ ## x __attribute__((unused)) +#endif + +#if BT_USE_RFCOMM +# define XP_UNUSED_RFCOMM(x) x +#else +# define XP_UNUSED_RFCOMM(x) UNUSED__ ## x __attribute__((unused)) +#endif + +#ifdef KEYBOARD_NAV +# define XP_UNUSED_KEYBOARD_NAV(x) x +#else +# define XP_UNUSED_KEYBOARD_NAV(x) UNUSED__ ## x __attribute__((unused)) +#endif + +#ifndef XWFEATURE_STANDALONE_ONLY +# define XP_UNUSED_STANDALONE(x) x +#else +# define XP_UNUSED_STANDALONE(x) UNUSED__ ## x __attribute__((unused)) +#endif + +#endif diff --git a/xwords4/common/config.mk b/xwords4/common/config.mk new file mode 100644 index 000000000..eaccd7b0c --- /dev/null +++ b/xwords4/common/config.mk @@ -0,0 +1,81 @@ +# -*- mode: makefile -*- + +# Copyright 2002 by Eric House +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +COMMON_INCS = -I ./$(PLATFORM) -I../common -I../relay +INCLUDES += $(COMMON_INCS) -I./ + +COMMONDIR ?= ../common +COMMONOBJDIR = ../common/$(PLATFORM) + +COMMONSRC = \ + $(COMMONDIR)/board.c \ + $(COMMONDIR)/boarddrw.c \ + $(COMMONDIR)/dragdrpp.c \ + $(COMMONDIR)/scorebdp.c \ + $(COMMONDIR)/tray.c \ + $(COMMONDIR)/draw.c \ + $(COMMONDIR)/model.c \ + $(COMMONDIR)/mscore.c \ + $(COMMONDIR)/server.c \ + $(COMMONDIR)/pool.c \ + $(COMMONDIR)/game.c \ + $(COMMONDIR)/nwgamest.c \ + $(COMMONDIR)/dictnry.c \ + $(COMMONDIR)/engine.c \ + $(COMMONDIR)/memstream.c \ + $(COMMONDIR)/comms.c \ + $(COMMONDIR)/mempool.c \ + $(COMMONDIR)/movestak.c \ + $(COMMONDIR)/strutils.c \ + $(COMMONDIR)/vtabmgr.c \ + $(COMMONDIR)/dbgutil.c \ + +# PENDING: define this in terms of above!!! + +COMMON1 = \ + $(COMMONOBJDIR)/board.o \ + $(COMMONOBJDIR)/boarddrw.o \ + $(COMMONOBJDIR)/tray.o \ + $(COMMONOBJDIR)/scorebdp.o \ + $(COMMONOBJDIR)/draw.o \ + +COMMON2 = \ + $(COMMONOBJDIR)/model.o \ + $(COMMONOBJDIR)/mscore.o \ + $(COMMONOBJDIR)/server.o \ + $(COMMONOBJDIR)/pool.o \ + +COMMON3 = \ + $(COMMONOBJDIR)/game.o \ + $(COMMONOBJDIR)/nwgamest.o \ + $(COMMONOBJDIR)/dictnry.o \ + $(COMMONOBJDIR)/engine.o \ + +COMMON4 = \ + $(COMMONOBJDIR)/dragdrpp.o \ + $(COMMONOBJDIR)/memstream.o \ + $(COMMONOBJDIR)/comms.o \ + $(COMMONOBJDIR)/mempool.o \ + +COMMON5 = \ + $(COMMONOBJDIR)/movestak.o \ + $(COMMONOBJDIR)/strutils.o \ + $(COMMONOBJDIR)/vtabmgr.o \ + $(COMMONOBJDIR)/dbgutil.o \ + +COMMONOBJ = $(COMMON1) $(COMMON2) $(COMMON3) $(COMMON4) $(COMMON5) diff --git a/xwords4/common/dawg.h b/xwords4/common/dawg.h new file mode 100644 index 000000000..a29187d2d --- /dev/null +++ b/xwords4/common/dawg.h @@ -0,0 +1,88 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4;-*- */ +/* + * Copyright 1998-2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _DAWG_H_ +#define _DAWG_H_ + +#include "xptypes.h" + +/* + * the bits field has five bits for the character (0-based rather than + * 'a'-based, of course; one bit each indicating whether the edge may + * be terminal and whether it's the last edge of a sub-array; and a final + * bit that's overflow from the highByte field allowing indices to be in + * the range 0-(2^^17)-1 + */ +#define LETTERMASK_OLD 0x1f +#define ACCEPTINGMASK_OLD 0x20 +#define LASTEDGEMASK_OLD 0x40 +#define EXTRABITMASK_OLD 0x80 + +#define LETTERMASK_NEW_4 0x3f +#define LETTERMASK_NEW_3 0x1f +#define ACCEPTINGMASK_NEW 0x80 +#define LASTEDGEMASK_NEW 0x40 +/* This guy doesn't exist in 4-byte case */ +#define EXTRABITMASK_NEW 0x20 + +#define OLD_THREE_FIELDS \ + XP_U8 highByte; \ + XP_U8 lowByte; \ + XP_U8 bits + +typedef struct array_edge_old { + OLD_THREE_FIELDS; +} array_edge_old; + +typedef struct array_edge_new { + OLD_THREE_FIELDS; + XP_U8 moreBits; +} array_edge_new; + +typedef XP_U8 array_edge; + +/* This thing exists only in current xwords dicts (on PalmOS); not sure if I + * should do away with it. + */ +typedef struct dawg_header { + unsigned long numWords; + unsigned char firstEdgeRecNum; + unsigned char charTableRecNum; + unsigned char valTableRecNum; + unsigned char reserved[3]; /* worst case this points to a new resource */ +#ifdef NODE_CAN_4 + unsigned short flags; +#endif +} dawg_header; + +/* Part of xwords3 dictionaries on PalmOS */ +typedef struct Xloc_header { + unsigned char langCodeFlags; /* can't do bitfields; gcc for pilot and x86 + seem to generate different code */ + unsigned char padding; /* ptrs to the shorts in Xloc_specialEntry + will otherwise be odd */ +} Xloc_header; + +typedef struct Xloc_specialEntry { + unsigned char textVersion[4]; /* string can be up to 3 chars long */ + short hasLarge; + short hasSmall; +} Xloc_specialEntry; + +#endif /* _DAWG_H_ */ diff --git a/xwords4/common/dbgutil.c b/xwords4/common/dbgutil.c new file mode 100644 index 000000000..5ee32c935 --- /dev/null +++ b/xwords4/common/dbgutil.c @@ -0,0 +1,75 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2006 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef ENABLE_LOGGING + +#include "dbgutil.h" + +#define CASESTR(s) case s: return #s + +#define FUNC(f) #f + +const char* +XP_Key_2str( XP_Key key ) +{ + switch( key ) { + CASESTR(XP_KEY_NONE); + CASESTR(XP_CURSOR_KEY_DOWN); + CASESTR(XP_CURSOR_KEY_ALTDOWN); + CASESTR(XP_CURSOR_KEY_RIGHT); + CASESTR(XP_CURSOR_KEY_ALTRIGHT); + CASESTR(XP_CURSOR_KEY_UP); + CASESTR(XP_CURSOR_KEY_ALTUP); + CASESTR(XP_CURSOR_KEY_LEFT); + CASESTR(XP_CURSOR_KEY_ALTLEFT); + CASESTR(XP_CURSOR_KEY_DEL); + CASESTR(XP_RAISEFOCUS_KEY); + CASESTR(XP_RETURN_KEY); + CASESTR(XP_KEY_LAST ); + default: return FUNC(__func__) " unknown"; + } +} + +const char* +DrawFocusState_2str( DrawFocusState dfs ) +{ + switch( dfs ) { + CASESTR(DFS_NONE); + CASESTR(DFS_TOP); + CASESTR(DFS_DIVED); + default: return FUNC(__func__) " unknown"; + } +} + +const char* +BoardObjectType_2str( BoardObjectType obj ) +{ + switch( obj ) { + CASESTR(OBJ_NONE); + CASESTR(OBJ_BOARD); + CASESTR(OBJ_SCORE); + CASESTR(OBJ_TRAY); + default: return FUNC(__func__) " unknown"; + } +} + +#undef CASESTR + +#endif /* ENABLE_LOGGING */ + diff --git a/xwords4/common/dbgutil.h b/xwords4/common/dbgutil.h new file mode 100644 index 000000000..fae2486bd --- /dev/null +++ b/xwords4/common/dbgutil.h @@ -0,0 +1,29 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2006 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _DBGUTIL_H_ +#define _DBGUTIL_H_ + +#include "board.h" + +const char* XP_Key_2str( XP_Key key ); +const char* DrawFocusState_2str( DrawFocusState dfs ); +const char* BoardObjectType_2str( BoardObjectType dfs ); + +#endif diff --git a/xwords4/common/dictnry.c b/xwords4/common/dictnry.c new file mode 100644 index 000000000..f2f76d72e --- /dev/null +++ b/xwords4/common/dictnry.c @@ -0,0 +1,531 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997-2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef USE_STDIO +# include +# include +#endif + +#include "comtypes.h" +#include "dictnryp.h" +#include "xwstream.h" +#include "strutils.h" + +#ifdef CPLUS +extern "C" { +#endif + +/***************************************************************************** + * + ****************************************************************************/ +void +setBlankTile( DictionaryCtxt* dctx ) +{ + XP_U16 i; + + dctx->blankTile = -1; /* no known blank */ + + for ( i = 0; i < dctx->nFaces; ++i ) { + if ( dctx->faces16[i] == 0 ) { + XP_ASSERT( dctx->blankTile == -1 ); /* only one passes test? */ + dctx->blankTile = (XP_S8)i; +#ifndef DEBUG + break; +#endif + } + } +} /* setBlankTile */ + +/* #if defined BLANKS_FIRST || defined DEBUG */ +XP_Bool +dict_hasBlankTile( const DictionaryCtxt* dict ) +{ + return dict->blankTile >= 0; +} /* dict_hasBlankTile */ +/* #endif */ + +Tile +dict_getBlankTile( const DictionaryCtxt* dict ) +{ + XP_ASSERT( dict_hasBlankTile(dict) ); + return (Tile)dict->blankTile; +} /* dict_getBlankTile */ + +XP_U16 +dict_getTileValue( const DictionaryCtxt* dict, Tile tile ) +{ + if ( (tile & TILE_VALUE_MASK) != tile ) { + XP_ASSERT( tile == 32 && + tile == dict_getBlankTile( dict ) ); + } + XP_ASSERT( tile < dict->nFaces ); + tile *= 2; + return dict->countsAndValues[tile+1]; +} /* dict_getTileValue */ + +static XP_UCHAR +dict_getTileChar( const DictionaryCtxt* dict, Tile tile ) +{ + XP_ASSERT( tile < dict->nFaces ); + XP_ASSERT( (dict->faces16[tile] & 0xFF00) == 0 ); /* no unicode yet */ + return (XP_UCHAR)dict->faces16[tile]; +} /* dict_getTileValue */ + +XP_U16 +dict_numTiles( const DictionaryCtxt* dict, Tile tile ) +{ + tile *= 2; + return dict->countsAndValues[tile]; +} /* dict_numTiles */ + +XP_U16 +dict_numTileFaces( const DictionaryCtxt* dict ) +{ + return dict->nFaces; +} /* dict_numTileFaces */ + +XP_U16 +dict_tilesToString( const DictionaryCtxt* ctxt, const Tile* tiles, + XP_U16 nTiles, XP_UCHAR* buf, XP_U16 bufSize ) +{ + XP_UCHAR* bufp = buf; + XP_UCHAR* end = bufp + bufSize; + XP_U16 result = 0; + + while ( nTiles-- ) { + Tile tile = *tiles++; + XP_UCHAR face = dict_getTileChar(ctxt, tile); + + if ( IS_SPECIAL(face) ) { + XP_UCHAR* chars = ctxt->chars[(XP_U16)face]; + XP_U16 len = XP_STRLEN( chars ); + if ( bufp + len >= end ) { + bufp = NULL; + break; + } + XP_MEMCPY( bufp, chars, len ); + bufp += len; + } else { + XP_ASSERT ( tile != ctxt->blankTile ); /* printing blank should be + handled by specials + mechanism */ + if ( bufp + 1 >= end ) { + bufp = NULL; + break; + } + *bufp++ = face; + } + } + + if ( bufp != NULL && bufp < end ) { + *bufp = '\0'; + result = bufp - buf; + } + return result; +} /* dict_tilesToString */ + +Tile +dict_tileForString( const DictionaryCtxt* dict, const XP_UCHAR* key ) +{ + XP_U16 nFaces = dict_numTileFaces( dict ); + Tile tile; + XP_Bool keyNotSpecial = XP_STRLEN((char*)key) == 1; + + for ( tile = 0; tile < nFaces; ++tile ) { + XP_UCHAR face = dict_getTileChar( dict, tile ); + if ( IS_SPECIAL(face) ) { + XP_UCHAR* chars = dict->chars[(XP_U16)face]; + if ( 0 == XP_STRNCMP( chars, key, XP_STRLEN(chars) ) ) { + return tile; + } + } else if ( keyNotSpecial && (face == *key) ) { + return tile; + } + } + return EMPTY_TILE; +} /* dict_tileForChar */ + +XP_Bool +dict_tilesAreSame( const DictionaryCtxt* dict1, const DictionaryCtxt* dict2 ) +{ + XP_Bool result = XP_FALSE; + + Tile i; + XP_U16 nTileFaces = dict_numTileFaces( dict1 ); + + if ( nTileFaces == dict_numTileFaces( dict2 ) ) { + for ( i = 0; i < nTileFaces; ++i ) { + + XP_UCHAR face1, face2; + + if ( dict_getTileValue( dict1, i ) + != dict_getTileValue( dict2, i ) ){ + break; + } +#if 1 + face1 = dict_getTileChar( dict1, i ); + face2 = dict_getTileChar( dict2, i ); + if ( face1 != face2 ) { + break; + } +#else + face1 = dict_getTileChar( dict1, i ); + face2 = dict_getTileChar( dict2, i ); + if ( IS_SPECIAL(face1) != IS_SPECIAL(face2) ) { + break; + } + if ( IS_SPECIAL(face1) ) { + XP_UCHAR* chars1 = dict1->chars[face1]; + XP_UCHAR* chars2 = dict2->chars[face2]; + XP_U16 len = XP_STRLEN(chars1); + if ( 0 != XP_STRNCMP( chars1, chars2, len ) ) { + break; + } + } else if ( face1 != face2 ) { + break; + } +#endif + if ( dict_numTiles( dict1, i ) != dict_numTiles( dict2, i ) ) { + break; + } + } + result = i == nTileFaces; /* did we get that far */ + } + return result; +} /* dict_tilesAreSame */ + +void +dict_writeToStream( const DictionaryCtxt* dict, XWStreamCtxt* stream ) +{ + XP_U16 maxCount = 0; + XP_U16 maxValue = 0; + XP_U16 i, nSpecials; + XP_U16 maxCountBits, maxValueBits; + + stream_putBits( stream, 6, dict->nFaces ); + + for ( i = 0; i < dict->nFaces*2; i+=2 ) { + XP_U16 count, value; + + count = dict->countsAndValues[i]; + if ( maxCount < count ) { + maxCount = count; + } + + value = dict->countsAndValues[i+1]; + if ( maxValue < value ) { + maxValue = value; + } + } + + maxCountBits = bitsForMax( maxCount ); + maxValueBits = bitsForMax( maxValue ); + + stream_putBits( stream, 3, maxCountBits ); /* won't be bigger than 5 */ + stream_putBits( stream, 3, maxValueBits ); + + for ( i = 0; i < dict->nFaces*2; i+=2 ) { + stream_putBits( stream, maxCountBits, dict->countsAndValues[i] ); + stream_putBits( stream, maxValueBits, dict->countsAndValues[i+1] ); + } + + for ( i = 0; i < dict->nFaces; ++i ) { + stream_putU8( stream, (XP_U8)dict->faces16[i] ); + } + + for ( nSpecials = i = 0; i < dict->nFaces; ++i ) { + XP_UCHAR face = dict_getTileChar( dict, (Tile)i ); + if ( IS_SPECIAL( face ) ) { + stringToStream( stream, dict->chars[nSpecials++] ); + } + } +} /* dict_writeToStream */ + +static void +freeSpecials( DictionaryCtxt* dict ) +{ + Tile t; + XP_U16 nSpecials; + + for ( nSpecials = t = 0; t < dict->nFaces; ++t ) { + XP_UCHAR face = dict_getTileChar( dict, t ); + if ( IS_SPECIAL( face ) ) { + + XP_ASSERT( !!dict->chars[nSpecials] ); + XP_FREE( dict->mpool, dict->chars[nSpecials] ); + + if ( !!dict->bitmaps[nSpecials].largeBM ) { + XP_FREE( dict->mpool, dict->bitmaps[nSpecials].largeBM ); + } + if ( !!dict->bitmaps[nSpecials].smallBM ) { + XP_FREE( dict->mpool, dict->bitmaps[nSpecials].smallBM ); + } + + ++nSpecials; + } + } + if ( nSpecials > 0 ) { + XP_FREE( dict->mpool, dict->chars ); + XP_FREE( dict->mpool, dict->bitmaps ); + } +} /* freeSpecials */ + +static void +common_destructor( DictionaryCtxt* dict ) +{ + freeSpecials( dict ); + + XP_FREE( dict->mpool, dict->countsAndValues ); + XP_FREE( dict->mpool, dict->faces16 ); + + XP_FREE( dict->mpool, dict ); +} /* dict */ + +void +dict_loadFromStream( DictionaryCtxt* dict, XWStreamCtxt* stream ) +{ + XP_U8 nFaces; + XP_U16 maxCountBits, maxValueBits; + XP_U16 i, nSpecials; + XP_UCHAR* localTexts[32]; + + XP_ASSERT( !dict->destructor ); + dict->destructor = common_destructor; + dict->func_dict_getShortName = dict_getName; /* default */ + + nFaces = (XP_U8)stream_getBits( stream, 6 ); + maxCountBits = (XP_U16)stream_getBits( stream, 3 ); + maxValueBits = (XP_U16)stream_getBits( stream, 3 ); + + dict->nFaces = nFaces; + + dict->countsAndValues = + (XP_U8*)XP_MALLOC( dict->mpool, + sizeof(dict->countsAndValues[0]) * nFaces * 2 ); + + for ( i = 0; i < dict->nFaces*2; i+=2 ) { + dict->countsAndValues[i] = (XP_U8)stream_getBits( stream, + maxCountBits ); + dict->countsAndValues[i+1] = (XP_U8)stream_getBits( stream, + maxValueBits ); + } + + dict->faces16 = (XP_CHAR16*)XP_MALLOC( dict->mpool, + sizeof(dict->faces16[0]) * nFaces ); + for ( i = 0; i < dict->nFaces; ++i ) { + dict->faces16[i] = (XP_CHAR16)stream_getU8( stream ); + } + + for ( nSpecials = i = 0; i < nFaces; ++i ) { + XP_UCHAR face = dict_getTileChar( dict, (Tile)i ); + if ( IS_SPECIAL( face ) ) { + XP_UCHAR* txt = stringFromStream( dict->mpool, stream ); + XP_ASSERT( !!txt ); + localTexts[nSpecials] = txt; + + ++nSpecials; + } + } + if ( nSpecials > 0 ) { + dict->bitmaps = + (SpecialBitmaps*)XP_MALLOC( dict->mpool, + nSpecials * sizeof(*dict->bitmaps) ); + XP_MEMSET( dict->bitmaps, 0, nSpecials * sizeof(*dict->bitmaps) ); + + dict->chars = (XP_UCHAR**)XP_MALLOC( dict->mpool, + nSpecials * sizeof(*dict->chars) ); + XP_MEMCPY(dict->chars, localTexts, nSpecials * sizeof(*dict->chars)); + } + + setBlankTile( dict ); +} /* dict_loadFromStream */ + +const XP_UCHAR* +dict_getName( const DictionaryCtxt* dict ) +{ + XP_ASSERT( !!dict ); + XP_ASSERT( !!dict->name ); + return dict->name; +} /* dict_getName */ + +XP_Bool +dict_faceIsBitmap( const DictionaryCtxt* dict, Tile tile ) +{ + XP_UCHAR face = dict_getTileChar( dict, tile ); + return /* face != 0 && */IS_SPECIAL(face); +} /* dict_faceIsBitmap */ + +XP_Bitmap +dict_getFaceBitmap( const DictionaryCtxt* dict, Tile tile, XP_Bool isLarge ) +{ + SpecialBitmaps* bitmaps; + XP_UCHAR face = dict_getTileChar( dict, tile ); + + XP_ASSERT( dict_faceIsBitmap( dict, tile ) ); + XP_ASSERT( !!dict->bitmaps ); + + bitmaps = &dict->bitmaps[(XP_U16)face]; + return isLarge? bitmaps->largeBM:bitmaps->smallBM; +} /* dict_getFaceBitmap */ + +#ifdef TALL_FONTS +XP_LangCode +dict_getLangCode( const DictionaryCtxt* dict ) +{ + return dict->langCode; +} +#endif + +#ifdef STUBBED_DICT + +#define BLANK_FACE '\0' + +static XP_U8 stub_english_data[] = { + /* count value face */ + 9, 1, 'A', + 2, 3, 'B', + 2, 3, 'C', + 4, 2, 'D', + 12, 1, 'E', + 2, 4, 'F', + 3, 2, 'G', + 2, 4, 'H', + 9, 1, 'I', + 1, 8, 'J', + 1, 5, 'K', + 4, 1, 'L', + 2, 3, 'M', + 6, 1, 'N', + 8, 1, 'O', + 2, 3, 'P', + 1, 10, 'Q', + 6, 1, 'R', + 4, 1, 'S', + 6, 1, 'T', + 4, 1, 'U', + 2, 4, 'V', + 2, 4, 'W', + 1, 8, 'X', + 2, 4, 'Y', + 1, 10, 'Z', + 2, 0, BLANK_FACE, /* BLANK1 */ +}; + +void +setStubbedSpecials( DictionaryCtxt* dict ) +{ + dict->chars = (XP_UCHAR**)XP_MALLOC( dict->mpool, sizeof(char*) ); + dict->chars[0] = "_"; + +} /* setStubbedSpecials */ + +void +destroy_stubbed_dict( DictionaryCtxt* dict ) +{ + XP_FREE( dict->mpool, dict->countsAndValues ); + XP_FREE( dict->mpool, dict->faces16 ); + XP_FREE( dict->mpool, dict->chars ); + XP_FREE( dict->mpool, dict->name ); + XP_FREE( dict->mpool, dict->bitmaps ); + XP_FREE( dict->mpool, dict ); +} /* destroy_stubbed_dict */ + +DictionaryCtxt* +make_stubbed_dict( MPFORMAL_NOCOMMA ) +{ + DictionaryCtxt* dict = (DictionaryCtxt*)XP_MALLOC( mpool, sizeof(*dict) ); + XP_U8* data = stub_english_data; + XP_U16 datasize = sizeof(stub_english_data); + XP_U16 i; + + XP_MEMSET( dict, 0, sizeof(*dict) ); + + MPASSIGN( dict->mpool, mpool ); + dict->name = copyString( mpool, "Stub dictionary" ); + dict->nFaces = datasize/3; + + dict->destructor = destroy_stubbed_dict; + + dict->faces16 = (XP_CHAR16*) + XP_MALLOC( mpool, dict->nFaces * sizeof(dict->faces16[0]) ); + for ( i = 0; i < datasize/3; ++i ) { + dict->faces16[i] = (XP_CHAR16)data[(i*3)+2]; + } + + dict->countsAndValues = (XP_U8*)XP_MALLOC( mpool, dict->nFaces*2 ); + for ( i = 0; i < datasize/3; ++i ) { + dict->countsAndValues[i*2] = data[(i*3)]; + dict->countsAndValues[(i*2)+1] = data[(i*3)+1]; + } + + dict->bitmaps = (SpecialBitmaps*)XP_MALLOC( mpool, sizeof(SpecialBitmaps) ); + dict->bitmaps->largeBM = dict->bitmaps->largeBM = NULL; + + setStubbedSpecials( dict ); + + setBlankTile( dict ); + + return dict; +} /* make_subbed_dict */ + +#endif /* STUBBED_DICT */ + +static array_edge* +dict_super_edge_for_index( const DictionaryCtxt* dict, XP_U32 index ) +{ + array_edge* result; + + if ( index == 0 ) { + result = NULL; + } else { + XP_ASSERT( index < dict->numEdges ); +#ifdef NODE_CAN_4 + /* avoid long-multiplication lib call on Palm... */ + if ( dict->nodeSize == 3 ) { + index += (index << 1); + } else { + XP_ASSERT( dict->nodeSize == 4 ); + index <<= 2; + } +#else + index += (index << 1); +#endif + result = &dict->base[index]; + } + return result; +} /* dict_edge_for_index */ + +static array_edge* +dict_super_getTopEdge( const DictionaryCtxt* dict ) +{ + return dict->topEdge; +} /* dict_super_getTopEdge */ + +void +dict_super_init( DictionaryCtxt* ctxt ) +{ + /* subclass may change these later.... */ + ctxt->func_edge_for_index = dict_super_edge_for_index; + ctxt->func_dict_getTopEdge = dict_super_getTopEdge; + ctxt->func_dict_getShortName = dict_getName; +} /* dict_super_init */ + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/dictnry.h b/xwords4/common/dictnry.h new file mode 100644 index 000000000..660c91688 --- /dev/null +++ b/xwords4/common/dictnry.h @@ -0,0 +1,168 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __DICTNRY_H__ +#define __DICTNRY_H__ + +#include "comtypes.h" + +#include "dawg.h" +#include "model.h" +#include "mempool.h" + +#ifdef CPLUS +extern "C" { +#endif + +#define LETTER_NONE '\0' +#define IS_SPECIAL(face) (((XP_CHAR16)(face)) < 0x0020) + +typedef XP_U8 XP_LangCode; + +typedef XP_U16 XP_CHAR16; + +typedef enum { + BONUS_NONE, + BONUS_DOUBLE_LETTER, + BONUS_DOUBLE_WORD, + BONUS_TRIPLE_LETTER, + BONUS_TRIPLE_WORD, + + BONUS_LAST +} XWBonusType; + +typedef enum { + INTRADE_MW_TEXT = BONUS_LAST +} XWMiniTextType; + +typedef struct SpecialBitmaps { + XP_Bitmap largeBM; + XP_Bitmap smallBM; +} SpecialBitmaps; + + +struct DictionaryCtxt { + void (*destructor)( DictionaryCtxt* dict ); + + array_edge* (*func_edge_for_index)( const DictionaryCtxt* dict, XP_U32 index ); + array_edge* (*func_dict_getTopEdge)( const DictionaryCtxt* dict ); + const XP_UCHAR* (*func_dict_getShortName)( const DictionaryCtxt* dict ); + + array_edge* topEdge; + array_edge* base; /* the physical beginning of the dictionary; not + necessarily the entry point for search!! */ + XP_UCHAR* name; + XP_CHAR16* faces16; /* 16 for unicode */ + XP_U8* countsAndValues; + + SpecialBitmaps* bitmaps; + XP_UCHAR** chars; + +#ifdef TALL_FONTS + XP_LangCode langCode; +#endif + + XP_U8 nFaces; +#ifdef NODE_CAN_4 + XP_U8 nodeSize; + XP_Bool is_4_byte; +#endif + + XP_S8 blankTile; /* negative means there's no known blank */ +#ifdef DEBUG + XP_U32 numEdges; +#endif + MPSLOT +}; + +/* This is the datastructure that allows access to a DAWG in a + * platform-independent way. + */ +/* typedef struct DictionaryVtable { */ +/* XP_U16 (*m_getTileValue)( DictionaryCtxt* ctxt, CellTile tile ); */ +/* unsigned char (*m_getTileChar)( DictionaryCtxt* ctxt, CellTile tile, */ +/* XP_FontCode* fontCode ); */ +/* XP_U16 (*m_numTiles)( DictionaryCtxt* ctxt, Tile tile ); */ +/* XP_U16 (*m_numTileFaces)( DictionaryCtxt* ctxt ); */ +/* } DictionaryVtable; */ + + +/* struct DictionaryCtxt { */ +/* DictionaryVtable* vtable; */ +/* }; */ + +/* #define dict_getTileValue(dc,t) \ */ +/* (dc)->vtable->m_getTileValue((dc),(t)) */ + +/* #define dict_getTileChar(dc,t,fc) \ */ +/* (dc)->vtable->m_getTileChar((dc),(t),(fc)) */ + +/* #define dict_numTiles(dc,t) (dc)->vtable->m_numTiles((dc),(t)) */ + +/* #define dict_numTileFaces(dc) (dc)->vtable->m_numTileFaces(dc) */ + +#define dict_destroy(d) (*((d)->destructor))(d) +#define dict_edge_for_index(d, i) (*((d)->func_edge_for_index))((d), (i)) +#define dict_getTopEdge(d) (*((d)->func_dict_getTopEdge))(d) +#define dict_getShortName(d) (*((d)->func_dict_getShortName))(d) + + +XP_Bool dict_tilesAreSame( const DictionaryCtxt* dict1, + const DictionaryCtxt* dict2 ); + +XP_Bool dict_hasBlankTile( const DictionaryCtxt* dict ); +Tile dict_getBlankTile( const DictionaryCtxt* dict ); +XP_U16 dict_getTileValue( const DictionaryCtxt* ctxt, Tile tile ); +XP_U16 dict_numTiles( const DictionaryCtxt* ctxt, Tile tile ); +XP_U16 dict_numTileFaces( const DictionaryCtxt* ctxt ); + +XP_U16 dict_tilesToString( const DictionaryCtxt* ctxt, const Tile* tiles, + XP_U16 nTiles, XP_UCHAR* buf, XP_U16 bufSize ); +const XP_UCHAR* dict_getName( const DictionaryCtxt* ctxt ); + +Tile dict_tileForString( const DictionaryCtxt* dict, const XP_UCHAR* key ); + +XP_Bool dict_faceIsBitmap( const DictionaryCtxt* dict, Tile tile ); +XP_Bitmap dict_getFaceBitmap( const DictionaryCtxt* dict, Tile tile, + XP_Bool isLarge ); + +#ifdef TALL_FONTS +XP_LangCode dict_getLangCode( const DictionaryCtxt* dict ); +#endif + +void dict_writeToStream( const DictionaryCtxt* ctxt, XWStreamCtxt* stream ); +void dict_loadFromStream( DictionaryCtxt* dict, XWStreamCtxt* stream ); + + +/* These methods get "overridden" by subclasses. That is, they must be + implemented by each platform. */ + +#ifdef STUBBED_DICT +DictionaryCtxt* make_stubbed_dict( MPFORMAL_NOCOMMA ); +#endif + +/* To be called only by subclasses!!! */ +void dict_super_init( DictionaryCtxt* ctxt ); + + +#ifdef CPLUS +} +#endif + +#endif diff --git a/xwords4/common/dictnryp.h b/xwords4/common/dictnryp.h new file mode 100644 index 000000000..14f3079c9 --- /dev/null +++ b/xwords4/common/dictnryp.h @@ -0,0 +1,36 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4;-*- */ +/* + * Copyright 1997-2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef _DICTNRYP_H_ +#define _DICTNRYP_H_ + +#include "dictnry.h" + +#ifdef CPLUS +extern "C" { +#endif + +void setBlankTile( DictionaryCtxt* dctx ); + +#ifdef CPLUS +} +#endif + +#endif diff --git a/xwords4/common/dragdrpp.c b/xwords4/common/dragdrpp.c new file mode 100644 index 000000000..9995b3ce3 --- /dev/null +++ b/xwords4/common/dragdrpp.c @@ -0,0 +1,573 @@ +/* -*-mode: C; fill-column: 78; compile-command: "cd ../linux && make MEMDEBUG=TRUE"; -*- */ +/* + * Copyright 1997 - 2008 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef CPLUS +extern "C" { +#endif + +#include "dragdrpp.h" +#include "game.h" + +/* How many squares must scroll gesture take in to be recognized. */ +#define SCROLL_DRAG_THRESHHOLD 3 + +static XP_Bool dragDropContinueImpl( BoardCtxt* board, XP_U16 xx, XP_U16 yy, + BoardObjectType* onWhichP ); +static void invalDragObjRange( BoardCtxt* board, const DragObjInfo* from, + const DragObjInfo* to ); +#ifdef XWFEATURE_SEARCHLIMIT +static void invalHintRectDiffs( BoardCtxt* board, const DragObjInfo* cur, + const DragObjInfo* nxt ); +static void setLimitsFrom( const BoardCtxt* board, BdHintLimits* limits ); +#endif + +static void startScrollTimerIf( BoardCtxt* board ); + +XP_Bool +dragDropInProgress( const BoardCtxt* board ) +{ + const DragState* ds = &board->dragState; +/* LOG_RETURNF( "%d", ds->dragInProgress ); */ + return ds->dtype != DT_NONE; +} /* dragDropInProgress */ + +XP_Bool +dragDropHasMoved( const BoardCtxt* board ) +{ + return dragDropInProgress(board) && board->dragState.didMove; +} /* dragDropHasMoved */ + +static XP_Bool +ddStartBoard( BoardCtxt* board, XP_U16 xx, XP_U16 yy ) +{ + DragState* ds = &board->dragState; + XP_Bool found; + XP_Bool trayVisible; + XP_U16 col, row; + + found = coordToCell( board, xx, yy, &col, &row ); + XP_ASSERT( found ); + + trayVisible = board->trayVisState == TRAY_REVEALED; + if ( trayVisible && holdsPendingTile( board, col, row ) ) { + XP_U16 modelc, modelr; + XP_Bool ignore; + + ds->dtype = DT_TILE; + flipIf( board, col, row, &modelc, &modelr ); + + found = model_getTile( board->model, modelc, modelr, XP_TRUE, + board->selPlayer, &ds->tile, &ds->isBlank, + &ignore, &ignore ); + XP_ASSERT( found ); + } else { + /* If we're not dragging a tile, we can either drag the board (scroll) + or work on hint regions. Sometimes scrolling isn't possible. + Sometimes hint dragging is disabled. But if both are possible, + then the alt key determines it. I figure scrolling will be more + common than hint dragging when both are possible, but you can turn + hint dragging off, so if it's on that's probably what you want. */ + XP_Bool canScroll = board->lastVisibleRow < model_numRows(board->model); + if ( 0 ) { +#ifdef XWFEATURE_SEARCHLIMIT + } else if ( !board->gi->hintsNotAllowed && board->gi->allowHintRect + && trayVisible ) { + if ( !util_altKeyDown(board->util) ) { + ds->dtype = DT_HINTRGN; + } else if ( canScroll ) { + ds->dtype = DT_BOARD; + } +#endif + } else if ( canScroll ) { + ds->dtype = DT_BOARD; + } + } + ds->start.u.board.col = col; + ds->start.u.board.row = row; + + return ds->dtype != DT_NONE; +} /* ddStartBoard */ + +static XP_Bool +ddStartTray( BoardCtxt* board, XP_U16 x, XP_U16 y ) +{ + XP_Bool canDrag; + DragState* ds = &board->dragState; + + XP_Bool onDivider; + XP_S16 index = pointToTileIndex( board, x, y, &onDivider ); + canDrag = onDivider || index >= 0; + if ( canDrag ) { + if ( onDivider ) { + board->dividerInvalid = XP_TRUE; + ds->start.u.tray.index = board->selInfo->dividerLoc; + + ds->dtype = DT_DIVIDER; + } else { + Tile tile; + tile = model_getPlayerTile( board->model, board->selPlayer, index ); + ds->isBlank = + tile == dict_getBlankTile( model_getDictionary(board->model) ); + ds->tile = tile; + + ds->start.u.tray.index = index; + + /* during drag the moving tile is drawn as selected, so inval + currently selected tile. */ + board_invalTrayTiles( board, board->selInfo->traySelBits ); + + ds->dtype = DT_TILE; + } + } + + return canDrag; +} /* ddStartTray */ + +/* x and y, in board coordinates (not model, should the board be flipped), are + * already col,row (cells) in the board case, but real x/y (pixels) on the + * tray. +*/ +XP_Bool +dragDropStart( BoardCtxt* board, BoardObjectType obj, XP_U16 x, XP_U16 y ) +{ + XP_Bool result = XP_FALSE; + DragState* ds = &board->dragState; + if ( dragDropInProgress(board) ) { + XP_LOGF( "warning: starting drag while dragDropInProgress() true" ); + } + XP_MEMSET( ds, 0, sizeof(*ds) ); + + ds->start.obj = obj; + + if ( OBJ_BOARD == obj ) { + result = ddStartBoard( board, x, y ); + } else if ( OBJ_TRAY == obj ) { + result = ddStartTray( board, x, y ); + } else { + XP_ASSERT(0); + } + + if ( result ) { + ds->cur = ds->start; + invalDragObj( board, &ds->start ); + startScrollTimerIf( board ); + } + + return result; +} /* dragDropStart */ + +/* We're potentially dragging. If we're leaving one object, inval it. + * If we're entering another, inval it. Track where we are so we don't inval + * again. If we didn't really move, don't inval anything! + * + * Our overriding concern must be to preserve data integrity in the face of + * buggy OSes that may drop events (esp. MouseUp). So never own a tile: keep + * it attached either to the tray or the board. If the state of the board is + * to change as dragging happens, that's for the board (view) to display, not + * the model. So drawing has to be aware of drag-and-drop, but we don't + * change the model until the pen's actually lifted. If we never get the + * pen-up event at the worst the board's drawn incorrectly for a bit. + * + * Exception: since divider location is in the board rather than the model + * just change it every time we can. + */ +XP_Bool +dragDropContinue( BoardCtxt* board, XP_U16 xx, XP_U16 yy ) +{ + BoardObjectType ignore; + XP_ASSERT( dragDropInProgress(board) ); + + return dragDropContinueImpl( board, xx, yy, &ignore ); +} + +XP_Bool +dragDropEnd( BoardCtxt* board, XP_U16 xx, XP_U16 yy, XP_Bool* dragged ) +{ + DragState* ds = &board->dragState; + BoardObjectType newObj; + + XP_ASSERT( dragDropInProgress(board) ); + + (void)dragDropContinueImpl( board, xx, yy, &newObj ); + *dragged = ds->didMove; + + /* If we've dropped on something, put the tile there! Since we + don't remove it from its earlier location until it's dropped, + we need to specify where it's coming from. */ + if ( ds->dtype == DT_DIVIDER ) { + board->dividerInvalid = XP_TRUE; + /* nothing to do */ +#ifdef XWFEATURE_SEARCHLIMIT + } else if ( ds->dtype == DT_HINTRGN ) { + if ( OBJ_BOARD == newObj && ds->didMove ) { + XP_Bool makeActive = ds->start.u.board.row <= ds->cur.u.board.row; + board->selInfo->hasHintRect = makeActive; + if ( makeActive ) { + setLimitsFrom( board, &board->selInfo->limits ); + } else { + invalHintRectDiffs( board, &ds->cur, NULL ); + } + board_resetEngine( board ); + } else { + /* return it to whatever it was */ + invalHintRectDiffs( board, &ds->cur, NULL ); + invalCurHintRect( board, board->selPlayer ); + } +#endif + } else if ( ds->dtype == DT_BOARD ) { + /* do nothing */ + } else { + XP_U16 mod_startc, mod_startr; + + flipIf( board, ds->start.u.board.col, ds->start.u.board.row, + &mod_startc, &mod_startr ); + + if ( newObj == OBJ_TRAY ) { + if ( ds->start.obj == OBJ_BOARD ) { /* board->tray is pending */ + model_moveBoardToTray( board->model, board->selPlayer, + mod_startc, mod_startr, + ds->cur.u.tray.index ); + } else { + model_moveTileOnTray( board->model, board->selPlayer, + ds->start.u.tray.index, + ds->cur.u.tray.index ); + } + } else if ( (newObj == OBJ_BOARD) && + !cellOccupied( board, ds->cur.u.board.col, + ds->cur.u.board.row, XP_TRUE ) ) { + if ( ds->start.obj == OBJ_TRAY ) { + /* moveTileToBoard flips its inputs */ + (void)moveTileToBoard( board, ds->cur.u.board.col, + ds->cur.u.board.row, + ds->start.u.tray.index, EMPTY_TILE ); + } else if ( ds->start.obj == OBJ_BOARD ) { + XP_U16 mod_curc, mod_curr; + flipIf( board, ds->cur.u.board.col, ds->cur.u.board.row, + &mod_curc, &mod_curr ); + if ( model_moveTileOnBoard( board->model, board->selPlayer, + mod_startc, mod_startr, mod_curc, + mod_curr ) ) { + /* inval points tile in case score changed */ + board_invalTrayTiles( board, 1 << (MAX_TRAY_TILES-1) ); + } + } + } else { + /* We're returning it to start, so will be re-inserted in tray */ + if ( OBJ_TRAY == ds->start.obj ) { + invalTrayTilesAbove( board, ds->start.u.tray.index ); + } + } + + /* These may change appearance, e.g. from big tile to dropped cell. */ + invalDragObj( board, &ds->cur ); + invalDragObj( board, &ds->start ); + } + ds->dtype = DT_NONE; + + return XP_TRUE; +} /* dragDropEnd */ + +XP_Bool +dragDropGetBoardTile( const BoardCtxt* board, XP_U16* col, XP_U16* row ) +{ + const DragState* ds = &board->dragState; + XP_Bool found = ds->dtype == DT_TILE && ds->cur.obj == OBJ_BOARD; + if ( found ) { + *col = ds->cur.u.board.col; + *row = ds->cur.u.board.row; + } + return found; +} + +XP_Bool +dragDropIsBeingDragged( const BoardCtxt* board, XP_U16 col, XP_U16 row, + XP_Bool* isOrigin ) +{ + const DragState* ds = &board->dragState; + XP_Bool result = ds->dtype == DT_TILE && ds->cur.obj == OBJ_BOARD; + if ( result ) { + const DragState* ds = &board->dragState; + if ( (ds->cur.obj == OBJ_BOARD) && (ds->cur.u.board.col == col) + && (ds->cur.u.board.row == row) ) { + *isOrigin = XP_FALSE; + } else if ( (ds->start.obj == OBJ_BOARD) + && (ds->start.u.board.col == col) + && (ds->start.u.board.row == row) ) { + *isOrigin = XP_TRUE; + } else { + result = XP_FALSE; + } + + } + return result; +} + +void +dragDropGetTrayChanges( const BoardCtxt* board, XP_U16* rmvdIndx, + XP_U16* addedIndx ) +{ + const DragState* ds = &board->dragState; + *addedIndx = *rmvdIndx = MAX_TRAY_TILES; /* too big means ignore me */ + if ( ds->dtype == DT_TILE ) { + if ( OBJ_TRAY == ds->start.obj ) { + *rmvdIndx = ds->start.u.tray.index; + } + if ( OBJ_TRAY == ds->cur.obj ) { + *addedIndx = ds->cur.u.tray.index; + } + } +} + +#ifdef XWFEATURE_SEARCHLIMIT +XP_Bool +dragDropGetHintLimits( const BoardCtxt* board, BdHintLimits* limits ) +{ + XP_Bool result = board->dragState.dtype == DT_HINTRGN; + if ( result ) { + setLimitsFrom( board, limits ); + } + return result; +} +#endif + +XP_Bool +dragDropIsDividerDrag( const BoardCtxt* board ) +{ + return board->dragState.dtype == DT_DIVIDER; +} + +void +dragDropTileInfo( const BoardCtxt* board, Tile* tile, XP_Bool* isBlank ) +{ + const DragState* ds = &board->dragState; + XP_ASSERT( dragDropInProgress( board ) ); + XP_ASSERT( ds->dtype == DT_TILE ); + XP_ASSERT ( OBJ_BOARD == ds->start.obj || OBJ_TRAY == ds->start.obj ); + *tile = ds->tile; + *isBlank = ds->isBlank; +} + +#ifdef XWFEATURE_SEARCHLIMIT +static void +invalHintRectDiffs( BoardCtxt* board, const DragObjInfo* cur, + const DragObjInfo* nxt ) +{ + XP_U16 startCol = board->dragState.start.u.board.col; + XP_U16 startRow = board->dragState.start.u.board.row; + + /* These two regions will generally have close to 50% of their borders in + common. Try not to inval what needn't be inval'd. But at the moment + performance seems good enough without adding the complexity and new + bugs... + + The challenge in doing a smarter diff is that some squares need to be + invalidated even if they're part of the borders of both limits rects, + in particular if one is a corner of one and just a side of another. + One simple but expensive way of accounting for this would be to call + figureHintAtts() on each square in the borders of both rects and + invalidate when the hintAttributes aren't the same for both. That + misses an opportunity to avoid doing any calculations on those border + squares that clearly haven't changed at all. + */ + + invalCellRegion( board, startCol, startRow, cur->u.board.col, + cur->u.board.row ); + if ( !!nxt ) { + BdHintLimits limits; + setLimitsFrom( board, &limits ); + invalCellRegion( board, startCol, startRow, nxt->u.board.col, + nxt->u.board.row ); + } +} /* invalHintRectDiffs */ +#endif + +static XP_Bool +dragDropContinueImpl( BoardCtxt* board, XP_U16 xx, XP_U16 yy, + BoardObjectType* onWhichP ) +{ + XP_Bool moving = XP_FALSE; + DragObjInfo newInfo; + DragState* ds = &board->dragState; + + if ( !pointOnSomething( board, xx, yy, &newInfo.obj ) ) { + newInfo.obj = OBJ_NONE; + } + *onWhichP = newInfo.obj; + + if ( newInfo.obj == OBJ_BOARD ) { + (void)coordToCell( board, xx, yy, &newInfo.u.board.col, + &newInfo.u.board.row ); + } + + if ( ds->dtype == DT_DIVIDER ) { + if ( OBJ_TRAY == newInfo.obj ) { + XP_U16 newloc; + XP_U16 scale = board->trayScaleH; + xx -= board->trayBounds.left; + newloc = xx / scale; + if ( (xx % scale) > ((scale+board->dividerWidth)/2)) { + ++newloc; + } + moving = dividerMoved( board, newloc ); + } +#ifdef XWFEATURE_SEARCHLIMIT + } else if ( ds->dtype == DT_HINTRGN && newInfo.obj != OBJ_BOARD ) { + /* do nothing */ +#endif + } else if ( ds->dtype == DT_BOARD ) { + if ( newInfo.obj == OBJ_BOARD ) { + XP_S16 diff = newInfo.u.board.row - ds->cur.u.board.row; + diff /= SCROLL_DRAG_THRESHHOLD; + moving = adjustYOffset( board, diff ); + } + } else { + if ( newInfo.obj == OBJ_BOARD ) { + moving = (newInfo.u.board.col != ds->cur.u.board.col) + || (newInfo.u.board.row != ds->cur.u.board.row) + || (OBJ_TRAY == ds->cur.obj); + } else if ( newInfo.obj == OBJ_TRAY ) { + XP_Bool onDivider; + XP_S16 index = pointToTileIndex( board, xx, yy, &onDivider ); + if ( !onDivider ) { + if ( index < 0 ) { /* negative means onto empty part of + tray. Force left. */ + index = model_getNumTilesInTray( board->model, + board->selPlayer ); + if ( OBJ_TRAY == ds->start.obj ) { + --index; /* dragging right into space */ + } + } + moving = (OBJ_BOARD == ds->cur.obj) + || (index != ds->cur.u.tray.index); + if ( moving ) { + newInfo.u.tray.index = index; + } + } + } + + if ( moving ) { + if ( ds->dtype == DT_TILE ) { + invalDragObjRange( board, &ds->cur, &newInfo ); +#ifdef XWFEATURE_SEARCHLIMIT + } else if ( ds->dtype == DT_HINTRGN ) { + invalHintRectDiffs( board, &ds->cur, &newInfo ); + if ( !ds->didMove ) { /* first time through */ + invalCurHintRect( board, board->selPlayer ); + } +#endif + } + + XP_MEMCPY( &ds->cur, &newInfo, sizeof(ds->cur) ); + startScrollTimerIf( board ); + } + } + + if ( moving ) { + if ( !ds->didMove ) { + /* This is the first time we've moved!!! Kill any future timers, + and if there's a window up kill it.*/ + board->penTimerFired = XP_FALSE; + if ( valHintMiniWindowActive( board ) ) { + hideMiniWindow( board, XP_TRUE, MINIWINDOW_VALHINT ); + } + } + ds->didMove = XP_TRUE; + } + + return moving; +} /* dragDropContinueImpl */ + +static void +invalDragObjRange( BoardCtxt* board, const DragObjInfo* from, + const DragObjInfo* to ) +{ + invalDragObj( board, from ); + if ( NULL != to ) { + invalDragObj( board, to ); + + if ( (OBJ_TRAY == from->obj) && (OBJ_TRAY == to->obj) ) { + invalTrayTilesBetween( board, from->u.tray.index, + to->u.tray.index ); + } else if ( OBJ_TRAY == from->obj ) { + invalTrayTilesAbove( board, from->u.tray.index ); + } else if ( OBJ_TRAY == to->obj ) { + invalTrayTilesAbove( board, to->u.tray.index ); + } + } +} + +#ifdef XWFEATURE_SEARCHLIMIT +static void +setLimitsFrom( const BoardCtxt* board, BdHintLimits* limits ) +{ + const DragState* ds = &board->dragState; + limits->left = XP_MIN( ds->start.u.board.col, ds->cur.u.board.col ); + limits->right = XP_MAX( ds->start.u.board.col, ds->cur.u.board.col ); + limits->top = XP_MIN( ds->start.u.board.row, ds->cur.u.board.row ); + limits->bottom = XP_MAX( ds->start.u.board.row, ds->cur.u.board.row ); +} +#endif + +static XP_Bool +scrollTimerProc( void* closure, XWTimerReason XP_UNUSED_DBG(why) ) +{ + XP_Bool draw = XP_FALSE; + BoardCtxt* board = (BoardCtxt*)closure; + DragState* ds = &board->dragState; + XP_ASSERT( why == TIMER_PENDOWN ); + + if ( ds->scrollTimerSet ) { + XP_S16 change; + ds->scrollTimerSet = XP_FALSE; + if ( onBorderCanScroll( board, ds->cur.u.board.row, &change ) ) { + invalDragObj( board, &ds->cur ); + ds->cur.u.board.row += (change >0 ? 1 : -1); + if ( checkScrollCell( board, ds->cur.u.board.col, + ds->cur.u.board.row ) ) { + board_draw( board ); /* may fail, e.g. on wince */ + startScrollTimerIf( board ); + draw = XP_TRUE; + } + } + } + return draw; +} /* scrollTimerProc */ + +static void +startScrollTimerIf( BoardCtxt* board ) +{ + DragState* ds = &board->dragState; + + if ( (ds->dtype == DT_TILE) && (ds->cur.obj == OBJ_BOARD) ) { + XP_S16 ignore; + if ( onBorderCanScroll( board, ds->cur.u.board.row, &ignore ) ) { + util_setTimer( board->util, TIMER_PENDOWN, 0, + scrollTimerProc, (void*) board ); + ds->scrollTimerSet = XP_TRUE; + } else { + /* ignore if we've moved off */ + ds->scrollTimerSet = XP_FALSE; + } + } +} /* startScrollTimerIf */ + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/dragdrpp.h b/xwords4/common/dragdrpp.h new file mode 100644 index 000000000..cfdc869b3 --- /dev/null +++ b/xwords4/common/dragdrpp.h @@ -0,0 +1,61 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2008 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _DRAGDRPP_H_ +#define _DRAGDRPP_H_ + + +#include "boardp.h" + + +#ifdef CPLUS +extern "C" { +#endif + +XP_Bool dragDropInProgress( const BoardCtxt* board ); +XP_Bool dragDropHasMoved( const BoardCtxt* board ); + +XP_Bool dragDropStart( BoardCtxt* board, BoardObjectType obj, + XP_U16 xx, XP_U16 yy ); +XP_Bool dragDropContinue( BoardCtxt* board, XP_U16 xx, XP_U16 yy ); +XP_Bool dragDropEnd( BoardCtxt* board, XP_U16 xx, XP_U16 yy, XP_Bool* dragged ); + +XP_Bool dragDropGetBoardTile( const BoardCtxt* board, XP_U16* col, XP_U16* row ); +XP_Bool dragDropIsBeingDragged( const BoardCtxt* board, XP_U16 col, XP_U16 row, + XP_Bool* isOrigin ); + +/* return locations (0-based indices from left) in tray where a drag has added + * and removed a tile. Index larger than MAX_TRAY_TILES means invalid: don't + * use. + */ +void dragDropGetTrayChanges( const BoardCtxt* board, XP_U16* rmvdIndx, + XP_U16* addedIndx ); +XP_Bool dragDropIsDividerDrag( const BoardCtxt* board ); +#ifdef XWFEATURE_SEARCHLIMIT +XP_Bool dragDropGetHintLimits( const BoardCtxt* board, BdHintLimits* limits ); +#endif + + +void dragDropTileInfo( const BoardCtxt* board, Tile* tile, XP_Bool* isBlank ); + +#ifdef CPLUS +} +#endif + +#endif /* __DRAGDRPP_H_ */ diff --git a/xwords4/common/draw.c b/xwords4/common/draw.c new file mode 100644 index 000000000..8221e6939 --- /dev/null +++ b/xwords4/common/draw.c @@ -0,0 +1,366 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2003 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef DRAW_WITH_PRIMITIVES + +#include "draw.h" +#include "xptypes.h" + +static void +insetRect( XP_Rect* r, XP_S16 amt ) +{ + r->top += amt; + r->left += amt; + amt *= 2; + r->width -= amt; + r->height -= amt; +} /* insetRect */ + +static void +getRemText( XP_UCHAR* buf, XP_U16 bufSize, XP_S16 nTilesLeft ) +{ + if ( nTilesLeft > 0 ) { + XP_SNPRINTF( buf, bufSize, "rem: %d", nTilesLeft ); + } else { + buf[0] = '\0'; + } +} /* getRemText */ + +static void +default_draw_measureRemText( DrawCtx* dctx, const XP_Rect* XP_UNUSED(r), + XP_S16 nTilesLeft, + XP_U16* widthP, XP_U16* heightP ) +{ + XP_U16 width, height; + + if ( nTilesLeft > 0 ) { + XP_UCHAR buf[20]; + getRemText( buf, sizeof(buf), nTilesLeft ); + draw_measureText( dctx, buf, &width, &height ); + } else { + width = height = 0; + } + + *widthP = width; + *heightP = height; +} /* default_draw_measureRemText */ + +static void +default_draw_drawRemText( DrawCtx* dctx, const XP_Rect* XP_UNUSED(rInner), + const XP_Rect* rOuter, XP_S16 nTilesLeft ) +{ + XP_Rect oldClip; + XP_UCHAR buf[10]; + + getRemText( buf, sizeof(buf), nTilesLeft ); + + draw_setClip( dctx, rOuter, &oldClip ); + draw_drawString( dctx, buf, rOuter->left, rOuter->top ); + draw_setClip( dctx, &oldClip, NULL ); +} /* default_draw_drawRemText */ + +static void +formatScore( XP_UCHAR* buf, XP_U16 bufSize, const DrawScoreInfo* dsi ) +{ + XP_UCHAR remBuf[10]; + XP_UCHAR* selStr; + + if ( dsi->selected ) { + selStr = "*"; + } else { + selStr = ""; + } + + if ( dsi->nTilesLeft >= 0 ) { + XP_SNPRINTF( remBuf, sizeof(remBuf), ":%d", dsi->nTilesLeft ); + } else { + remBuf[0] = '\0'; + } + + XP_SNPRINTF( buf, bufSize, "%s%d%s%s", selStr, dsi->totalScore, + remBuf, selStr ); +} /* formatScore */ + +static void +default_draw_measureScoreText( DrawCtx* dctx, const XP_Rect* XP_UNUSED(r), + const DrawScoreInfo* dsi, + XP_U16* widthP, XP_U16* heightP ) +{ + XP_UCHAR buf[20]; + formatScore( buf, sizeof(buf), dsi ); + draw_measureText( dctx, buf, widthP, heightP ); +} /* default_draw_measureScoreText */ + +static void +default_draw_score_drawPlayer( DrawCtx* dctx, + const XP_Rect* rInner, const XP_Rect* rOuter, + const DrawScoreInfo* dsi ) +{ + XP_Rect oldClip; + XP_UCHAR buf[20]; + + draw_setClip( dctx, rInner, &oldClip ); + draw_clearRect( dctx, rOuter ); + + formatScore( buf, sizeof(buf), dsi ); + draw_drawString( dctx, buf, rInner->left, rInner->top ); + + draw_setClip( dctx, &oldClip, NULL ); +} /* default_draw_score_drawPlayer */ + +static XP_Bool +default_draw_drawCell( DrawCtx* dctx, const XP_Rect* rect, + const XP_UCHAR* text, + const XP_Bitmap bitmap, + Tile XP_UNUSED(tile), XP_S16 XP_UNUSED(owner), + XWBonusType bonus, HintAtts XP_UNUSED(hintAtts), + CellFlags flags ) +{ + XP_Rect oldClip; + XP_Rect inset = *rect; + insetRect( &inset, 1 ); + + draw_setClip( dctx, rect, &oldClip ); + + draw_clearRect( dctx, rect ); + + if ( !!text && text[0] != 0 ) { + draw_drawString( dctx, text, inset.left, inset.top ); + } else if ( !!bitmap ) { + draw_drawBitmap( dctx, bitmap, inset.left, inset.top ); + } else if ( bonus != BONUS_NONE ) { + XP_UCHAR* bstr; + switch( bonus ) { + case BONUS_DOUBLE_LETTER: + bstr = "*"; + break; + case BONUS_DOUBLE_WORD: + bstr = "%"; + break; + case BONUS_TRIPLE_LETTER: + bstr = "#"; + break; + case BONUS_TRIPLE_WORD: + bstr = "@"; + break; + default: + XP_ASSERT(0); + break; + } + draw_drawString( dctx, bstr, inset.left, inset.top ); + } + + if ( 0 != (flags & CELL_HIGHLIGHT) ) { + draw_invertRect( dctx, &inset ); + } + + draw_frameRect( dctx, rect ); + draw_setClip( dctx, &oldClip, NULL ); + + return XP_TRUE; +} /* default_draw_drawCell */ + +static void +default_draw_drawBoardArrow( DrawCtx* dctx, const XP_Rect* rect, + XWBonusType XP_UNUSED(bonus), XP_Bool vert, + HintAtts XP_UNUSED(hintAtts), + CellFlags XP_UNUSED(flags) ) +{ + XP_Rect oldClip; + XP_UCHAR* arrow; + + if ( vert ) { + arrow = "|"; + } else { + arrow = "-"; + } + + draw_setClip( dctx, rect, &oldClip ); + draw_clearRect( dctx, rect ); + draw_frameRect( dctx, rect ); + draw_drawString( dctx, arrow, rect->left+1, rect->top+1 ); + draw_setClip( dctx, &oldClip, NULL ); +} /* default_draw_drawBoardArrow */ + +static void +default_draw_drawTile( DrawCtx* dctx, const XP_Rect* rect, + const XP_UCHAR* text, + const XP_Bitmap bitmap, + XP_S16 val, CellFlags flags ) +{ + XP_Rect oldClip; + XP_Rect inset = *rect; + + draw_setClip( dctx, rect, &oldClip ); + draw_clearRect( dctx, rect ); + + draw_frameRect( dctx, rect ); + + if ( 0 != (flags & CELL_HIGHLIGHT) ) { + insetRect( &inset, 1 ); + draw_frameRect( dctx, &inset ); + insetRect( &inset, 1 ); + } else { + insetRect( &inset, 2 ); + } + + if ( !!text && text[0] != '\0' ) { + draw_drawString( dctx, text, inset.left, inset.top ); + } else if ( !!bitmap ) { + draw_drawBitmap( dctx, bitmap, inset.left, inset.top ); + } + + if ( val >= 0 ) { + XP_UCHAR sbuf[4]; + XP_U16 width, height; + XP_U16 x, y; + + XP_SNPRINTF( sbuf, sizeof(sbuf), "%d", val ); + draw_measureText( dctx, sbuf, &width, &height ); + + x = inset.left + inset.width - width; + y = inset.top + inset.height - height; + draw_drawString( dctx, sbuf, x, y ); + } + + draw_setClip( dctx, &oldClip, NULL ); +} /* default_draw_drawTile */ + +static void +default_draw_drawTileBack( DrawCtx* dctx, const XP_Rect* rect, + CellFlags XP_UNUSED(flags) ) +{ + default_draw_drawTile( dctx, rect, "?", NULL, -1, XP_FALSE ); +} /* default_draw_drawTileBack */ + +static void +default_draw_drawTrayDivider( DrawCtx* dctx, const XP_Rect* rect, + CellFlags XP_UNUSED(flags)) +{ + XP_Rect r = *rect; + draw_clearRect( dctx, rect ); + if ( r.width > 2 ) { + r.width -= 2; + r.left += 1; + } + draw_frameRect( dctx, &r ); +} /* default_draw_drawTrayDivider */ + +static void +default_draw_score_pendingScore( DrawCtx* dctx, + const XP_Rect* rect, + XP_S16 score, + XP_U16 XP_UNUSED(playerNum), + CellFlags XP_UNUSED(flags) ) +{ + XP_UCHAR buf[5]; + XP_Rect oldClip; + XP_Rect r; + XP_U16 width, height; + XP_UCHAR* stxt; + + draw_setClip( dctx, rect, &oldClip ); + + XP_MEMCPY( &r, rect, sizeof(r) ); + ++r.left; /* don't erase neighbor's border */ + --r.width; + draw_clearRect( dctx, &r ); + + draw_drawString( dctx, "pts", r.left, r.top ); + + if ( score >= 0 ) { + XP_SNPRINTF( buf, sizeof(buf), "%d", score ); + stxt = buf; + } else { + stxt = "???"; + } + draw_measureText( dctx, stxt, &width, &height ); + draw_drawString( dctx, stxt, r.left, r.top + r.height - height ); + + draw_setClip( dctx, &oldClip, NULL ); +} /* default_draw_score_pendingScore */ + +static const XP_UCHAR* +default_draw_getMiniWText( DrawCtx* XP_UNUSED(dctx), XWMiniTextType textHint ) +{ + char* str; + + switch( textHint ) { + case BONUS_DOUBLE_LETTER: + str = "Double letter"; break; + case BONUS_DOUBLE_WORD: + str = "Double word"; break; + case BONUS_TRIPLE_LETTER: + str = "Triple letter"; break; + case BONUS_TRIPLE_WORD: + str = "Triple word"; break; + case INTRADE_MW_TEXT: + str = "Trading tiles;\nclick D when done"; break; + default: + XP_ASSERT( XP_FALSE ); + } + return str; +} /* default_draw_getMiniWText */ + +static void +default_draw_measureMiniWText( DrawCtx* dctx, const XP_UCHAR* textP, + XP_U16* widthP, XP_U16* heightP ) +{ + draw_measureText( dctx, textP, widthP, heightP ); + + /* increase for frame */ + *widthP += 2; + *heightP += 2; +} /* default_draw_measureMiniWText */ + +static void +default_draw_drawMiniWindow( DrawCtx* dctx, const XP_UCHAR* text, + const XP_Rect* rect, void** XP_UNUSED(closure) ) +{ + XP_Rect oldClip; + + draw_setClip( dctx, rect, &oldClip ); + + draw_clearRect( dctx, rect ); + draw_frameRect( dctx, rect ); + draw_drawString( dctx, text, rect->left+1, rect->top+1 ); + + draw_setClip( dctx, &oldClip, NULL ); +} /* default_draw_drawMiniWindow */ + +void +InitDrawDefaults( DrawCtxVTable* vtable ) +{ + SET_VTABLE_ENTRY( vtable, draw_measureRemText, default ); + SET_VTABLE_ENTRY( vtable, draw_drawRemText, default ); + SET_VTABLE_ENTRY( vtable, draw_measureScoreText, default ); + SET_VTABLE_ENTRY( vtable, draw_score_drawPlayer, default ); + SET_VTABLE_ENTRY( vtable, draw_drawCell, default ); + SET_VTABLE_ENTRY( vtable, draw_drawBoardArrow, default ); + SET_VTABLE_ENTRY( vtable, draw_drawTile, default ); + SET_VTABLE_ENTRY( vtable, draw_drawTileBack, default ); + SET_VTABLE_ENTRY( vtable, draw_drawTrayDivider, default ); + SET_VTABLE_ENTRY( vtable, draw_score_pendingScore, default ); + + SET_VTABLE_ENTRY( vtable, draw_getMiniWText, default ); + SET_VTABLE_ENTRY( vtable, draw_measureMiniWText, default ); + SET_VTABLE_ENTRY( vtable, draw_drawMiniWindow, default ); +} /* InitDrawDefaults */ + +#endif diff --git a/xwords4/common/draw.h b/xwords4/common/draw.h new file mode 100644 index 000000000..520b6fdb2 --- /dev/null +++ b/xwords4/common/draw.h @@ -0,0 +1,309 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2007 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _DRAW_H_ +#define _DRAW_H_ + +#include "comtypes.h" +#include "xptypes.h" +#include "model.h" + +/* typedef struct DrawCtx DrawCtx; */ + + +typedef XP_Bool (*LastScoreCallback)( void* closure, XP_S16 player, + XP_UCHAR* expl, XP_U16* explLen ); + +typedef enum { + CELL_NONE = 0x00 + , CELL_ISBLANK = 0x01 + , CELL_HIGHLIGHT = 0x02 + , CELL_ISSTAR = 0x04 + , CELL_ISCURSOR = 0x08 + , CELL_ISEMPTY = 0x10 /* of a tray tile slot */ + , CELL_VALHIDDEN = 0x20 /* show letter only, not value */ + , CELL_DRAGSRC = 0x40 /* where drag originated */ + , CELL_DRAGCUR = 0x80 /* where drag is now */ + , CELL_ALL = 0xFF +} CellFlags; + +typedef struct DrawScoreInfo { + LastScoreCallback lsc; + void* lscClosure; + XP_UCHAR* name; + XP_U16 playerNum; + XP_S16 totalScore; + XP_S16 nTilesLeft; /* < 0 means don't use */ + CellFlags flags; + XP_Bool isTurn; + XP_Bool selected; + XP_Bool isRemote; + XP_Bool isRobot; +} DrawScoreInfo; + +enum HINT_ATTS { HINT_BORDER_NONE = 0, + HINT_BORDER_LEFT = 1, + HINT_BORDER_RIGHT = 2, + HINT_BORDER_TOP = 4, + HINT_BORDER_BOTTOM = 8, + HINT_BORDER_CENTER = 0x10 +}; +typedef XP_UCHAR HintAtts; +#define HINT_BORDER_EDGE \ + (HINT_BORDER_LEFT|HINT_BORDER_RIGHT|HINT_BORDER_TOP|HINT_BORDER_BOTTOM) + + +/* Platform-supplied draw functions are either staticly linked, or called via + * a vtable. If you want static linking, define DRAW_LINK_DIRECT via a -D + * option to your compiler, and use the DRAW_FUNC_NAME macro to make your + * names match what's declared here. Otherwise, if DRAW_LINK_DIRECT is not + * defined, you need to create and populate a vtable. See any of the existing + * platforms' draw implementations for examples. + * + * As to how to choose, static linking makes the binary a tiny bit smaller, + * but vtables give more flexibilty. For example, Palm uses them to support + * both black-and-white and color screens, while linux on linux separate + * vtable are created to allow a runtime choice between gtk and ncurses + * drawing. + */ +#ifdef DRAW_LINK_DIRECT +# define DRAW_FUNC_NAME(name) linked##_draw_##name +# define DRAW_VTABLE_NAME DRAW_FUNC_NAME +#else +# define DRAW_VTABLE_NAME(name) (*m_draw_ ## name) +#endif + +#ifdef DRAW_LINK_DIRECT +typedef void DrawCtxVTable; +#else +typedef struct DrawCtxVTable { +#endif + +#ifdef DRAW_WITH_PRIMITIVES + void DRAW_VTABLE_NAME(setClip)( DrawCtx* dctx, const XP_Rect* newClip, + const XP_Rect* oldClip ); + void DRAW_VTABLE_NAME(frameRect)( DrawCtx* dctx, const XP_Rect* rect ); + void DRAW_VTABLE_NAME(invertRect)( DrawCtx* dctx, const XP_Rect* rect ); + void DRAW_VTABLE_NAME(drawString)( DrawCtx* dctx, const XP_UCHAR* str, + XP_U16 x, XP_U16 y ); + void DRAW_VTABLE_NAME(drawBitmap)( DrawCtx* dctx, const XP_Bitmap bm, + XP_U16 x, XP_U16 y ); + void DRAW_VTABLE_NAME(measureText)( DrawCtx* dctx, const XP_UCHAR* buf, + XP_U16* widthP, XP_U16* heightP ); +#endif + + void DRAW_VTABLE_NAME(destroyCtxt) ( DrawCtx* dctx ); + + void DRAW_VTABLE_NAME(dictChanged)( DrawCtx* dctx, + const DictionaryCtxt* dict ); + + XP_Bool DRAW_VTABLE_NAME(boardBegin) ( DrawCtx* dctx, + const XP_Rect* rect, + DrawFocusState dfs ); + void DRAW_VTABLE_NAME(objFinished)( DrawCtx* dctx, BoardObjectType typ, + const XP_Rect* rect, + DrawFocusState dfs ); + + /* rect is not const: set by callee */ + XP_Bool DRAW_VTABLE_NAME(vertScrollBoard) (DrawCtx* dctx, XP_Rect* rect, + XP_S16 dist, DrawFocusState dfs ); + + XP_Bool DRAW_VTABLE_NAME(trayBegin) ( DrawCtx* dctx, const XP_Rect* rect, + XP_U16 owner, + DrawFocusState dfs ); + void DRAW_VTABLE_NAME(measureRemText) ( DrawCtx* dctx, const XP_Rect* r, + XP_S16 nTilesLeft, + XP_U16* width, XP_U16* height ); + void DRAW_VTABLE_NAME(drawRemText) (DrawCtx* dctx, const XP_Rect* rInner, + const XP_Rect* rOuter, + XP_S16 nTilesLeft, XP_Bool focussed ); + + void DRAW_VTABLE_NAME(scoreBegin) ( DrawCtx* dctx, const XP_Rect* rect, + XP_U16 numPlayers, DrawFocusState dfs ); + void DRAW_VTABLE_NAME(measureScoreText) ( DrawCtx* dctx, + const XP_Rect* r, + const DrawScoreInfo* dsi, + XP_U16* width, XP_U16* height ); + void DRAW_VTABLE_NAME(score_drawPlayer) ( DrawCtx* dctx, + const XP_Rect* rInner, + const XP_Rect* rOuter, + const DrawScoreInfo* dsi ); + + void DRAW_VTABLE_NAME(score_pendingScore) ( DrawCtx* dctx, + const XP_Rect* rect, + XP_S16 score, + XP_U16 playerNum, + CellFlags flags ); + + void DRAW_VTABLE_NAME(drawTimer) ( DrawCtx* dctx, const XP_Rect* rInner, + const XP_Rect* rOuter, + XP_U16 player, XP_S16 secondsLeft ); + + XP_Bool DRAW_VTABLE_NAME(drawCell) ( DrawCtx* dctx, const XP_Rect* rect, + /* at least one of these two will be + null */ + const XP_UCHAR* text, + const XP_Bitmap bitmap, + Tile tile, + XP_S16 owner, /* -1 means don't use */ + XWBonusType bonus, HintAtts hintAtts, + CellFlags flags ); + + void DRAW_VTABLE_NAME(invertCell) ( DrawCtx* dctx, const XP_Rect* rect ); + + void DRAW_VTABLE_NAME(drawTile) ( DrawCtx* dctx, const XP_Rect* rect, + /* at least 1 of these two will be null*/ + const XP_UCHAR* text, + const XP_Bitmap bitmap, + XP_S16 val, CellFlags flags ); +#ifdef POINTER_SUPPORT + void DRAW_VTABLE_NAME(drawTileMidDrag) ( DrawCtx* dctx, const XP_Rect* rect, + /* at least 1 of these two will be null*/ + const XP_UCHAR* text, + const XP_Bitmap bitmap, + XP_S16 val, XP_U16 owner, + CellFlags flags ); +#endif + void DRAW_VTABLE_NAME(drawTileBack) ( DrawCtx* dctx, const XP_Rect* rect, + CellFlags flags ); + void DRAW_VTABLE_NAME(drawTrayDivider) ( DrawCtx* dctx, const XP_Rect* rect, + CellFlags flags ); + + void DRAW_VTABLE_NAME(clearRect) ( DrawCtx* dctx, const XP_Rect* rect ); + + void DRAW_VTABLE_NAME(drawBoardArrow) ( DrawCtx* dctx, + const XP_Rect* rect, + XWBonusType bonus, XP_Bool vert, + HintAtts hintAtts, + CellFlags flags); + const XP_UCHAR* DRAW_VTABLE_NAME(getMiniWText) ( DrawCtx* dctx, + XWMiniTextType textHint ); + void DRAW_VTABLE_NAME(measureMiniWText) ( DrawCtx* dctx, const XP_UCHAR* textP, + XP_U16* width, XP_U16* height ); + void DRAW_VTABLE_NAME(drawMiniWindow)( DrawCtx* dctx, const XP_UCHAR* text, + const XP_Rect* rect, void** closure ); +#ifndef DRAW_LINK_DIRECT +} DrawCtxVTable; /* */ +#endif + +struct DrawCtx { + DrawCtxVTable* vtable; +}; + +/* Franklin's compiler is too old to support __VA_ARGS__... */ +#ifdef DRAW_LINK_DIRECT +# define CALL_DRAW_NAME0(name,dc) linked##_draw_##name(dc) +# define CALL_DRAW_NAME1(name,dc,p1) linked##_draw_##name(dc,(p1)) +# define CALL_DRAW_NAME2(name,dc,p1,p2) \ + linked##_draw_##name(dc,(p1),(p2)) +# define CALL_DRAW_NAME3(name,dc,p1,p2,p3) \ + linked##_draw_##name(dc,(p1),(p2),(p3)) +# define CALL_DRAW_NAME4(name,dc,p1,p2,p3,p4) \ + linked##_draw_##name(dc,(p1),(p2),(p3),(p4)) +# define CALL_DRAW_NAME5(name,dc,p1,p2,p3,p4,p5) \ + linked##_draw_##name(dc,(p1),(p2),(p3),(p4),(p5)) +# define CALL_DRAW_NAME6(name,dc,p1,p2,p3,p4,p5,p6) \ + linked##_draw_##name(dc,(p1),(p2),(p3),(p4),(p5),(p6)) +# define CALL_DRAW_NAME8(name,dc,p1,p2,p3,p4,p5,p6,p7,p8) \ + linked##_draw_##name(dc,(p1),(p2),(p3),(p4),(p5),(p6),(p7),(p8)) +# define CALL_DRAW_NAME10(name,dc,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10) \ + linked##_draw_##name(dc,(p1),(p2),(p3),(p4),(p5),(p6),(p7),\ + (p8),(p9),(p10)) +#else +# define CALL_DRAW_NAME0(name,dc) ((dc)->vtable->m_draw_##name)(dc) +# define CALL_DRAW_NAME1(name,dc,p1) ((dc)->vtable->m_draw_##name)(dc,(p1)) +# define CALL_DRAW_NAME2(name,dc,p1,p2) \ + ((dc)->vtable->m_draw_##name)(dc,(p1),(p2)) +# define CALL_DRAW_NAME3(name,dc,p1,p2,p3) \ + ((dc)->vtable->m_draw_##name)(dc,(p1),(p2),(p3)) +# define CALL_DRAW_NAME4(name,dc,p1,p2,p3,p4) \ + ((dc)->vtable->m_draw_##name)(dc,(p1),(p2),(p3),(p4)) +# define CALL_DRAW_NAME5(name,dc,p1,p2,p3,p4,p5) \ + ((dc)->vtable->m_draw_##name)(dc,(p1),(p2),(p3),(p4),(p5)) +# define CALL_DRAW_NAME6(name,dc,p1,p2,p3,p4,p5,p6) \ + ((dc)->vtable->m_draw_##name)(dc,(p1),(p2),(p3),(p4),(p5),(p6)) +# define CALL_DRAW_NAME8(name,dc,p1,p2,p3,p4,p5,p6,p7,p8) \ + ((dc)->vtable->m_draw_##name)(dc,(p1),(p2),(p3),(p4),(p5),(p6),(p7),\ + (p8)) +# define CALL_DRAW_NAME10(name,dc,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10) \ + ((dc)->vtable->m_draw_##name)(dc,(p1),(p2),(p3),(p4),(p5),(p6),(p7),\ + (p8),(p9),(p10)) +#endif + +#define draw_destroyCtxt(dc) CALL_DRAW_NAME0(destroyCtxt, dc) +#define draw_dictChanged( dc, d ) CALL_DRAW_NAME1(dictChanged, (dc), (d)) +#define draw_boardBegin( dc,r,f ) CALL_DRAW_NAME2(boardBegin, dc, r,f) +#define draw_objFinished( dc, t, r, d ) CALL_DRAW_NAME3(objFinished, (dc), (t), (r), (d)) +#define draw_trayBegin( dc, r, o, f ) CALL_DRAW_NAME3(trayBegin,dc, r, o, f) +#define draw_vertScrollBoard( dc, r, d, f ) \ + CALL_DRAW_NAME3(vertScrollBoard, (dc),(r),(d),(f)) +#define draw_scoreBegin( dc, r, t, f ) \ + CALL_DRAW_NAME3( scoreBegin,(dc), (r), (t), (f)) +#define draw_measureRemText( dc, r, n, wp, hp ) \ + CALL_DRAW_NAME4(measureRemText, (dc), (r), (n), (wp), (hp) ) +#define draw_drawRemText( dc, ri, ro, n, f ) \ + CALL_DRAW_NAME4(drawRemText, (dc), (ri), (ro), (n), (f) ) +#define draw_measureScoreText(dc,r,dsi,wp,hp) \ + CALL_DRAW_NAME4(measureScoreText,(dc),(r),(dsi),(wp),(hp)) +#define draw_score_drawPlayer(dc, ri, ro, dsi) \ + CALL_DRAW_NAME3(score_drawPlayer,(dc),(ri),(ro),(dsi)) +#define draw_score_pendingScore(dc, r, s, p, f ) \ + CALL_DRAW_NAME4(score_pendingScore,(dc), (r), (s), (p), (f)) +#define draw_drawTimer( dc, ri, ro, plyr, sec ) \ + CALL_DRAW_NAME4(drawTimer,(dc),(ri),(ro),(plyr),(sec)) +#define draw_drawCell( dc, rect, txt, bmap, t, o, bon, hi, f ) \ + CALL_DRAW_NAME8(drawCell,(dc),(rect),(txt),(bmap),(t),(o),(bon),(hi),\ + (f)) +#define draw_invertCell( dc, rect ) CALL_DRAW_NAME1(invertCell,(dc),(rect)) +#define draw_drawTile( dc, rect, text, bmp, val, hil ) \ + CALL_DRAW_NAME5(drawTile,(dc),(rect),(text),(bmp),(val),(hil)) +#ifdef POINTER_SUPPORT +#define draw_drawTileMidDrag( dc, rect, text, bmp, val, ownr, hil ) \ + CALL_DRAW_NAME6(drawTileMidDrag,(dc),(rect),(text),(bmp),(val),(ownr),(hil)) +#endif /* POINTER_SUPPORT */ +#define draw_drawTileBack( dc, rect, f ) \ + CALL_DRAW_NAME2(drawTileBack, (dc), (rect), (f) ) +#define draw_drawTrayDivider( dc, rect, s ) \ + CALL_DRAW_NAME2(drawTrayDivider,(dc),(rect), (s)) +#define draw_clearRect( dc, rect ) CALL_DRAW_NAME1(clearRect,(dc),(rect)) +#define draw_drawBoardArrow( dc, r, b, v, h, f ) \ + CALL_DRAW_NAME5(drawBoardArrow,(dc),(r),(b), (v), (h), (f)) + +#define draw_getMiniWText( dc, b ) CALL_DRAW_NAME1(getMiniWText, (dc),(b) ) +#define draw_measureMiniWText( dc, t, wp, hp) \ + CALL_DRAW_NAME3(measureMiniWText, (dc),(t), (wp), (hp) ) +#define draw_drawMiniWindow( dc, t, r, c ) \ + CALL_DRAW_NAME3(drawMiniWindow, (dc), (t), (r), (c) ) + +#ifdef DRAW_WITH_PRIMITIVES +# define draw_setClip( dc, rn, ro ) CALL_DRAW_NAME2(setClip, (dc), (rn), (ro)) +# define draw_frameRect( dc, r ) CALL_DRAW_NAME1(frameRect, (dc), (r) ) +# define draw_invertRect( dc, r ) CALL_DRAW_NAME1(invertCell, (dc), (r) ) +# define draw_drawString( dc, s, x, y) \ + CALL_DRAW_NAME3(drawString, (dc), (s), (x), (y) ) +# define draw_drawBitmap( dc, bm, x, y ) \ + CALL_DRAW_NAME3(drawBitmap, (dc), (bm), (x), (y) ) +# define draw_measureText( dc, t, wp, hp ) \ + CALL_DRAW_NAME3(measureText, (dc), (t), (wp), (hp) ) + +void InitDrawDefaults( DrawCtxVTable* vtable ); +#endif /* DRAW_WITH_PRIMITIVES */ + + +#endif diff --git a/xwords4/common/engine.c b/xwords4/common/engine.c new file mode 100644 index 000000000..aa337e53c --- /dev/null +++ b/xwords4/common/engine.c @@ -0,0 +1,1245 @@ +/* -*- fill-column: 78; compile-command: "cd ../linux && make -j MEMDEBUG=TRUE"; -*- */ +/* + * Copyright 1997 - 2006 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "comtypes.h" +#include "engine.h" +#include "dictnry.h" +#include "util.h" + +#ifdef CPLUS +extern "C" { +#endif + +#define eEND 0x63454e44 + +typedef XP_U8 Engine_rack[MAX_UNIQUE_TILES+1]; + +#define NUM_SAVED_MOVES 10 + +typedef struct BlankTuple { + short col; + Tile tile; +} BlankTuple; + +typedef struct PossibleMove { + XP_U16 score; /* Because I'm doing a memcmp to sort these things, + the comparison must be done differently on + little-endian platforms. */ + MoveInfo moveInfo; + //XP_U16 whichBlanks; /* flags */ + Tile blankVals[MAX_COLS]; /* the faces for which we've substituted + blanks */ +} PossibleMove; + +typedef struct MoveIterationData { + PossibleMove savedMoves[NUM_SAVED_MOVES]; + XP_U16 lowestSavedScore; + PossibleMove lastSeenMove; + XP_U16 leftInMoveCache; +} MoveIterationData; + +/* one bit per tile that's possible here *\/ */ +typedef XP_U32 CrossBits; +typedef struct Crosscheck { CrossBits bits[2]; } Crosscheck; + +struct EngineCtxt { + const ModelCtxt* model; + const DictionaryCtxt* dict; + XW_UtilCtxt* util; + + Engine_rack rack; + Tile blankTile; + XP_Bool searchInProgress; + XP_Bool searchHorizontal; + XP_Bool isRobot; + XP_Bool isFirstMove; + XP_U16 numRows, numCols; + XP_U16 curRow; + XP_U16 blankCount; + XP_U16 targetScore; + XP_U16 star_row; + XP_Bool returnNOW; + MoveIterationData miData; + + XP_S16 blankValues[MAX_TRAY_TILES]; + Crosscheck rowChecks[MAX_ROWS]; // also used in xwscore + XP_U16 scoreCache[MAX_ROWS]; + + XP_U16 nTilesMax; +#ifdef XWFEATURE_SEARCHLIMIT + XP_U16 nTilesMin; + XP_U16 nTilesMinUser, nTilesMaxUser; + XP_Bool tileLimitsKnown; + const BdHintLimits* searchLimits; +#endif + XP_U16 lastRowToFill; + +#ifdef DEBUG + XP_U16 curLimit; +#endif + MPSLOT +}; /* EngineCtxt */ + +static void findMovesOneRow( EngineCtxt* engine ); +static Tile localGetBoardTile( EngineCtxt* engine, XP_U16 col, + XP_U16 row, XP_Bool substBlank ); +static array_edge* edge_with_tile( const DictionaryCtxt* dict, + array_edge* from, Tile tile ); +static XP_Bool scoreQualifies( EngineCtxt* engine, XP_U16 score ); +static void findMovesForAnchor( EngineCtxt* engine, XP_S16* prevAnchor, + XP_U16 col, XP_U16 row ) ; +static void figureCrosschecks( EngineCtxt* engine, XP_U16 col, + XP_U16 row, XP_U16* scoreP, + Crosscheck* check ); +static XP_Bool isAnchorSquare( EngineCtxt* engine, XP_U16 col, XP_U16 row ); +static array_edge* follow( const DictionaryCtxt* dict, array_edge* in ); +static array_edge* edge_from_tile( const DictionaryCtxt* dict, + array_edge* from, Tile tile ); +static void leftPart( EngineCtxt* engine, Tile* tiles, XP_U16 tileLength, + array_edge* edge, XP_U16 limit, XP_U16 firstCol, + XP_U16 anchorCol, XP_U16 row ); +static void extendRight( EngineCtxt* engine, Tile* tiles, XP_U16 tileLength, + array_edge* edge, XP_Bool accepting, + XP_U16 firstCol, XP_U16 col, XP_U16 row ); +static array_edge* consumeFromLeft( EngineCtxt* engine, array_edge* edge, + short col, short row ); +static XP_Bool rack_remove( EngineCtxt* engine, Tile tile, XP_Bool* isBlank ); +static void rack_replace( EngineCtxt* engine, Tile tile, XP_Bool isBlank ); +static void considerMove( EngineCtxt* engine, Tile* tiles, short tileLength, + short firstCol, short lastRow ); +static void considerScoreWordHasBlanks( EngineCtxt* engine, XP_U16 blanksLeft, + PossibleMove* posmove, + XP_U16 lastRow, + BlankTuple* usedBlanks, + XP_U16 usedBlanksCount ); +static void saveMoveIfQualifies( EngineCtxt* engine, PossibleMove* posmove ); + + + +#if defined __LITTLE_ENDIAN +static XP_S16 cmpMoves( PossibleMove* m1, PossibleMove* m2 ); +# define CMPMOVES( m1, m2 ) cmpMoves( m1, m2 ) +#elif defined __BIG_ENDIAN +# define CMPMOVES( m1, m2 ) XP_MEMCMP( m1, m2, sizeof(*(m1))) +#else + error: need to pick one!!! +#endif + +#ifdef NODE_CAN_4 +# define ISACCEPTING(d,e) \ + ((ACCEPTINGMASK_NEW & ((array_edge_old*)(e))->bits) != 0) +# define IS_LAST_EDGE(d,e) \ + ((LASTEDGEMASK_NEW & ((array_edge_old*)(e))->bits) != 0) +# define EDGETILE(e,edge) \ + ((Tile)(((array_edge_old*)(edge))->bits & \ + ((e)->is_4_byte?LETTERMASK_NEW_4:LETTERMASK_NEW_3))) +#else +# define ISACCEPTING(d,e) \ + ((ACCEPTINGMASK_OLD & ((array_edge_old*)(e))->bits) != 0) +# define IS_LAST_EDGE(d,e) \ + ((LASTEDGEMASK_OLD & ((array_edge_old*)(e))->bits) != 0) +# define EDGETILE(d,edge) \ + ((Tile)(((array_edge_old*)(edge))->bits & LETTERMASK_OLD)) +#endif + +/* #define CROSSCHECK_CONTAINS(chk,tile) (((chk) & (1L<<(tile))) != 0) */ +#define CROSSCHECK_CONTAINS(chk,tile) checkIsSet( (chk), (tile) ) + +#define HILITE_CELL( engine, col, row ) \ + util_hiliteCell( (engine)->util, (col), (row) ) + +/* not implemented yet */ +XP_U16 +engine_getScoreCache( EngineCtxt* engine, XP_U16 row ) +{ + return engine->scoreCache[row]; +} /* engine_getScoreCache */ + +/***************************************************************************** + * This should be the first executable code in the file in case I want to + * turn it into a separate code module later. + ****************************************************************************/ +EngineCtxt* +engine_make( MPFORMAL XW_UtilCtxt* util, XP_Bool isRobot ) +{ + EngineCtxt* result = (EngineCtxt*)XP_MALLOC( mpool, sizeof(*result) ); + XP_MEMSET( result, 0, sizeof(*result) ); + + MPASSIGN(result->mpool, mpool); + + result->util = util; + + result->isRobot = isRobot; + + engine_reset( result ); + + return result; +} /* engine_make */ + +void +engine_writeToStream( EngineCtxt* XP_UNUSED(ctxt), + XWStreamCtxt* XP_UNUSED_DBG(stream) ) +{ + /* nothing to save; see comment below */ +#ifdef DEBUG + stream_putU32( stream, eEND ); +#endif +} /* engine_writeToStream */ + +EngineCtxt* +engine_makeFromStream( MPFORMAL XWStreamCtxt* XP_UNUSED_DBG(stream), + XW_UtilCtxt* util, XP_Bool isRobot ) +{ + EngineCtxt* engine = engine_make( MPPARM(mpool) util, isRobot ); + + /* All the engine's data seems to be used only in the process of finding a + move. So if we're willing to have the set of moves found lost across + a save, there's nothing to do! */ + + XP_ASSERT( stream_getU32( stream ) == eEND ); + + return engine; +} /* engine_makeFromStream */ + +void +engine_reset( EngineCtxt* engine ) +{ + XP_MEMSET( &engine->miData, 0, sizeof(engine->miData) ); + engine->miData.lastSeenMove.score = 0xffff; /* max possible */ + engine->searchInProgress = XP_FALSE; +#ifdef XWFEATURE_SEARCHLIMIT + engine->tileLimitsKnown = XP_FALSE; /* indicates not set */ + if ( engine->nTilesMin == 0 ) { + engine->nTilesMinUser = engine->nTilesMin = 1; + engine->nTilesMaxUser = engine->nTilesMax = MAX_TRAY_TILES; + } +#endif +} /* engine_reset */ + +void +engine_destroy( EngineCtxt* engine ) +{ + XP_ASSERT( engine != NULL ); + XP_FREE( engine->mpool, engine ); +} /* engine_destroy */ + +static XP_Bool +initTray( EngineCtxt* engine, const Tile* tiles, XP_U16 numTiles ) +{ + XP_Bool result = numTiles > 0; + XP_U16 i; + + if ( result ) { + XP_MEMSET( engine->rack, 0, sizeof(engine->rack) ); + for ( i = 0; i < numTiles; ++i ) { + Tile tile = *tiles++; + XP_ASSERT( tile < MAX_UNIQUE_TILES ); + ++engine->rack[tile]; + } + } + + return result; +} /* initTray */ + +#if defined __LITTLE_ENDIAN +static XP_S16 +cmpMoves( PossibleMove* m1, PossibleMove* m2 ) +{ + if ( m1->score == m2->score ) { + return XP_MEMCMP( &m1->moveInfo, &m2->moveInfo, + sizeof(*m1) - sizeof( m1->score ) ); + } else if ( m1->score < m2->score ) { + return -1; + } else { + return 1; + } +} /* cmpMoves */ +#endif + +static XP_Bool +chooseMove( EngineCtxt* engine, PossibleMove** move ) +{ + XP_U16 i; + PossibleMove* chosen; + + /* First, sort 'em. Put the higher-scoring moves at the top where they'll + get picked up first. Don't sort if we're working for a robot; we've + only been saving the single best move anyway. At least not until we + start applying other criteria than score to moves. */ + if ( engine->isRobot ) { + chosen = &engine->miData.savedMoves[0]; + } else { + while ( engine->miData.leftInMoveCache == 0 ) { + XP_Bool done = XP_TRUE; + for ( i = 0; i < NUM_SAVED_MOVES-1; ++i ) { + if ( CMPMOVES( &engine->miData.savedMoves[i], + &engine->miData.savedMoves[i+1]) > 0 ) { + PossibleMove tmp; + XP_MEMCPY( &tmp, &engine->miData.savedMoves[i], + sizeof(tmp) ); + XP_MEMCPY( &engine->miData.savedMoves[i], + &engine->miData.savedMoves[i+1], + sizeof(engine->miData.savedMoves[i]) ); + XP_MEMCPY( &engine->miData.savedMoves[i+1], &tmp, + sizeof(engine->miData.savedMoves[i+1]) ); + done = XP_FALSE; + } + } + if ( done ) { + engine->miData.leftInMoveCache = NUM_SAVED_MOVES; + } +#if 0 + XP_DEBUGF( "sorted moves; scores are: " ); + for ( i = 0; i < NUM_SAVED_MOVES; ++i ) { + XP_DEBUGF( "%d; ", engine->miData.savedMoves[i].score ); + } + XP_DEBUGF( "\n" ); +#endif + } + /* now pick the one we're supposed to return */ + chosen = &engine->miData.savedMoves[--engine->miData.leftInMoveCache]; + } + + *move = chosen; /* set either way */ + + if ( chosen->score > 0 ) { + + if ( engine->miData.leftInMoveCache == 0 ) { + XP_MEMCPY( &engine->miData.lastSeenMove, + &engine->miData.savedMoves[0], + sizeof(engine->miData.lastSeenMove) ); + engine->miData.lowestSavedScore = 0; + } + + return XP_TRUE; + } else { + engine_reset( engine ); + return XP_FALSE; + } +} /* chooseMove */ + +/* Return of XP_TRUE means that we ran to completion. XP_FALSE means we were + * interrupted. Whether an actual move was found is indicated by what's + * filled in in *newMove. + */ +XP_Bool +engine_findMove( EngineCtxt* engine, const ModelCtxt* model, + const DictionaryCtxt* dict, const Tile* tiles, + XP_U16 nTiles, +#ifdef XWFEATURE_SEARCHLIMIT + const BdHintLimits* searchLimits, + XP_Bool useTileLimits, +#endif + XP_U16 targetScore, XP_Bool* canMoveP, + MoveInfo* newMove ) +{ + XP_Bool result = XP_TRUE; + XP_U16 star_row; + + engine->nTilesMax = MAX_TRAY_TILES; +#ifdef XWFEATURE_SEARCHLIMIT + if ( useTileLimits ) { + /* We'll want to use the numbers we've been using already unless + there's been a reset. In that case, though, provide the old + ones as defaults */ + if ( !engine->tileLimitsKnown ) { + + XP_U16 nTilesMin = engine->nTilesMinUser; + XP_U16 nTilesMax = engine->nTilesMaxUser; + + if ( util_getTraySearchLimits( engine->util, + &nTilesMin, &nTilesMax ) ) { + engine->tileLimitsKnown = XP_TRUE; + engine->nTilesMinUser = nTilesMin; + engine->nTilesMaxUser = nTilesMax; + } else { + *canMoveP = XP_FALSE; + return XP_TRUE; + } + } + + engine->nTilesMin = engine->nTilesMinUser; + engine->nTilesMax = engine->nTilesMaxUser; + } else { + engine->nTilesMin = 1; + } +#endif + + engine->model = model; + engine->dict = dict; + engine->blankTile = dict_getBlankTile( dict ); + engine->returnNOW = XP_FALSE; +#ifdef XWFEATURE_SEARCHLIMIT + engine->searchLimits = searchLimits; +#endif + + engine->star_row = star_row = model_numRows(model) / 2; + engine->isFirstMove = + EMPTY_TILE == localGetBoardTile( engine, star_row, + star_row, XP_FALSE ); + + /* If we've been asked to generate a move but can't because the + dictionary's emtpy or there are no tiles, still return TRUE so we don't + get scheduled again. Fixes infinite loop with empty dict and a + robot. */ + *canMoveP = dict_getTopEdge(dict) != NULL && initTray( engine, tiles, + nTiles ); + if ( *canMoveP ) { + + util_engineStarting( engine->util, + engine->rack[engine->blankTile] ); + + engine->targetScore = targetScore; + + if ( engine->miData.leftInMoveCache == 0 ) { + + XP_MEMSET( engine->miData.savedMoves, 0, + sizeof(engine->miData.savedMoves) ); + + if ( engine->searchInProgress ) { + goto resumePoint; + } else { + engine->searchHorizontal = XP_TRUE; + engine->searchInProgress = XP_TRUE; + } + for ( ; ; ) { + XP_U16 firstRowToFill = 0; + engine->numRows = model_numRows(engine->model); + engine->numCols = model_numCols(engine->model); + if ( !engine->searchHorizontal ) { + XP_U16 tmp = engine->numRows; + engine->numRows = engine->numCols; + engine->numCols = tmp; + } + + if ( 0 ) { +#ifdef XWFEATURE_SEARCHLIMIT + } else if ( !!searchLimits ) { + if ( engine->searchHorizontal ) { + firstRowToFill = searchLimits->top; + engine->lastRowToFill = searchLimits->bottom; + } else { + firstRowToFill = searchLimits->left; + engine->lastRowToFill = searchLimits->right; + } +#endif + } else { + engine->lastRowToFill = engine->numRows - 1; + } + + for ( engine->curRow = firstRowToFill; + engine->curRow <= engine->lastRowToFill; + ++engine->curRow ) { + resumePoint: + if ( engine->isFirstMove && (engine->curRow != star_row)) { + continue; + } + findMovesOneRow( engine ); + if ( engine->returnNOW ) { + goto outer; + } + } + + if ( !engine->searchHorizontal || + (engine->isFirstMove && !searchLimits) ) { + engine->searchInProgress = XP_FALSE; + break; + } else { + engine->searchHorizontal = XP_FALSE; + } + } /* forever */ + outer: + result = result; /* c++ wants a statement after the label */ + } + /* Search is finished. Choose (or just return) the best move found. */ + if ( engine->returnNOW ) { + result = XP_FALSE; + } else { + PossibleMove* move; + + result = XP_TRUE; + + (void)chooseMove( engine, &move ); + XP_ASSERT( !!newMove ); + XP_MEMCPY( newMove, &move->moveInfo, sizeof(*newMove) ); + } + + util_engineStopping( engine->util ); + } else { + /* set up a PASS. I suspect the caller should be deciding how to + handle this case itself, but this doesn't preclude its doing + so. */ + newMove->nTiles = 0; + } + + return result; +} /* engine_findMove */ + +static void +findMovesOneRow( EngineCtxt* engine ) +{ + XP_U16 lastCol = engine->numCols - 1; + XP_U16 col, row = engine->curRow; + XP_S16 prevAnchor; + XP_U16 firstSearchCol, lastSearchCol; + const BdHintLimits* searchLimits = engine->searchLimits; + + if ( 0 ) { +#ifdef XWFEATURE_SEARCHLIMIT + } else if ( !!searchLimits ) { + if ( engine->searchHorizontal ) { + firstSearchCol = searchLimits->left; + lastSearchCol = searchLimits->right; + } else { + firstSearchCol = searchLimits->top; + lastSearchCol = searchLimits->bottom; + } +#endif + } else { + firstSearchCol = 0; + lastSearchCol = lastCol; + } + + XP_MEMSET( &engine->rowChecks, 0, sizeof(engine->rowChecks) ); /* clear */ + for ( col = 0; col <= lastCol; ++col ) { + if ( col < firstSearchCol || col > lastSearchCol ) { + engine->scoreCache[col] = 0; + } else { + figureCrosschecks( engine, col, row, + &engine->scoreCache[col], + &engine->rowChecks[col]); + } + } + + prevAnchor = firstSearchCol - 1; + for ( col = firstSearchCol; col <= lastSearchCol && !engine->returnNOW; + ++col ) { + if ( isAnchorSquare( engine, col, row ) ) { + findMovesForAnchor( engine, &prevAnchor, col, row ); + } + } +} /* findMovesOneRow */ + +static XP_Bool +lookup( const DictionaryCtxt* dict, array_edge* edge, Tile* buf, + XP_U16 tileIndex, XP_U16 length ) +{ + while ( edge != NULL ) { + Tile targetTile = buf[tileIndex]; + edge = edge_with_tile( dict, edge, targetTile ); + if ( edge == NULL ) { /* tile not available out of this node */ + return XP_FALSE; + } else { + if ( ++tileIndex == length ) { /* is this the last tile? */ + return ISACCEPTING(dict, edge); + } else { + edge = follow( dict, edge ); + continue; + } + } + } + return XP_FALSE; +} /* lookup */ + +static void +figureCrosschecks( EngineCtxt* engine, XP_U16 x, XP_U16 y, XP_U16* scoreP, + Crosscheck* check ) +{ + XP_S16 startY, maybeY; + XP_U16 numRows = engine->numRows; + Tile tile; + array_edge* in_edge; + array_edge* candidateEdge; + Tile tiles[MAX_COLS]; + XP_U16 tilesAfter; + XP_U16 checkScore = 0; + const DictionaryCtxt* dict = engine->dict; + + if ( localGetBoardTile( engine, x, y, XP_FALSE ) == EMPTY_TILE ) { + + /* find the first tile of any prefix */ + startY = (XP_S16)y; + for ( ; ; ) { + maybeY = startY - 1; + if ( maybeY < 0 ) { + break; + } + if ( localGetBoardTile( engine, x, maybeY, XP_FALSE ) + == EMPTY_TILE ) { + break; + } + startY = maybeY; + } + + /* Take care of the "special case" where the square has no neighbors + in either crosscheck direction */ + if ( (y == startY) && + ((y == numRows-1) || + (localGetBoardTile( engine, x, y+1, XP_FALSE ) == EMPTY_TILE))){ + /* all tiles legal and checkScore remains 0, as there are no + neighbors */ + XP_MEMSET( check, 0xFF, sizeof(*check) ); + goto outer; + } + + /* now walk the DAWG consuming any prefix. We want in_edge to wind up + holding the edge that leads to {x,y}, which will be the root edge + if there's no prefix. I can't use consumeFromLeft() here because + here I'm consuming upward. But I could generalize it.... */ + in_edge = dict_getTopEdge( dict ); + while ( startY < y ) { + tile = localGetBoardTile( engine, x, startY, XP_TRUE ); + XP_ASSERT( tile != EMPTY_TILE ); + checkScore += dict_getTileValue( dict, tile ); + tile = localGetBoardTile( engine, x, startY, XP_FALSE ); + in_edge = edge_from_tile( dict, in_edge, tile ); + /* If we run into a null edge here we have a prefix that by the + dictionary is an illegal word. One way it could have gotten + there is by being placed by a human. So it's not something to + flag here, but we won't be able to put anything in the spot so + the crosscheck is empty. And the ASSERT goes. + + Note that if we were disallowing words not in the dictionary + (as in a robot-only game) then the assertion would be valid: + only if there's a single letter as the "prefix" of our + crosscheck would it make sense for there to be no edge leading + out of it. But when can that happen? I.e. what letters don't + begin words in any reasonable word list? */ + if ( in_edge == NULL ) { + /* Only way to have gotten here is if a user's played a word + not in this dict. We'll not be able to build on it! */ + XP_ASSERT( check->bits[0] == 0L && check->bits[1] == 0L ); + goto outer; + } + ++startY; + } + + /* now in_edge points to the array of candidate edges. We'll build up + a buffer of the Tiles following the candidate square on the board, + then put each candidate edge's Tile in place and do a lookup + beginning at in_edge. Successful candidate tiles get added to the + Crosscheck */ + for ( tilesAfter = 1, maybeY = y + 1; maybeY < numRows; ++maybeY ) { + tile = localGetBoardTile( engine, x, maybeY, XP_TRUE ); + if ( tile == EMPTY_TILE ) { + break; + } else { + checkScore += dict_getTileValue( dict, tile ); + tiles[tilesAfter++] = localGetBoardTile( engine, x, maybeY, + XP_FALSE ); + } + } + + /* would it be possible to use extendRight here? With an empty + tray? No: it calls considerMove etc. */ + candidateEdge = in_edge; + for ( ; ; ) { + tile = EDGETILE( dict, candidateEdge ); + XP_ASSERT( tile < MAX_UNIQUE_TILES ); + tiles[0] = tile; + if ( lookup( dict, in_edge, tiles, 0, tilesAfter ) ) { + XP_ASSERT( (tile >> 5) + < (VSIZE(check->bits)) ); + check->bits[tile>>5] |= (1L << (tile & 0x1F)); + } + + if ( IS_LAST_EDGE(dict,candidateEdge ) ) { + break; + } +#ifdef NODE_CAN_4 + candidateEdge += dict->nodeSize; +#else + candidateEdge += 3; +#endif + } + } + outer: + if ( scoreP != NULL ) { + *scoreP = checkScore; + } +} /* figureCrosschecks */ + +XP_Bool +engine_check( DictionaryCtxt* dict, Tile* tiles, XP_U16 nTiles ) +{ + array_edge* in_edge = dict_getTopEdge( dict ); + + return lookup( dict, in_edge, tiles, 0, nTiles ); +} /* engine_check */ + +static Tile +localGetBoardTile( EngineCtxt* engine, XP_U16 col, XP_U16 row, + XP_Bool substBlank ) +{ + Tile result; + XP_Bool isBlank, ignore; + + if ( !engine->searchHorizontal ) { + XP_U16 tmp = col; + col = row; + row = tmp; + } + + if ( model_getTile( engine->model, col, row, XP_FALSE, + 0, /* don't get pending, so turn doesn't matter */ + &result, &isBlank, &ignore, (XP_Bool*)NULL ) ) { + if ( isBlank && substBlank ) { + result = engine->blankTile; + } + } else { + result = EMPTY_TILE; + } + return result; +} /* localGetBoardTile */ + +/***************************************************************************** + * Return true if the tile is empty and has a filled-in square on any of the + * four sides. First move is a special case: empty and 7,7 + ****************************************************************************/ +static XP_Bool +isAnchorSquare( EngineCtxt* engine, XP_U16 col, XP_U16 row ) +{ + if ( localGetBoardTile( engine, col, row, XP_FALSE ) != EMPTY_TILE ) { + return XP_FALSE; + } + + if ( engine->isFirstMove ) { + return col == engine->star_row && row == engine->star_row; + } + + if ( (col != 0) && + localGetBoardTile( engine, col-1, row, XP_FALSE ) != EMPTY_TILE ) { + return XP_TRUE; + } + if ( (col < engine->numCols-1) + && localGetBoardTile( engine, col+1, row, XP_FALSE ) != EMPTY_TILE) { + return XP_TRUE; + } + if ( (row != 0) + && localGetBoardTile( engine, col, row-1, XP_FALSE) != EMPTY_TILE ) { + return XP_TRUE; + } + if ( (row < engine->numRows-1) + && localGetBoardTile( engine, col, row+1, XP_FALSE ) != EMPTY_TILE ){ + return XP_TRUE; + } + return XP_FALSE; +} /* isAnchorSquare */ + +static void +hiliteForAnchor( EngineCtxt* engine, XP_U16 col, XP_U16 row ) +{ + if ( !engine->searchHorizontal ) { + XP_U16 tmp = col; + col = row; + row = tmp; + } + + if ( !HILITE_CELL( engine, col, row ) ) { + engine->returnNOW = XP_TRUE; + } +} /* hiliteForAnchor */ + +static void +findMovesForAnchor( EngineCtxt* engine, XP_S16* prevAnchor, + XP_U16 col, XP_U16 row ) +{ + XP_S16 limit; + array_edge* edge; + array_edge* topEdge; + Tile tiles[MAX_ROWS]; + + hiliteForAnchor( engine, col, row ); + + if ( engine->returnNOW ) { + /* time to bail */ + } else { + limit = col - *prevAnchor - 1; +#ifdef TEST_MINLIMIT + if ( limit >= MAX_TRAY_TILES ) { + limit = MAX_TRAY_TILES - 1; + } +#endif + topEdge = dict_getTopEdge( engine->dict ); + if ( col == 0 ) { + edge = topEdge; + } else if ( localGetBoardTile( engine, col-1, row, XP_FALSE ) + == EMPTY_TILE ) { + leftPart( engine, tiles, 0, topEdge, limit, col, col, row ); + goto done; + } else { + edge = consumeFromLeft( engine, topEdge, col, row ); + } + DEBUG_ASSIGN(engine->curLimit, 0); + extendRight( engine, tiles, 0, edge, + XP_FALSE, // can't accept without the anchor square + col-limit, col, row ); + + done: + *prevAnchor = col; + } +} /* findMovesForAnchor */ + +static array_edge* +consumeFromLeft( EngineCtxt* engine, array_edge* edge, short col, short row ) +{ + XP_S16 maybeX; + Tile tile; + Tile tiles[MAX_ROWS]; + XP_U16 numTiles; + + /* Back up to the left until an empty tile or board edge is reached, saving + the tiles for cheaper retrieval as we walk forward through the DAWG. */ + for ( numTiles = 0, maybeX = col - 1; maybeX >= 0; --maybeX ) { + tile = localGetBoardTile( engine, maybeX, row, XP_FALSE ); + if ( tile == EMPTY_TILE ) { + break; + } + tiles[numTiles++] = tile; /* we're building the word backwards */ + } + XP_ASSERT( numTiles > 0 ); /* we should consume *something* */ + + /* could I just call lookup() here? Only if I fixed it to + communicate back the edge it's at after finishing. */ + while ( numTiles-- ) { + XP_ASSERT( tiles[numTiles] != EMPTY_TILE ); + + edge = edge_from_tile( engine->dict, edge, tiles[numTiles] ); + if ( edge == NULL ) { + break; + } + } + return edge; +} /* consumeFromLeft */ + +static void +leftPart( EngineCtxt* engine, Tile* tiles, XP_U16 tileLength, + array_edge* edge, XP_U16 limit, XP_U16 firstCol, + XP_U16 anchorCol, XP_U16 row ) +{ + DEBUG_ASSIGN( engine->curLimit, tileLength ); + + extendRight( engine, tiles, tileLength, edge, XP_FALSE, firstCol, + anchorCol, row ); + if ( !engine->returnNOW ) { + if ( (limit > 0) && (edge != NULL) ) { +#ifdef NODE_CAN_4 + XP_U16 nodeSize = engine->dict->nodeSize; +#endif + if ( engine->nTilesMax > 0 ) { + for ( ; ; ) { + XP_Bool isBlank; + Tile tile = EDGETILE( engine->dict, edge ); + if ( rack_remove( engine, tile, &isBlank ) ) { + tiles[tileLength] = tile; + leftPart( engine, tiles, tileLength+1, + follow( engine->dict, edge ), + limit-1, firstCol-1, anchorCol, row ); + rack_replace( engine, tile, isBlank ); + } + + if ( IS_LAST_EDGE( dict, edge ) || engine->returnNOW ) { + break; + } +#ifdef NODE_CAN_4 + edge += nodeSize; +#else + edge += 3; +#endif + + } + } + } + } +} /* leftPart */ + +static void +extendRight( EngineCtxt* engine, Tile* tiles, XP_U16 tileLength, + array_edge* edge, XP_Bool accepting, + XP_U16 firstCol, XP_U16 col, XP_U16 row ) +{ + Tile tile; + const DictionaryCtxt* dict = engine->dict; + + if ( col == engine->numCols ) { /* we're off the board */ + goto check_exit; + } + tile = localGetBoardTile( engine, col, row, XP_FALSE ); + + if ( edge == NULL ) { // we're off the dictionary + if ( tile != EMPTY_TILE ) { + goto no_check; // don't check at the end + } + } else if ( tile == EMPTY_TILE ) { + if ( engine->nTilesMax > 0 ) { + CrossBits check = engine->rowChecks[col].bits[0]; + XP_Bool advanced = XP_FALSE; + for ( ; ; ) { + XP_Bool contains; + tile = EDGETILE( dict, edge ); + + /* If it's bigger than 32, use the second crosscheck. This is + a hack to optimize for the vastly more common case. Even + with languages that have more than 32 tiles at least half + will be less than 32 in value. */ + if ( (tile & ~0x1F) != 0 ) { + if ( !advanced ) { + check = engine->rowChecks[col].bits[1]; + advanced = XP_TRUE; + } + contains = (check & (1L << (tile-32))) != 0; + } else { + contains = (check & (1L << tile)) != 0; + } + + if ( contains ) { + XP_Bool isBlank; + if ( rack_remove( engine, tile, &isBlank ) ) { + tiles[tileLength] = tile; + extendRight( engine, tiles, tileLength+1, + edge_from_tile( dict, edge, tile ), + ISACCEPTING( dict, edge ), firstCol, + col+1, row ); + rack_replace( engine, tile, isBlank ); + if ( engine->returnNOW ) { + goto no_check; + } + } + } + + if ( IS_LAST_EDGE( dict, edge ) ) { + break; + } +#ifdef NODE_CAN_4 + edge += dict->nodeSize; +#else + edge += 3; +#endif + } + } + + } else if ( (edge = edge_with_tile( dict, edge, tile ) ) != NULL ) { + accepting = ISACCEPTING( dict, edge ); + extendRight( engine, tiles, tileLength, follow(dict, edge), + accepting, firstCol, col+1, row ); + goto no_check; /* don't do the check at the end */ + } else { + goto no_check; + } + check_exit: + if ( accepting +#ifdef XWFEATURE_SEARCHLIMIT + && tileLength >= engine->nTilesMin +#endif + ) { + considerMove( engine, tiles, tileLength, firstCol, row ); + } + no_check: + return; +} /* extendRight */ + +static XP_Bool +rack_remove( EngineCtxt* engine, Tile tile, XP_Bool* isBlank ) +{ + Tile blankIndex = engine->blankTile; + + XP_ASSERT( tile < MAX_UNIQUE_TILES ); + XP_ASSERT( tile != blankIndex ); + XP_ASSERT( engine->nTilesMax > 0 ); + + if ( engine->rack[(short)tile] > 0 ) { /* we have the tile itself */ + --engine->rack[(short)tile]; + *isBlank = XP_FALSE; + } else if ( engine->rack[blankIndex] > 0 ) { /* we have and must use a + blank */ + --engine->rack[(short)blankIndex]; + engine->blankValues[engine->blankCount++] = tile; + *isBlank = XP_TRUE; + } else { /* we can't satisfy the request */ + return XP_FALSE; + } + + --engine->nTilesMax; + return XP_TRUE; +} /* rack_remove */ + +static void +rack_replace( EngineCtxt* engine, Tile tile, XP_Bool isBlank ) +{ + if ( isBlank ) { + --engine->blankCount; + tile = engine->blankTile; + } + ++engine->rack[(short)tile]; + + ++engine->nTilesMax; +} /* rack_replace */ + +static void +considerMove( EngineCtxt* engine, Tile* tiles, XP_S16 tileLength, + XP_S16 firstCol, XP_S16 lastRow ) +{ + PossibleMove posmove; + short col; + BlankTuple blankTuples[MAX_NUM_BLANKS]; + + if ( !util_engineProgressCallback( engine->util ) ) { + engine->returnNOW = XP_TRUE; + } else { + + /* if this never gets hit then the top-level caller of leftPart should + never pass a value greater than 7 for limit. I think we're always + guaranteed to run out of tiles before finding a legal move with + larger values but that it's expensive to look only to fail. */ + XP_ASSERT( engine->curLimit < MAX_TRAY_TILES ); + + XP_MEMSET( &posmove, 0, sizeof(posmove) ); + + for ( col = firstCol; posmove.moveInfo.nTiles < tileLength; ++col ) { + /* is it one of the new ones? */ + if ( localGetBoardTile( engine, col, lastRow, XP_FALSE ) + == EMPTY_TILE ) { + posmove.moveInfo.tiles[posmove.moveInfo.nTiles].tile = + tiles[posmove.moveInfo.nTiles]; + posmove.moveInfo.tiles[posmove.moveInfo.nTiles].varCoord + = (XP_U8)col; + ++posmove.moveInfo.nTiles; + } + } + posmove.moveInfo.isHorizontal = engine->searchHorizontal; + posmove.moveInfo.commonCoord = (XP_U8)lastRow; + + + considerScoreWordHasBlanks( engine, engine->blankCount, &posmove, + lastRow, blankTuples, 0 ); + } +} /* considerMove */ + +static void +considerScoreWordHasBlanks( EngineCtxt* engine, XP_U16 blanksLeft, + PossibleMove* posmove, + XP_U16 lastRow, BlankTuple* usedBlanks, + XP_U16 usedBlanksCount ) +{ + XP_U16 i; + + if ( blanksLeft == 0 ) { + XP_U16 score; + + score = figureMoveScore( engine->model, + &posmove->moveInfo, + engine, (XWStreamCtxt*)NULL, + (WordNotifierInfo*)NULL, NULL ); + + /* First, check that the score is even what we're interested in. If + it is, then go to the expense of filling in a PossibleMove to be + compared in full */ + if ( scoreQualifies( engine, score ) ) { + posmove->score = score; + XP_MEMSET( &posmove->blankVals, 0, sizeof(posmove->blankVals) ); + for ( i = 0; i < usedBlanksCount; ++i ) { + short col = usedBlanks[i].col; + posmove->blankVals[col] = usedBlanks[i].tile; + } + XP_ASSERT( posmove->moveInfo.isHorizontal== + engine->searchHorizontal ); + posmove->moveInfo.commonCoord = (XP_U8)lastRow; + saveMoveIfQualifies( engine, posmove ); + } + } else { + Tile bTile; + BlankTuple* bt; + + --blanksLeft; + XP_ASSERT( engine->blankValues[blanksLeft] < 128 ); + bTile = (Tile)engine->blankValues[blanksLeft]; + bt = &usedBlanks[usedBlanksCount++]; + + /* for each letter for which the blank might be standing in... */ + for ( i = 0; i < posmove->moveInfo.nTiles; ++i ) { + CellTile tile = posmove->moveInfo.tiles[i].tile; + if ( (tile & TILE_VALUE_MASK) == bTile && !IS_BLANK(tile) ) { + posmove->moveInfo.tiles[i].tile |= TILE_BLANK_BIT; + bt->col = i; + bt->tile = bTile; + considerScoreWordHasBlanks( engine, blanksLeft, + posmove, lastRow, + usedBlanks, + usedBlanksCount ); + /* now put things back */ + posmove->moveInfo.tiles[i].tile &= ~TILE_BLANK_BIT; + } + } + } +} /* considerScoreWordHasBlanks */ + +static void +saveMoveIfQualifies( EngineCtxt* engine, PossibleMove* posmove ) +{ + XP_S16 lowest = 0; + + if ( !engine->isRobot ) { /* robot doesn't ask for next hint.... */ + + /* we're not interested if we've seen this */ + if ( CMPMOVES( posmove, &engine->miData.lastSeenMove ) >= 0 ) { + lowest = -1; + } else { + XP_S16 i; + /* terminate i at 1 because lowest starts at 0 */ + for ( lowest = NUM_SAVED_MOVES-1, i = lowest - 1; i >= 0; --i ) { + /* Find the lowest value move and overwrite it. Note that + there might not be one, as all may have the same or higher + scores and those that have the same score may compare + higher. + + can't have this asssertion until I start noting the + lowest saved score (setting miData.lowestSavedScore) + below. */ + /* 1/20/2001 I don't see that this assertion is valid. I + simply don't understand why it isn't tripped all the time + in the old crosswords. */ + /* XP_ASSERT( (engine->miData.lastSeenMove.score == 0x7fff) */ + /* || (engine->miData.savedMoves[i].score */ + /* <= posmove->score) ); */ + + if ( CMPMOVES( &engine->miData.savedMoves[lowest], + &engine->miData.savedMoves[i] ) > 0 ) { + lowest = i; + } + } + } + } + if ( lowest >= 0) { + /* record the score we're dumping. No point in considering any scores + lower than this for the rest of this round. */ + engine->miData.lowestSavedScore = + engine->miData.savedMoves[lowest].score; + /* XP_DEBUGF( "lowestSavedScore now %d\n", */ + /* engine->miData.lowestSavedScore ); */ + if ( CMPMOVES( posmove, &engine->miData.savedMoves[lowest]) > 0 ) { + XP_MEMCPY( &engine->miData.savedMoves[lowest], posmove, + sizeof(engine->miData.savedMoves[lowest]) ); + /* XP_DEBUGF( "just saved move with score %d\n", */ + /* engine->miData.savedMoves[lowest].score ); */ + } + } +} /* saveMoveIfQualifies */ + +static XP_Bool +scoreQualifies( EngineCtxt* engine, XP_U16 score ) +{ + XP_Bool qualifies = XP_FALSE; + + if ( (score > engine->miData.lastSeenMove.score) + || (score > engine->targetScore) + || (score < engine->miData.lowestSavedScore) ) { + /* do nothing */ + } else { + XP_S16 i; + /* Look at each saved score, and return true as soon as one's found + with a lower or equal score to this. As an optimization, + consider remembering what the lowest score is *once there are + NUM_SAVED_MOVES moves in here* and doing a quick test on that. Or + better, keeping the list in sorted order. */ + for ( i = engine->isRobot? 0: NUM_SAVED_MOVES-1; i >= 0; --i ) { + if ( score >= engine->miData.savedMoves[i].score ) { + qualifies = XP_TRUE; + break; + } + } + } + return qualifies; +} /* scoreQualifies */ + +static array_edge* +edge_with_tile( const DictionaryCtxt* dict, array_edge* from, Tile tile ) +{ + for ( ; ; ) { + Tile candidate = EDGETILE(dict,from); + if ( candidate == tile ) { + break; + } + + if ( IS_LAST_EDGE(dict, from ) ) { + from = NULL; + break; + } +#ifdef NODE_CAN_4 + from += dict->nodeSize; +#else + from += 3; +#endif + + } + + return from; +} /* edge_with_tile */ + +static unsigned long +index_from( const DictionaryCtxt* dict, array_edge* p_edge ) +{ + unsigned long result; + +#ifdef NODE_CAN_4 + array_edge_new* edge = (array_edge_new*)p_edge; + result = ((edge->highByte << 8) | edge->lowByte) & 0x0000FFFF; + + if ( dict->is_4_byte ) { + result |= ((XP_U32)edge->moreBits) << 16; + } else { + XP_ASSERT( dict->nodeSize == 3 ); + if ( (edge->bits & EXTRABITMASK_NEW) != 0 ) { + result |= 0x00010000; /* using | instead of + saves 4 bytes */ + } + } +#else + array_edge_old* edge = (array_edge_old*)p_edge; + result = ((edge->highByte << 8) | edge->lowByte) & 0x0000FFFF; + if ( (edge->bits & EXTRABITMASK_OLD) != 0 ) { + result |= 0x00010000; /* using | instead of + saves 4 bytes */ + } +#endif + + return result; +} /* index_from */ + +static array_edge* +follow( const DictionaryCtxt* dict, array_edge* in ) +{ + XP_U32 index = index_from( dict, in ); + array_edge* result = index > 0? + dict_edge_for_index( dict, index ): (array_edge*)NULL; + return result; +} /* follow */ + +static array_edge* +edge_from_tile( const DictionaryCtxt* dict, array_edge* from, Tile tile ) +{ + array_edge* edge = edge_with_tile( dict, from, tile ); + if ( edge != NULL ) { + edge = follow( dict, edge ); + } + return edge; +} /* edge_from_tile */ + +#ifdef CPLUS +} +#endif + diff --git a/xwords4/common/engine.h b/xwords4/common/engine.h new file mode 100644 index 000000000..6b090ab0c --- /dev/null +++ b/xwords4/common/engine.h @@ -0,0 +1,67 @@ + /* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2002 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _ENGINE_H_ +#define _ENGINE_H_ + +#include "comtypes.h" +#include "dictnry.h" + +#ifdef CPLUS +extern "C" { +#endif + +#ifdef XWFEATURE_SEARCHLIMIT +typedef struct BdHintLimits { + XP_U16 left; + XP_U16 top; + XP_U16 right; + XP_U16 bottom; +} BdHintLimits; +#endif + +XP_U16 engine_getScoreCache( EngineCtxt* engine, XP_U16 row ); + +EngineCtxt* engine_make( MPFORMAL XW_UtilCtxt* util, XP_Bool isRobot ); + +void engine_writeToStream( EngineCtxt* ctxt, XWStreamCtxt* stream ); +EngineCtxt* engine_makeFromStream( MPFORMAL XWStreamCtxt* stream, + XW_UtilCtxt* util, XP_Bool isRobot ); + +void engine_init( EngineCtxt* ctxt ); +void engine_reset( EngineCtxt* ctxt ); +void engine_destroy( EngineCtxt* ctxt ); + +#define NO_SCORE_LIMIT 10000 /* for targetScore */ +XP_Bool engine_findMove( EngineCtxt* ctxt, const ModelCtxt* model, + const DictionaryCtxt* dict, const Tile* tiles, + XP_U16 nTiles, +#ifdef XWFEATURE_SEARCHLIMIT + const BdHintLimits* boardLimits, + XP_Bool useTileLimits, +#endif + XP_U16 targetScore, XP_Bool* canMove, + MoveInfo* result ); +XP_Bool engine_check( DictionaryCtxt* dict, Tile* buf, XP_U16 buflen ); + +#ifdef CPLUS +} +#endif + +#endif /* _ENGINE_H_ */ diff --git a/xwords4/common/game.c b/xwords4/common/game.c new file mode 100644 index 000000000..afa56211f --- /dev/null +++ b/xwords4/common/game.c @@ -0,0 +1,511 @@ +/* -*-mode: C; fill-column: 76; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "game.h" +#include "dictnry.h" +#include "strutils.h" + +#ifdef CPLUS +extern "C" { +#endif + +#ifdef DEBUG +static void +assertUtilOK( XW_UtilCtxt* util ) +{ + UtilVtable* vtable = util->vtable; + XP_U16 nSlots = sizeof(vtable) / 4; + while ( nSlots-- ) { + void* fptr = ((void**)vtable)[nSlots]; + XP_ASSERT( !!fptr ); + } +} /* assertUtilOK */ +#else +# define assertUtilOK(u) +#endif + +static void +checkServerRole( CurGameInfo* gi, XP_U16* nPlayersHere, XP_U16* nPlayersTotal ) +{ + if ( !!gi ) { + XP_Bool standAlone = gi->serverRole == SERVER_STANDALONE; + XP_U16 i, remoteCount = 0; + + for ( i = 0; i < gi->nPlayers; ++i ) { + LocalPlayer* player = &gi->players[i]; + if ( !player->isLocal ) { + ++remoteCount; + if ( standAlone ) { + player->isLocal = XP_TRUE; + } + } + } + if ( remoteCount == 0 && gi->serverRole != SERVER_ISCLIENT ) { + gi->serverRole = SERVER_STANDALONE; + } + + *nPlayersHere = gi->nPlayers - remoteCount; + if ( gi->serverRole == SERVER_ISCLIENT ) { + *nPlayersTotal = 0; + } else { + *nPlayersTotal = gi->nPlayers; + } + } +} /* checkServerRole */ + +void +game_makeNewGame( MPFORMAL XWGame* game, CurGameInfo* gi, + XW_UtilCtxt* util, DrawCtx* draw, + XP_U16 gameID, CommonPrefs* cp, + TransportSend XP_UNUSED_STANDALONE(sendproc), + IF_CH( TransportReset resetproc ) + void* XP_UNUSED_STANDALONE(closure) ) +{ + XP_U16 nPlayersHere, nPlayersTotal; + + assertUtilOK( util ); + checkServerRole( gi, &nPlayersHere, &nPlayersTotal ); + + gi->gameID = gameID; + + game->model = model_make( MPPARM(mpool) (DictionaryCtxt*)NULL, util, + gi->boardSize, gi->boardSize ); + +#ifndef XWFEATURE_STANDALONE_ONLY + if ( !!sendproc && gi->serverRole != SERVER_STANDALONE ) { + game->comms = comms_make( MPPARM(mpool) util, + gi->serverRole != SERVER_ISCLIENT, + nPlayersHere, nPlayersTotal, + sendproc, IF_CH(resetproc) closure ); + } else { + game->comms = (CommsCtxt*)NULL; + } +#endif + game->server = server_make( MPPARM(mpool) game->model, +#ifndef XWFEATURE_STANDALONE_ONLY + game->comms, +#else + (CommsCtxt*)NULL, +#endif + util ); + game->board = board_make( MPPARM(mpool) game->model, game->server, + draw, util ); + + server_prefsChanged( game->server, cp ); + board_prefsChanged( game->board, cp ); +} /* game_makeNewGame */ + +void +game_reset( MPFORMAL XWGame* game, CurGameInfo* gi, + XW_UtilCtxt* XP_UNUSED_STANDALONE(util), + XP_U16 gameID, CommonPrefs* cp, + TransportSend XP_UNUSED_STANDALONE(sendproc), + IF_CH(TransportReset resetproc) + void* XP_UNUSED_STANDALONE(closure) ) +{ + XP_U16 i; + XP_U16 nPlayersHere, nPlayersTotal; + + XP_ASSERT( !!game->model ); + XP_ASSERT( !!gi ); + + checkServerRole( gi, &nPlayersHere, &nPlayersTotal ); + gi->gameID = gameID; + +#ifndef XWFEATURE_STANDALONE_ONLY + if ( !!game->comms ) { + if ( gi->serverRole == SERVER_STANDALONE ) { + comms_destroy( game->comms ); + game->comms = NULL; + } else { + comms_reset( game->comms, gi->serverRole != SERVER_ISCLIENT, + nPlayersHere, nPlayersTotal ); + } + } else if ( gi->serverRole != SERVER_STANDALONE ) { + game->comms = comms_make( MPPARM(mpool) util, + gi->serverRole != SERVER_ISCLIENT, + nPlayersHere, nPlayersTotal, + sendproc, IF_CH(resetproc) closure ); + } +#else +# ifdef DEBUG + mpool = mpool; /* quash unused formal warning */ +# endif +#endif + + model_init( game->model, gi->boardSize, gi->boardSize ); + server_reset( game->server, +#ifndef XWFEATURE_STANDALONE_ONLY + game->comms +#else + NULL +#endif + ); + board_reset( game->board ); + + for ( i = 0; i < gi->nPlayers; ++i ) { + gi->players[i].secondsUsed = 0; + } + + server_prefsChanged( game->server, cp ); + board_prefsChanged( game->board, cp ); +} /* game_reset */ + +XP_Bool +game_makeFromStream( MPFORMAL XWStreamCtxt* stream, XWGame* game, + CurGameInfo* gi, DictionaryCtxt* dict, + XW_UtilCtxt* util, DrawCtx* draw, CommonPrefs* cp, + TransportSend XP_UNUSED_STANDALONE(sendProc), + IF_CH(TransportReset resetProc) + void* XP_UNUSED_STANDALONE(closure) ) +{ + XP_Bool success = XP_FALSE; + XP_U8 strVersion; +#ifndef XWFEATURE_STANDALONE_ONLY + XP_Bool hasComms; +#endif + strVersion = stream_getU8( stream ); + XP_DEBUGF( "strVersion = %d", (XP_U16)strVersion ); + + if ( strVersion <= CUR_STREAM_VERS ) { + stream_setVersion( stream, strVersion ); + + gi_readFromStream( MPPARM(mpool) stream, gi ); + +#ifndef XWFEATURE_STANDALONE_ONLY + hasComms = stream_getU8( stream ); + if ( hasComms ) { + game->comms = comms_makeFromStream( MPPARM(mpool) stream, util, + sendProc, IF_CH(resetProc) closure ); + } else { + game->comms = NULL; + } +#endif + XP_ASSERT( !!dict ); + game->model = model_makeFromStream( MPPARM(mpool) stream, dict, util ); + + game->server = server_makeFromStream( MPPARM(mpool) stream, + game->model, +#ifndef XWFEATURE_STANDALONE_ONLY + game->comms, +#else + (CommsCtxt*)NULL, +#endif + util, gi->nPlayers ); + + game->board = board_makeFromStream( MPPARM(mpool) stream, game->model, + game->server, draw, util, + gi->nPlayers ); + server_prefsChanged( game->server, cp ); + board_prefsChanged( game->board, cp ); + draw_dictChanged( draw, dict ); + success = XP_TRUE; + } else { + XP_LOGF( "%s: aborting; stream version too new!", __func__ ); + } + return success; +} /* game_makeFromStream */ + +void +game_saveToStream( const XWGame* game, const CurGameInfo* gi, + XWStreamCtxt* stream ) +{ + stream_putU8( stream, CUR_STREAM_VERS ); + + gi_writeToStream( stream, gi ); + +#ifndef XWFEATURE_STANDALONE_ONLY + stream_putU8( stream, (XP_U8)!!game->comms ); + if ( !!game->comms ) { + comms_writeToStream( game->comms, stream ); + } +#endif + + model_writeToStream( game->model, stream ); + server_writeToStream( game->server, stream ); + board_writeToStream( game->board, stream ); +} /* game_saveToStream */ + +void +game_dispose( XWGame* game ) +{ + /* The board should be reused!!! PENDING(ehouse) */ + if ( !!game->board ) { + board_destroy( game->board ); + game->board = NULL; + } + +#ifndef XWFEATURE_STANDALONE_ONLY + if ( !!game->comms ) { + comms_destroy( game->comms ); + game->comms = NULL; + } +#endif + if ( !!game->model ) { + DictionaryCtxt* dict = model_getDictionary( game->model ); + if ( !!dict ) { + dict_destroy( dict ); + } + model_destroy( game->model ); + game->model = NULL; + } + if ( !!game->server ) { + server_destroy( game->server ); + game->server = NULL; + } +} /* game_dispose */ + +void +gi_initPlayerInfo( MPFORMAL CurGameInfo* gi, const XP_UCHAR* nameTemplate ) +{ + XP_U16 i; + + XP_MEMSET( gi, 0, sizeof(*gi) ); + gi->serverRole = SERVER_STANDALONE; + gi->nPlayers = 2; + gi->boardSize = 15; + gi->robotSmartness = SMART_ROBOT; + gi->gameSeconds = 25 * 60; /* 25 minute game is common? */ + + gi->confirmBTConnect = XP_TRUE; + + for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { + XP_UCHAR buf[20]; + LocalPlayer* fp = &gi->players[i]; + + if ( !!nameTemplate ) { + XP_SNPRINTF( buf, sizeof(buf), nameTemplate, i+1 ); + XP_ASSERT( fp->name == NULL ); + fp->name = copyString( mpool, buf ); + } + + fp->isRobot = (i == 0); /* one robot */ + fp->isLocal = XP_TRUE; + fp->secondsUsed = 0; + } +} /* game_initPlayerInfo */ + +static void +disposePlayerInfoInt( MPFORMAL CurGameInfo* gi ) +{ + XP_U16 i; + LocalPlayer* lp; + + for ( lp = gi->players, i = 0; i < MAX_NUM_PLAYERS; ++lp, ++i ) { + if ( !!lp->name ) { + XP_FREE( mpool, lp->name ); + lp->name = (XP_UCHAR*)NULL; + } + if ( !!lp->password ) { + XP_FREE( mpool, lp->password ); + lp->password = (XP_UCHAR*)NULL; + } + } +} /* disposePlayerInfoInt */ + +void +gi_disposePlayerInfo( MPFORMAL CurGameInfo* gi ) +{ + disposePlayerInfoInt( MPPARM(mpool) gi ); + + if ( !!gi->dictName ) { + XP_FREE( mpool, gi->dictName ); + gi->dictName = (XP_UCHAR*)NULL; + } +} /* gi_disposePlayerInfo */ + +void +gi_copy( MPFORMAL CurGameInfo* destGI, const CurGameInfo* srcGI ) +{ + XP_U16 nPlayers, i; + const LocalPlayer* srcPl; + LocalPlayer* destPl; + + replaceStringIfDifferent( mpool, &destGI->dictName, + srcGI->dictName ); + + destGI->gameID = srcGI->gameID; + destGI->gameSeconds = srcGI->gameSeconds; + destGI->nPlayers = (XP_U8)srcGI->nPlayers; + nPlayers = srcGI->nPlayers; + destGI->boardSize = (XP_U8)srcGI->boardSize; + destGI->serverRole = srcGI->serverRole; + + destGI->hintsNotAllowed = srcGI->hintsNotAllowed; + destGI->timerEnabled = srcGI->timerEnabled; + destGI->robotSmartness = (XP_U8)srcGI->robotSmartness; + destGI->phoniesAction = srcGI->phoniesAction; + destGI->allowPickTiles = srcGI->allowPickTiles; + + for ( srcPl = srcGI->players, destPl = destGI->players, i = 0; + i < nPlayers; ++srcPl, ++destPl, ++i ) { + + replaceStringIfDifferent( mpool, &destPl->name, srcPl->name ); + replaceStringIfDifferent( mpool, &destPl->password, + srcPl->password ); + destPl->secondsUsed = srcPl->secondsUsed; + destPl->isRobot = srcPl->isRobot; + destPl->isLocal = srcPl->isLocal; + } +} /* gi_copy */ + +XP_U16 +gi_countLocalHumans( const CurGameInfo* gi ) +{ + XP_U16 count = 0; + XP_U16 nPlayers = gi->nPlayers; + const LocalPlayer* lp = gi->players; + while ( nPlayers-- ) { + if ( lp->isLocal && !lp->isRobot ) { + ++count; + } + ++lp; + } + return count; +} /* gi_countLocalHumans */ + +void +gi_readFromStream( MPFORMAL XWStreamCtxt* stream, CurGameInfo* gi ) +{ + LocalPlayer* pl; + XP_U16 i; + XP_UCHAR* str; + XP_U16 strVersion = stream_getVersion( stream ); + + str = stringFromStream( mpool, stream ); + replaceStringIfDifferent( mpool, &gi->dictName, str ); + if ( !!str ) { + XP_FREE( mpool, str ); + } + + gi->nPlayers = (XP_U8)stream_getBits( stream, NPLAYERS_NBITS ); + gi->boardSize = (XP_U8)stream_getBits( stream, 4 ); + gi->serverRole = (DeviceRole)stream_getBits( stream, 2 ); + gi->hintsNotAllowed = stream_getBits( stream, 1 ); + gi->robotSmartness = (XP_U8)stream_getBits( stream, 2 ); + gi->phoniesAction = (XWPhoniesChoice)stream_getBits( stream, 2 ); + gi->timerEnabled = stream_getBits( stream, 1 ); + + if ( strVersion >= STREAM_VERS_41B4 ) { + gi->allowPickTiles = stream_getBits( stream, 1 ); + gi->allowHintRect = stream_getBits( stream, 1 ); + } else { + gi->allowPickTiles = XP_FALSE; + gi->allowHintRect = XP_FALSE; + } + + if ( strVersion >= STREAM_VERS_BLUETOOTH ) { + gi->confirmBTConnect = stream_getBits( stream, 1 ); + } else { + gi->confirmBTConnect = XP_TRUE; /* safe given all the 650s out there. */ + } + + gi->gameID = stream_getU16( stream ); + if ( gi->timerEnabled ) { + gi->gameSeconds = stream_getU16( stream ); + } + + for ( pl = gi->players, i = 0; i < gi->nPlayers; ++pl, ++i ) { + str = stringFromStream( mpool, stream ); + replaceStringIfDifferent( mpool, &pl->name, str ); + if ( !!str ) { + XP_FREE( mpool, str ); + } + + str = stringFromStream( mpool, stream ); + replaceStringIfDifferent( mpool, &pl->password, str ); + if ( !!str ) { + XP_FREE( mpool, str ); + } + + pl->secondsUsed = stream_getU16( stream ); + pl->isRobot = stream_getBits( stream, 1 ); + pl->isLocal = stream_getBits( stream, 1 ); + } +} /* gi_readFromStream */ + +void +gi_writeToStream( XWStreamCtxt* stream, const CurGameInfo* gi ) +{ + const LocalPlayer* pl; + XP_U16 i; + + stringToStream( stream, gi->dictName ); + + stream_putBits( stream, NPLAYERS_NBITS, gi->nPlayers ); + stream_putBits( stream, 4, gi->boardSize ); + stream_putBits( stream, 2, gi->serverRole ); + stream_putBits( stream, 1, gi->hintsNotAllowed ); + stream_putBits( stream, 2, gi->robotSmartness ); + stream_putBits( stream, 2, gi->phoniesAction ); + stream_putBits( stream, 1, gi->timerEnabled ); + stream_putBits( stream, 1, gi->allowPickTiles ); + stream_putBits( stream, 1, gi->allowHintRect ); + stream_putBits( stream, 1, gi->confirmBTConnect ); + + stream_putU16( stream, gi->gameID ); + if ( gi->timerEnabled) { + stream_putU16( stream, gi->gameSeconds ); + } + + for ( pl = gi->players, i = 0; i < gi->nPlayers; ++pl, ++i ) { + stringToStream( stream, pl->name ); + stringToStream( stream, pl->password ); + stream_putU16( stream, pl->secondsUsed ); + stream_putBits( stream, 1, pl->isRobot ); + stream_putBits( stream, 1, pl->isLocal ); + } +} /* gi_writeToStream */ + +XP_Bool +player_hasPasswd( LocalPlayer* player ) +{ + XP_UCHAR* password = player->password; + /* XP_ASSERT( player->isLocal ); */ + return !!password && *password != '\0'; +} /* player_hasPasswd */ + +XP_Bool +player_passwordMatches( LocalPlayer* player, XP_U8* buf, XP_U16 len ) +{ + XP_ASSERT( player->isLocal ); + + return (XP_STRLEN(player->password) == len) + && (0 == XP_STRNCMP( player->password, (XP_UCHAR*)buf, len )); +} /* player_passwordMatches */ + +XP_U16 +player_timePenalty( CurGameInfo* gi, XP_U16 playerNum ) +{ + XP_S16 seconds = (gi->gameSeconds / gi->nPlayers); + LocalPlayer* player = gi->players + playerNum; + XP_U16 result = 0; + + seconds -= player->secondsUsed; + if ( seconds < 0 ) { + seconds = -seconds; + seconds += 59; + result = (seconds/60) * 10; + } + return result; +} /* player_timePenalty */ + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/game.h b/xwords4/common/game.h new file mode 100644 index 000000000..3f18f5003 --- /dev/null +++ b/xwords4/common/game.h @@ -0,0 +1,114 @@ +/* -*-mode: C; fill-column: 76; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _GAME_H_ +#define _GAME_H_ + +#include "model.h" +#include "board.h" +#include "comms.h" +#include "server.h" +#include "util.h" + +#ifdef CPLUS +extern "C" { +#endif + +#define STREAM_VERS_MODEL_NO_DICT 0x06 +#define STREAM_VERS_BLUETOOTH 0x05 +#define STREAM_VERS_KEYNAV 0x04 +#define STREAM_VERS_RELAY 0x03 +#define STREAM_VERS_41B4 0x02 +#define STREAM_VERS_405 0x01 + +#define CUR_STREAM_VERS STREAM_VERS_MODEL_NO_DICT + +typedef struct LocalPlayer { + XP_UCHAR* name; + XP_UCHAR* password; + XP_U16 secondsUsed; + XP_Bool isRobot; + XP_Bool isLocal; +} LocalPlayer; + +#define DUMB_ROBOT 0 +#define SMART_ROBOT 1 + +typedef struct CurGameInfo { + XP_UCHAR* dictName; + LocalPlayer players[MAX_NUM_PLAYERS]; + XP_U16 gameID; /* uniquely identifies game */ + XP_U16 gameSeconds; /* for timer */ + XP_U8 nPlayers; + XP_U8 boardSize; + DeviceRole serverRole; + + XP_Bool hintsNotAllowed; + XP_Bool timerEnabled; + XP_Bool allowPickTiles; + XP_Bool allowHintRect; + XP_U8 robotSmartness; + XWPhoniesChoice phoniesAction; + XP_Bool confirmBTConnect; /* only used for BT */ +} CurGameInfo; + +typedef struct XWGame { + BoardCtxt* board; + ModelCtxt* model; + ServerCtxt* server; +#ifndef XWFEATURE_STANDALONE_ONLY + CommsCtxt* comms; +#endif +} XWGame; + +void game_makeNewGame( MPFORMAL XWGame* game, CurGameInfo* gi, + XW_UtilCtxt* util, DrawCtx* draw, XP_U16 gameID, + CommonPrefs* cp, TransportSend sendproc, + IF_CH(TransportReset resetproc) void* closure); +void game_reset( MPFORMAL XWGame* game, CurGameInfo* gi, XW_UtilCtxt* util, + XP_U16 gameID, CommonPrefs* cp, TransportSend sendproc, + IF_CH(TransportReset resetproc) void* closure ); + +XP_Bool game_makeFromStream( MPFORMAL XWStreamCtxt* stream, XWGame* game, + CurGameInfo* gi, + DictionaryCtxt* dict, XW_UtilCtxt* util, + DrawCtx* draw, CommonPrefs* cp, + TransportSend sendProc, IF_CH(TransportReset rp) + void* closure ); + +void game_saveToStream( const XWGame* game, const CurGameInfo* gi, + XWStreamCtxt* stream ); +void game_dispose( XWGame* game ); +void gi_initPlayerInfo( MPFORMAL CurGameInfo* gi, + const XP_UCHAR* nameTemplate ); +void gi_disposePlayerInfo( MPFORMAL CurGameInfo* gi ); +void gi_writeToStream( XWStreamCtxt* stream, const CurGameInfo* gi ); +void gi_readFromStream( MPFORMAL XWStreamCtxt* stream, CurGameInfo* gi ); +void gi_copy( MPFORMAL CurGameInfo* destGI, const CurGameInfo* srcGi ); +XP_U16 gi_countLocalHumans( const CurGameInfo* gi ); + +XP_Bool player_hasPasswd( LocalPlayer* player ); +XP_Bool player_passwordMatches( LocalPlayer* player, XP_U8* buf, XP_U16 len ); +XP_U16 player_timePenalty( CurGameInfo* gi, XP_U16 playerNum ); + +#ifdef CPLUS +} +#endif + +#endif diff --git a/xwords4/common/mempool.c b/xwords4/common/mempool.c new file mode 100644 index 000000000..a122fb515 --- /dev/null +++ b/xwords4/common/mempool.c @@ -0,0 +1,276 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef MEM_DEBUG + +#include "mempool.h" +#include "comtypes.h" +#include "xwstream.h" + +/* #define MPOOL_DEBUG */ + +#ifdef CPLUS +extern "C" { +#endif + +typedef struct MemPoolEntry { + struct MemPoolEntry* next; + const char* fileName; + XP_U32 lineNo; + XP_U32 size; + void* ptr; +} MemPoolEntry; + +struct MemPoolCtx { + MemPoolEntry* freeList; + MemPoolEntry* usedList; + + XP_U16 nFree; + XP_U16 nUsed; + XP_U16 nAllocs; +}; + +/*--------------------------------------------------------------------------*/ + +MemPoolCtx* +mpool_make( void ) +{ + MemPoolCtx* result = (MemPoolCtx*)XP_PLATMALLOC( sizeof(*result) ); + XP_MEMSET( result, 0, sizeof(*result) ); + return result; +} /* mpool_make */ + +static void +freeList( MemPoolEntry* entry ) +{ + while ( !!entry ) { + MemPoolEntry* next = entry->next; + + XP_ASSERT( !entry->ptr ); + XP_PLATFREE( entry ); + + entry = next; + } +} /* freeList */ + +#ifdef DEBUG +static char* +checkIsText( MemPoolEntry* entry ) +{ + unsigned char* txt = (unsigned char*)entry->ptr; + XP_U32 len = entry->size; + + while ( len-- ) { + unsigned char c = *txt++; + if ( c < 32 || c > 127 ) { + if ( len == 0 && c == '\0' ) { + return (char*)entry->ptr; + } else { + return (char*)NULL; + } + } + } + + return (char*)NULL; +} /* checkIsText */ +#endif + +void +mpool_destroy( MemPoolCtx* mpool ) +{ + if ( mpool->nUsed > 0 ) { + XP_WARNF( "leaking %d blocks", mpool->nUsed ); + } + if ( !!mpool->usedList ) { + MemPoolEntry* entry; + for ( entry = mpool->usedList; !!entry; entry = entry->next ) { +#ifndef FOR_GREMLINS /* I don't want to hear about this right now */ + XP_LOGF( "%s: " XP_P " from ln %ld of %s\n", __func__, + entry->ptr, entry->lineNo, entry->fileName ); +#ifdef DEBUG + { + char* tryTxt; + tryTxt = checkIsText( entry ); + if ( !!tryTxt ) { + XP_WARNF( "--- looks like text: %s\n", tryTxt ); + } + } +#endif +#endif + } + } + +#ifndef FOR_GREMLINS + XP_ASSERT( !mpool->usedList && mpool->nUsed == 0 ); +#endif + + freeList( mpool->freeList ); + XP_PLATFREE( mpool ); +} /* mpool_destroy */ + +void* +mpool_alloc( MemPoolCtx* mpool, XP_U32 size, const char* file, XP_U32 lineNo ) +{ + MemPoolEntry* entry; + + if ( mpool->nFree > 0 ) { + entry = mpool->freeList; + mpool->freeList = entry->next; + --mpool->nFree; + } else { + entry = (MemPoolEntry*)XP_PLATMALLOC( sizeof(*entry) ); + } + + entry->next = mpool->usedList; + mpool->usedList = entry; + + entry->fileName = file; + entry->lineNo = lineNo; + entry->size = size; + entry->ptr = XP_PLATMALLOC( size ); + XP_ASSERT( !!entry->ptr ); + + ++mpool->nUsed; + ++mpool->nAllocs; + +#ifdef MPOOL_DEBUG + XP_LOGF( "%s(size=%ld,file=%s,lineNo=%ld)=>%p", + __func__, size, file, lineNo, entry->ptr ); +#endif + + return entry->ptr; +} /* mpool_alloc */ + +static MemPoolEntry* +findEntryFor( MemPoolCtx* mpool, void* ptr, MemPoolEntry** prevP ) +{ + MemPoolEntry* entry; + MemPoolEntry* prev; + + for ( prev = (MemPoolEntry*)NULL, entry = mpool->usedList; !!entry; + prev = entry, entry = prev->next ) { + + if ( entry->ptr == ptr ) { + + if ( !!prevP ) { + *prevP = prev; + } + + return entry; + } + } + return (MemPoolEntry*)NULL; +} /* findEntryFor */ + +void* +mpool_realloc( MemPoolCtx* mpool, void* ptr, XP_U32 newsize, const char* file, XP_U32 lineNo ) +{ + MemPoolEntry* entry = findEntryFor( mpool, ptr, (MemPoolEntry**)NULL ); + + if ( !entry ) { + XP_LOGF( "findEntryFor failed; called from %s, line %ld", + file, lineNo ); + } else { + entry->ptr = XP_PLATREALLOC( entry->ptr, newsize ); + XP_ASSERT( !!entry->ptr ); + entry->fileName = file; + entry->lineNo = lineNo; + } + return entry->ptr; +} /* mpool_realloc */ + +void +mpool_free( MemPoolCtx* mpool, void* ptr, const char* file, XP_U32 lineNo ) +{ + MemPoolEntry* entry; + MemPoolEntry* prev; + + entry = findEntryFor( mpool, ptr, &prev ); + + if ( !entry ) { + XP_LOGF( "findEntryFor failed; called from %s, line %ld", + file, lineNo ); + } else { + +#ifdef MPOOL_DEBUG + XP_LOGF( "%s(ptr=%p):size=%ld,file=%s,lineNo=%ld)", __func__, + entry->ptr, entry->size, entry->fileName, entry->lineNo ); +#endif + + if ( !!prev ) { + prev->next = entry->next; + } else { + mpool->usedList = entry->next; + } + + XP_MEMSET( entry->ptr, 0x00, entry->size ); + XP_PLATFREE( entry->ptr ); + entry->ptr = NULL; + + entry->next = mpool->freeList; + mpool->freeList = entry; + + ++mpool->nFree; + --mpool->nUsed; + + return; + } + + XP_ASSERT( 0 ); +} /* mpool_free */ + +void +mpool_stats( MemPoolCtx* mpool, XWStreamCtxt* stream ) +{ + XP_UCHAR buf[128]; + MemPoolEntry* entry; + + XP_SNPRINTF( buf, sizeof(buf), (XP_UCHAR*)"Number of blocks in use: %d\n" + "Number of free blocks: %d\n" + "Total number of blocks allocated: %d\n", + mpool->nUsed, mpool->nFree, mpool->nAllocs ); + if ( !!stream ) { + stream_putString( stream, buf ); + } else { + XP_LOGF( "%s", buf ); + } + + for ( entry = mpool->usedList; !!entry; entry = entry->next ) { + XP_SNPRINTF( buf, sizeof(buf), + (XP_UCHAR*)"%ld byte block allocated at %p, %s: line %ld\n", + entry->size, entry->ptr, entry->fileName, entry->lineNo ); + if ( !!stream ) { + stream_putString( stream, buf ); + } else { + XP_LOGF( "%s", buf ); + } + } +} /* mpool_stats */ + +XP_U16 +mpool_getNUsed( MemPoolCtx* mpool ) +{ + return mpool->nUsed; +} /* mpool_getNUsed */ + +#ifdef CPLUS +} +#endif + +#endif /* MEM_DEBUG */ diff --git a/xwords4/common/mempool.h b/xwords4/common/mempool.h new file mode 100644 index 000000000..c162f38e7 --- /dev/null +++ b/xwords4/common/mempool.h @@ -0,0 +1,53 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _MEMPOOL_H_ +#define _MEMPOOL_H_ + +#ifdef MEM_DEBUG + +#include "comtypes.h" + +#ifdef CPLUS +extern "C" { +#endif + +typedef struct MemPoolCtx MemPoolCtx; + +MemPoolCtx* mpool_make(void); +void mpool_destroy( MemPoolCtx* mpool ); + +void* mpool_alloc( MemPoolCtx* mpool, XP_U32 size, + const char* file, XP_U32 lineNo ); +void* mpool_realloc( MemPoolCtx* mpool, void* ptr, XP_U32 newsize, + const char* file, XP_U32 lineNo ); +void mpool_free( MemPoolCtx* mpool, void* ptr, const char* file, XP_U32 lineNo ); +void mpool_stats( MemPoolCtx* mpool, XWStreamCtxt* stream ); +XP_U16 mpool_getNUsed( MemPoolCtx* mpool ); + +#ifdef CPLUS +} +#endif + +#else + +# define mpool_destroy(p) + +#endif /* MEM_DEBUG */ +#endif /* _MEMPOOL_H_ */ diff --git a/xwords4/common/memstream.c b/xwords4/common/memstream.c new file mode 100644 index 000000000..1e5e59bde --- /dev/null +++ b/xwords4/common/memstream.c @@ -0,0 +1,469 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* #include */ +/* #include */ +/* #include */ + +#include "xwstream.h" +#include "comtypes.h" +#include "memstream.h" +#include "vtabmgr.h" + +#ifdef CPLUS +extern "C" { +#endif + +#define BIT_PART(pos) ((pos)&0x00000007) +#define BYTE_PART(pos) ((pos)>>3) + +#define MIN_PACKETBUF_SIZE (1<<6) + +#define STREAM_INCR_SIZE 100 + +#define SOCKET_STREAM_SUPER_COMMON_SLOTS \ + StreamCtxVTable* vtable; \ + void* closure; \ + XP_U32 curReadPos; \ + XP_U32 curWritePos; \ + XP_PlayerAddr channelNo; \ + XP_U8* buf; \ + MemStreamCloseCallback onClose; \ + XP_U16 nBytesWritten; \ + XP_U16 nBytesAllocated; \ + XP_U16 version; \ + XP_U8 nReadBits; \ + XP_U8 nWriteBits; \ + XP_Bool isOpen; \ + MPSLOT + +#define SOCKET_STREAM_SUPER_SLOTS \ + SOCKET_STREAM_SUPER_COMMON_SLOTS + +typedef struct MemStreamCtxt { + SOCKET_STREAM_SUPER_SLOTS +} MemStreamCtxt; + +static StreamCtxVTable* make_vtable( MemStreamCtxt* stream ); + +/* Try to keep this the only entry point to this file, and to keep it at the + * top of the file (first executable code). + */ +XWStreamCtxt* +mem_stream_make( MPFORMAL VTableMgr* vtmgr, void* closure, + XP_PlayerAddr channelNo, MemStreamCloseCallback onClose ) +{ + StreamCtxVTable* vtable; + MemStreamCtxt* result = (MemStreamCtxt*)XP_MALLOC( mpool, + sizeof(*result) ); + XP_MEMSET( result, 0, sizeof(*result) ); + + MPASSIGN(result->mpool, mpool); + + vtable = (StreamCtxVTable*)vtmgr_getVTable( vtmgr, VTABLE_MEM_STREAM ); + if ( !vtable ) { + vtable = make_vtable( result ); + vtmgr_setVTable( vtmgr, VTABLE_MEM_STREAM, vtable ); + } + result->vtable = vtable; + + result->closure = closure; + result->channelNo = channelNo; + result->onClose = onClose; + + result->isOpen = XP_TRUE; + + return (XWStreamCtxt*)result; +} /* make_mem_stream */ + +static void +mem_stream_getBytes( XWStreamCtxt* p_sctx, void* where, XP_U16 count ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + + if ( stream->nReadBits != 0 ) { + stream->nReadBits = 0; + } + + XP_ASSERT( stream->curReadPos + count <= stream->nBytesAllocated ); + XP_ASSERT( stream->curReadPos + count <= stream->nBytesWritten ); + + XP_MEMCPY( where, stream->buf + stream->curReadPos, count ); + stream->curReadPos += count; + XP_ASSERT( stream->curReadPos <= stream->nBytesWritten ); +} /* mem_stream_getBytes */ + +static XP_U8 +mem_stream_getU8( XWStreamCtxt* p_sctx ) +{ + XP_U8 result; + mem_stream_getBytes( p_sctx, &result, sizeof(result) ); + return result; +} /* mem_stream_getU8 */ + +static XP_U16 +mem_stream_getU16( XWStreamCtxt* p_sctx ) +{ + XP_U16 result; + mem_stream_getBytes( p_sctx, &result, sizeof(result) ); + + return XP_NTOHS(result); +} /* mem_stream_getU16 */ + +static XP_U32 +mem_stream_getU32( XWStreamCtxt* p_sctx ) +{ + XP_U32 result; + mem_stream_getBytes( p_sctx, &result, sizeof(result) ); + return XP_NTOHL( result ); +} /* mem_stream_getU32 */ + +static XP_Bool +getOneBit( MemStreamCtxt* stream ) +{ + XP_U8 mask, rack; + XP_Bool result; + + if ( stream->nReadBits == 0 ) { + ++stream->curReadPos; + } + XP_ASSERT( stream->curReadPos <= stream->nBytesWritten ); + + rack = stream->buf[stream->curReadPos-1]; + mask = 1 << stream->nReadBits++; + result = (rack & mask) != 0; + + if ( stream->nReadBits == 8 ) { + stream->nReadBits = 0; + } + return result; +} /* getOneBit */ + +static XP_U32 +mem_stream_getBits( XWStreamCtxt* p_sctx, XP_U16 nBits ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + XP_U32 mask; + XP_U32 result = 0; + + for ( mask = 1L; nBits--; mask <<= 1 ) { + if ( getOneBit( stream ) ) { + result |= mask; + } + } + + return result; +} /* stream_getBits */ + +static void +mem_stream_putBytes( XWStreamCtxt* p_sctx, const void* whence, + XP_U16 count ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + XP_U32 newSize; + + if ( !stream->buf ) { + XP_ASSERT( stream->nBytesAllocated == 0 ); + stream->buf = (XP_U8*)XP_MALLOC( stream->mpool, STREAM_INCR_SIZE ); + stream->nBytesAllocated = STREAM_INCR_SIZE; + } + + /* I don't yet deal with getting asked to get/put a byte when in the + middle of doing bitwise stuff. It's probably just a matter of skipping + to the next byte, though -- and curPos should already be there. */ + if ( stream->nWriteBits != 0 ) { + stream->nWriteBits = 0; + } + + /* Reallocation. We may be writing into the middle of an existing stream, + and doing so may still require expanding the stream. So figure out if + the new size is bigger than what we have, and if so expand to hold it + plus something. */ + + newSize = stream->nBytesWritten + count; + if ( stream->curWritePos < stream->nBytesWritten ) { + newSize -= stream->nBytesWritten - stream->curWritePos; + } + + if ( newSize > stream->nBytesAllocated ) { + XP_ASSERT( newSize + STREAM_INCR_SIZE < 0xFFFF ); + stream->nBytesAllocated = (XP_U16)newSize + STREAM_INCR_SIZE; + stream->buf = + (XP_U8*)XP_REALLOC( stream->mpool, stream->buf, + stream->nBytesAllocated ); + } + + XP_MEMCPY( stream->buf + stream->curWritePos, whence, count ); + stream->nBytesWritten = (XP_U16)newSize; + stream->curWritePos += count; +} /* mem_stream_putBytes */ + +static void +mem_stream_putString( XWStreamCtxt* p_sctx, const char* whence ) +{ + XP_U16 len = XP_STRLEN( whence ); + mem_stream_putBytes( p_sctx, (void*)whence, len ); +} + +static void +mem_stream_putU8( XWStreamCtxt* p_sctx, XP_U8 data ) +{ + mem_stream_putBytes( p_sctx, &data, sizeof(data) ); +} /* mem_stream_putU8 */ + +static void +mem_stream_putU16( XWStreamCtxt* p_sctx, XP_U16 data ) +{ + data = XP_HTONS( data ); + mem_stream_putBytes( p_sctx, &data, sizeof(data) ); +} /* linux_common_stream_putU16 */ + +static void +mem_stream_putU32( XWStreamCtxt* p_sctx, XP_U32 data ) +{ + data = XP_HTONL( data ); + mem_stream_putBytes( p_sctx, &data, sizeof(data) ); +} /* mem_stream_putU32 */ + +static void +putOneBit( MemStreamCtxt* stream, XP_U16 bit ) +{ + XP_U8 mask, rack; + + if ( stream->nWriteBits == 0 ) { + if ( stream->curWritePos == stream->nBytesWritten ) { + stream_putU8( (XWStreamCtxt*)stream, 0 ); /* increments curPos */ + } else { + ++stream->curWritePos; + } + } + + XP_ASSERT( stream->curWritePos > 0 ); + rack = stream->buf[stream->curWritePos-1]; + mask = 1 << stream->nWriteBits++; + if ( bit ) { + rack |= mask; + } else { + rack &= ~mask; + } + stream->buf[stream->curWritePos-1] = rack; + + stream->nWriteBits %= 8; +} /* putOneBit */ + +static void +mem_stream_putBits( XWStreamCtxt* p_sctx, XP_U16 nBits, XP_U32 data + DBG_LINE_FILE_FORMAL ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; +#ifdef DEBUG + XP_U16 origBits = nBits; +#endif + + XP_ASSERT( nBits > 0 ); + + while ( nBits-- ) { + putOneBit( stream, (XP_U16)(((data & 1L) != 0)? 1:0) ); + data >>= 1; + } + XP_ASSERT( data == 0 ); /* otherwise nBits was too small */ +#ifdef DEBUG + if ( data != 0 ) { + XP_LOGF( "%s: nBits was %d from line %d, %s", __func__, + origBits, lin, fil ); + } +#endif +} /* mem_stream_putBits */ + +static void +mem_stream_copyFromStream( XWStreamCtxt* p_sctx, XWStreamCtxt* src, + XP_U16 nBytes ) +{ + while ( nBytes > 0 ) { + XP_U8 buf[256]; + XP_U16 len = sizeof(buf); + if ( nBytes < len ) { + len = nBytes; + } + stream_getBytes( src, buf, len ); + stream_putBytes( p_sctx, buf, len ); + nBytes -= len; + } +} /* mem_stream_copyFromStream */ + +static void +mem_stream_open( XWStreamCtxt* p_sctx ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + + stream->nBytesWritten = 0; + stream->curReadPos = START_OF_STREAM; + stream->curWritePos = START_OF_STREAM; +} /* mem_stream_open */ + +static void +mem_stream_close( XWStreamCtxt* p_sctx ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + + XP_ASSERT( stream->isOpen ); + + if ( !!stream->onClose ) { + (*stream->onClose)( p_sctx, stream->closure ); + } + stream->isOpen = XP_FALSE; +} /* mem_stream_close */ + +static XP_U16 +mem_stream_getSize( XWStreamCtxt* p_sctx ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + XP_U16 size = stream->nBytesWritten - stream->curReadPos; + return size; +} /* mem_stream_getSize */ + +static XP_PlayerAddr +mem_stream_getAddress( XWStreamCtxt* p_sctx ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + return stream->channelNo; +} /* mem_stream_getAddress */ + +static void +mem_stream_setAddress( XWStreamCtxt* p_sctx, XP_PlayerAddr channelNo ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + stream->channelNo = channelNo; +} /* mem_stream_getAddress */ + +static void +mem_stream_setVersion( XWStreamCtxt* p_sctx, XP_U16 vers ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + stream->version = vers; +} /* mem_stream_setVersion */ + +static XP_U16 +mem_stream_getVersion( XWStreamCtxt* p_sctx ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + return stream->version; +} /* mem_stream_getVersion */ + +static void +mem_stream_setOnCloseProc( XWStreamCtxt* p_sctx, MemStreamCloseCallback proc ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + stream->onClose = proc; +} + +static XWStreamPos +mem_stream_getPos( XWStreamCtxt* p_sctx, PosWhich which ) +{ + XWStreamPos result; + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + + if ( which == POS_WRITE ) { + result = (stream->curWritePos << 3) | stream->nWriteBits; + } else { + result = (stream->curReadPos << 3) | stream->nReadBits; + } + + return result; +} /* mem_stream_getPos */ + +static XWStreamPos +mem_stream_setPos( XWStreamCtxt* p_sctx, XWStreamPos newpos, PosWhich which ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + XWStreamPos oldPos = mem_stream_getPos( p_sctx, which ); + + if ( which == POS_WRITE ) { + stream->nWriteBits = (XP_U8)BIT_PART(newpos); + stream->curWritePos = (XP_U32)BYTE_PART(newpos); + } else { + stream->nReadBits = (XP_U8)BIT_PART(newpos); + stream->curReadPos = (XP_U32)BYTE_PART(newpos); + } + + return oldPos; +} /* mem_stream_setPos */ + +static void +mem_stream_destroy( XWStreamCtxt* p_sctx ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_sctx; + + if ( stream->isOpen ) { + stream_close( p_sctx ); + } + + if ( !!stream->buf ) { + XP_FREE( stream->mpool, stream->buf ); + } + + XP_FREE( stream->mpool, stream ); +} /* mem_stream_destroy */ + +static StreamCtxVTable* +make_vtable( MemStreamCtxt* stream ) +{ + StreamCtxVTable* vtable; + XP_ASSERT( !stream->vtable ); + XP_ASSERT( sizeof(stream->vtable) == sizeof(vtable) ); + vtable = (StreamCtxVTable*)XP_MALLOC( stream->mpool, + sizeof(*stream->vtable) ); + + SET_VTABLE_ENTRY( vtable, stream_getU8, mem ); + SET_VTABLE_ENTRY( vtable, stream_getBytes, mem ); + SET_VTABLE_ENTRY( vtable, stream_getU16, mem ); + SET_VTABLE_ENTRY( vtable, stream_getU32, mem ); + SET_VTABLE_ENTRY( vtable, stream_getBits, mem ); + + SET_VTABLE_ENTRY( vtable, stream_putU8, mem ); + SET_VTABLE_ENTRY( vtable, stream_putBytes, mem ); + SET_VTABLE_ENTRY( vtable, stream_putString, mem ); + SET_VTABLE_ENTRY( vtable, stream_putU16, mem ); + SET_VTABLE_ENTRY( vtable, stream_putU32, mem ); + SET_VTABLE_ENTRY( vtable, stream_putBits, mem ); + + SET_VTABLE_ENTRY( vtable, stream_copyFromStream, mem ); + + SET_VTABLE_ENTRY( vtable, stream_setPos, mem ); + SET_VTABLE_ENTRY( vtable, stream_getPos, mem ); + + SET_VTABLE_ENTRY( vtable, stream_destroy, mem ); + SET_VTABLE_ENTRY( vtable, stream_open, mem ); + SET_VTABLE_ENTRY( vtable, stream_close, mem ); + + SET_VTABLE_ENTRY( vtable, stream_getSize, mem ); + SET_VTABLE_ENTRY( vtable, stream_getAddress, mem ); + SET_VTABLE_ENTRY( vtable, stream_setAddress, mem ); + + SET_VTABLE_ENTRY( vtable, stream_setVersion, mem ); + SET_VTABLE_ENTRY( vtable, stream_getVersion, mem ); + + SET_VTABLE_ENTRY( vtable, stream_setOnCloseProc, mem ); + + return vtable; +} /* make_vtable */ + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/memstream.h b/xwords4/common/memstream.h new file mode 100644 index 000000000..ea9c1245a --- /dev/null +++ b/xwords4/common/memstream.h @@ -0,0 +1,46 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _MEMSTREAM_H_ +#define _MEMSTREAM_H_ + + +#include "comtypes.h" +#include "mempool.h" +#include "vtabmgr.h" + +#ifdef CPLUS +extern "C" { +#endif + +typedef void (*MemStreamCloseCallback)( XWStreamCtxt* stream, + void* closure ); + +XWStreamCtxt* mem_stream_make( MPFORMAL VTableMgr* vtmgr, + void* closure, + XP_PlayerAddr addr, /* should be in a + subclass */ + MemStreamCloseCallback onCloseWritten ); + + +#ifdef CPLUS +} +#endif + +#endif diff --git a/xwords4/common/model.c b/xwords4/common/model.c new file mode 100644 index 000000000..3d00e760b --- /dev/null +++ b/xwords4/common/model.c @@ -0,0 +1,1837 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* #include */ + +#include "comtypes.h" +#include "modelp.h" +#include "xwstream.h" +#include "util.h" +#include "pool.h" +#include "game.h" +#include "memstream.h" +#include "strutils.h" +#include "LocalizedStrIncludes.h" + +#ifdef CPLUS +extern "C" { +#endif + +#define mEND 0x6d454e44 + +#define MAX_PASSES 2 /* how many times can all players pass? */ + +/****************************** prototypes ******************************/ +typedef void (*MovePrintFuncPre)(ModelCtxt*, XP_U16, StackEntry*, void*); +typedef void (*MovePrintFuncPost)(ModelCtxt*, XP_U16, StackEntry*, XP_S16, + void*); + +static void incrPendingTileCountAt( ModelCtxt* model, XP_U16 col, + XP_U16 row ); +static void decrPendingTileCountAt( ModelCtxt* model, XP_U16 col, + XP_U16 row ); +static void notifyBoardListeners( ModelCtxt* model, XP_U16 turn, + XP_U16 col, XP_U16 row, XP_Bool added ); +static void notifyTrayListeners( ModelCtxt* model, XP_U16 turn, + XP_S16 index1, XP_S16 index2 ); +static void notifyDictListeners( ModelCtxt* model, DictionaryCtxt* oldDict, + DictionaryCtxt* newDict ); + +static CellTile getModelTileRaw( const ModelCtxt* model, XP_U16 col, + XP_U16 row ); +static void setModelTileRaw( ModelCtxt* model, XP_U16 col, XP_U16 row, + CellTile tile ); +static void assignPlayerTiles( ModelCtxt* model, XP_S16 turn, + TrayTileSet* tiles ); +static void makeTileTrade( ModelCtxt* model, XP_S16 player, + TrayTileSet* oldTiles, TrayTileSet* newTiles ); +static XP_S16 commitTurn( ModelCtxt* model, XP_S16 turn, + TrayTileSet* newTiles, XWStreamCtxt* stream, + XP_Bool useStack ); +static void buildModelFromStack( ModelCtxt* model, StackCtxt* stack, + XWStreamCtxt* stream, + MovePrintFuncPre mpfpr, + MovePrintFuncPost mpfpo, + void* closure ); +static void setPendingCounts( ModelCtxt* model, XP_S16 turn ); +static void loadPlayerCtxt( XWStreamCtxt* stream, PlayerCtxt* pc ); +static void writePlayerCtxt( XWStreamCtxt* stream, PlayerCtxt* pc ); +static XP_U16 model_getRecentPassCount( ModelCtxt* model ); + + +/***************************************************************************** + * + ****************************************************************************/ +ModelCtxt* +model_make( MPFORMAL DictionaryCtxt* dict, XW_UtilCtxt* util, XP_U16 nCols, + XP_U16 nRows ) +{ + ModelCtxt* result = (ModelCtxt*)XP_MALLOC( mpool, sizeof( *result ) ); + if ( result != NULL ) { + XP_MEMSET( result, 0, sizeof(*result) ); + MPASSIGN(result->vol.mpool, mpool); + + result->vol.util = util; + + model_init( result, nCols, nRows ); + + XP_ASSERT( !!util->gameInfo ); + result->vol.gi = util->gameInfo; + + model_setDictionary( result, dict ); + } + + return result; +} /* model_make */ + +ModelCtxt* +model_makeFromStream( MPFORMAL XWStreamCtxt* stream, DictionaryCtxt* dict, + XW_UtilCtxt* util ) +{ + ModelCtxt* model; + XP_U16 nCols, nRows; + short i; + XP_Bool hasDict; + XP_U16 nPlayers; + XP_U16 version = stream_getVersion( stream ); + + XP_ASSERT( !!dict ); + + nCols = (XP_U16)stream_getBits( stream, NUMCOLS_NBITS ); + nRows = (XP_U16)stream_getBits( stream, NUMCOLS_NBITS ); + + hasDict = (version >= STREAM_VERS_MODEL_NO_DICT) + ? XP_FALSE : stream_getBits( stream, 1 ); + nPlayers = (XP_U16)stream_getBits( stream, NPLAYERS_NBITS ); + + if ( hasDict ) { + DictionaryCtxt* savedDict = util_makeEmptyDict( util ); + dict_loadFromStream( savedDict, stream ); + dict_destroy( savedDict ); + } + + model = model_make( MPPARM(mpool) dict, util, nCols, nRows ); + model->nPlayers = nPlayers; + + stack_loadFromStream( model->vol.stack, stream ); + + buildModelFromStack( model, model->vol.stack, (XWStreamCtxt*)NULL, + (MovePrintFuncPre)NULL, + (MovePrintFuncPost)NULL, NULL ); + + for ( i = 0; i < model->nPlayers; ++i ) { + loadPlayerCtxt( stream, &model->players[i] ); + setPendingCounts( model, i ); + invalidateScore( model, i ); + } + + XP_ASSERT( stream_getU32( stream ) == mEND ); + + return model; +} /* model_makeFromStream */ + +void +model_writeToStream( ModelCtxt* model, XWStreamCtxt* stream ) +{ + short i; + + stream_putBits( stream, NUMCOLS_NBITS, model->nCols ); + stream_putBits( stream, NUMCOLS_NBITS, model->nRows ); + + /* we have two bits for nPlayers, so range must be 0..3, not 1..4 */ + stream_putBits( stream, NPLAYERS_NBITS, model->nPlayers ); + + stack_writeToStream( model->vol.stack, stream ); + + for ( i = 0; i < model->nPlayers; ++i ) { + writePlayerCtxt( stream, &model->players[i] ); + } + +#ifdef DEBUG + stream_putU32( stream, mEND ); +#endif +} /* model_writeToStream */ + +void +model_init( ModelCtxt* model, XP_U16 nCols, XP_U16 nRows ) +{ + ModelVolatiles vol = model->vol; + + XP_ASSERT( model != NULL ); + XP_MEMSET( model, 0, sizeof( *model ) ); + XP_MEMSET( &model->tiles, TILE_EMPTY_BIT, sizeof(model->tiles) ); + + model->nCols = nCols; + model->nRows = nRows; + + model->vol = vol; + + if ( !!model->vol.stack ) { + stack_init( model->vol.stack ); + } else { + model->vol.stack = stack_make( MPPARM(model->vol.mpool) + util_getVTManager(model->vol.util)); + } +} /* model_init */ + +void +model_destroy( ModelCtxt* model ) +{ + stack_destroy( model->vol.stack ); + /* is this it!? */ + XP_FREE( model->vol.mpool, model ); +} /* model_destroy */ + +static void +buildModelFromStack( ModelCtxt* model, StackCtxt* stack, + XWStreamCtxt* stream, + MovePrintFuncPre mpf_pre, MovePrintFuncPost mpf_post, + void* closure ) +{ + StackEntry entry; + XP_U16 i; + XP_S16 moveScore = 0; /* keep compiler happy */ + + for ( i = 0; stack_getNthEntry( stack, i, &entry ); ++i ) { + + if ( !!mpf_pre ) { + (*mpf_pre)( model, i, &entry, closure ); + } + + switch ( entry.moveType ) { + case MOVE_TYPE: + + model_makeTurnFromMoveInfo( model, entry.playerNum, + &entry.u.move.moveInfo); + moveScore = commitTurn( model, entry.playerNum, + &entry.u.move.newTiles, stream, XP_FALSE); + break; + case TRADE_TYPE: + makeTileTrade( model, entry.playerNum, &entry.u.trade.oldTiles, + &entry.u.trade.newTiles ); + break; + case ASSIGN_TYPE: + assignPlayerTiles( model, entry.playerNum, + &entry.u.assign.tiles ); + break; + case PHONY_TYPE: /* nothing to add */ + model_makeTurnFromMoveInfo( model, entry.playerNum, + &entry.u.phony.moveInfo); + /* do something here to cause it to print */ + (void)getCurrentMoveScoreIfLegal( model, entry.playerNum, stream, + &moveScore ); + moveScore = 0; + model_resetCurrentTurn( model, entry.playerNum ); + + break; + default: + XP_ASSERT(0); + } + + if ( !!mpf_post ) { + (*mpf_post)( model, i, &entry, moveScore, closure ); + } + } +} /* buildModelFromStack */ + +void +model_setNPlayers( ModelCtxt* model, XP_U16 nPlayers ) +{ + model->nPlayers = nPlayers; +} /* model_setNPlayers */ + +void +model_setDictionary( ModelCtxt* model, DictionaryCtxt* dict ) +{ + DictionaryCtxt* oldDict = model->vol.dict; + model->vol.dict = dict; + + if ( !!dict ) { + XP_U16 nFaces = dict_numTileFaces( dict ); + XP_ASSERT( !!model->vol.stack ); + stack_setBitsPerTile( model->vol.stack, nFaces <= 32? 5 : 6 ); + + notifyDictListeners( model, oldDict, dict ); + } +} /* model_setDictionary */ + +DictionaryCtxt* +model_getDictionary( ModelCtxt* model ) +{ + XP_ASSERT( !!model->vol.dict ); + return model->vol.dict; +} /* model_getDictionary */ + +static XP_Bool +getPendingTileFor( const ModelCtxt* model, XP_U16 turn, XP_U16 col, XP_U16 row, + CellTile* cellTile ) +{ + XP_Bool found = XP_FALSE; + const PlayerCtxt* player; + const PendingTile* pendings; + XP_U16 i; + + player = &model->players[turn]; + pendings = player->pendingTiles; + for ( i = 0; i < player->nPending; ++i ) { + + if ( (pendings->col == col) && (pendings->row == row) ) { + *cellTile = pendings->tile; + found = XP_TRUE; + XP_ASSERT ( (*cellTile & TILE_EMPTY_BIT) == 0 ); + break; + } + ++pendings; + } + + return found; +} /* getPendingTileFor */ + +XP_Bool +model_getTile( const ModelCtxt* model, XP_U16 col, XP_U16 row, + XP_Bool getPending, XP_S16 turn, Tile* tileP, XP_Bool* isBlank, + XP_Bool* pendingP, XP_Bool* recentP ) +{ + CellTile cellTile = getModelTileRaw( model, col, row ); + XP_Bool pending = XP_FALSE; + + if ( (cellTile & TILE_PENDING_BIT) != 0 ) { + if ( getPending + && getPendingTileFor( model, turn, col, row, &cellTile ) ) { + + /* it's pending, but caller doesn't want to see it */ + pending = XP_TRUE; + } else { + cellTile = EMPTY_TILE; + } + } + + /* this needs to happen after the above b/c cellTile gets changed */ + if ( (cellTile & TILE_EMPTY_BIT) != 0 ) { + return XP_FALSE; + } + + *tileP = cellTile & TILE_VALUE_MASK; + *isBlank = IS_BLANK(cellTile); + *pendingP = pending; + if ( !!recentP ) { + *recentP = (cellTile & PREV_MOVE_BIT) != 0; + } + + return XP_TRUE; +} /* model_getTile */ + +void +model_listPlacedBlanks( ModelCtxt* model, XP_U16 turn, + XP_Bool includePending, BlankQueue* bcp ) +{ + XP_U16 nCols = model_numCols( model ); + XP_U16 nRows = model_numRows( model ); + XP_U16 col, row; + + XP_U16 nBlanks = 0; + + for ( row = 0; row < nRows; ++row ) { + for ( col = 0; col < nCols; ++col ) { + CellTile cellTile = getModelTileRaw( model, col, row ); + + if ( (cellTile & TILE_PENDING_BIT) != 0 ) { + if ( !includePending || + !getPendingTileFor( model, turn, col, row, &cellTile ) ) { + continue; + } + } + + if ( (cellTile & TILE_BLANK_BIT) != 0 ) { + bcp->col[nBlanks] = (XP_U8)col; + bcp->row[nBlanks] = (XP_U8)row; + ++nBlanks; + } + } + } + + bcp->nBlanks = nBlanks; +} /* model_listPlacedBlanks */ + +void +model_foreachPrevCell( ModelCtxt* model, BoardListener bl, void* closure ) +{ + XP_U16 col, row; + + for ( col = 0; col < model->nCols; ++col ) { + for ( row = 0; row < model->nRows; ++row) { + CellTile tile = getModelTileRaw( model, col, row ); + if ( (tile & PREV_MOVE_BIT) != 0 ) { + (*bl)( closure, (XP_U16)CELL_OWNER(tile), col, row, XP_FALSE ); + } + } + } +} /* model_foreachPrevCell */ + +static void +clearAndNotify( void* closure, XP_U16 XP_UNUSED(turn), XP_U16 col, XP_U16 row, + XP_Bool XP_UNUSED(added) ) +{ + ModelCtxt* model = (ModelCtxt*)closure; + CellTile tile = getModelTileRaw( model, col, row ); + setModelTileRaw( model, col, row, (CellTile)(tile & ~PREV_MOVE_BIT) ); + + notifyBoardListeners( model, (XP_U16)CELL_OWNER(tile), col, row, + XP_FALSE ); +} /* clearAndNotify */ + +static void +clearLastMoveInfo( ModelCtxt* model ) +{ + model_foreachPrevCell( model, clearAndNotify, model ); +} /* clearLastMoveInfo */ + +static void +invalLastMove( ModelCtxt* model ) +{ + if ( !!model->vol.boardListenerFunc ) { + model_foreachPrevCell( model, model->vol.boardListenerFunc, + model->vol.boardListenerData ); + } +} /* invalLastMove */ + +void +model_foreachPendingCell( ModelCtxt* model, XP_S16 turn, + BoardListener bl, void* closure ) +{ + PendingTile* pt; + PlayerCtxt* player; + XP_S16 count; + + XP_ASSERT( turn >= 0 ); + player = &model->players[turn]; + count = player->nPending; + + for ( pt = player->pendingTiles; count--; ++pt ) { + XP_U16 col, row; + + col = pt->col; + row = pt->row; + + (*bl)( closure, turn, pt->col, pt->row, XP_FALSE ); + } +} /* model_invalPendingCells */ + +XP_U16 +model_getCellOwner( ModelCtxt* model, XP_U16 col, XP_U16 row ) +{ + CellTile tile; + XP_U16 result; + + tile = getModelTileRaw( model, col, row ); + + result = CELL_OWNER(tile); + + return result; +} /* model_getCellOwner */ + +static void +setModelTileRaw( ModelCtxt* model, XP_U16 col, XP_U16 row, CellTile tile ) +{ + XP_ASSERT( col < MAX_COLS ); + XP_ASSERT( row < MAX_ROWS ); + model->tiles[col][row] = tile; +} /* model_setTile */ + +static CellTile +getModelTileRaw( const ModelCtxt* model, XP_U16 col, XP_U16 row ) +{ + XP_ASSERT( col < MAX_COLS ); + XP_ASSERT( row < MAX_ROWS ); + return model->tiles[col][row]; +} /* getModelTileRaw */ + +static void +undoFromMoveInfo( ModelCtxt* model, XP_U16 turn, Tile blankTile, MoveInfo* mi ) +{ + XP_U16 col, row, i; + XP_U16* other; + MoveInfoTile* tinfo; + + col = row = mi->commonCoord; + other = mi->isHorizontal? &col: &row; + + for ( tinfo = mi->tiles, i = 0; i < mi->nTiles; ++tinfo, ++i ) { + Tile tile; + + *other = tinfo->varCoord; + tile = tinfo->tile; + + setModelTileRaw( model, col, row, EMPTY_TILE ); + notifyBoardListeners( model, turn, col, row, XP_FALSE ); + + if ( IS_BLANK(tile) ) { + tile = blankTile; + } + model_addPlayerTile( model, turn, -1, tile ); + } + + adjustScoreForUndone( model, mi, turn ); +} /* undoFromMoveInfo */ + +/* Remove tiles in a set from tray and put them back in the pool. + */ +static void +replaceNewTiles( ModelCtxt* model, PoolContext* pool, XP_U16 turn, + TrayTileSet* tileSet ) +{ + Tile* t; + XP_U16 i, nTiles; + + for ( t = tileSet->tiles, i = 0, nTiles = tileSet->nTiles; + i < nTiles; ++i ) { + XP_S16 index; + Tile tile = *t++; + + index = model_trayContains( model, turn, tile ); + XP_ASSERT( index >= 0 ); + model_removePlayerTile( model, turn, index ); + } + if ( !!pool ) { + pool_replaceTiles( pool, tileSet); + } +} /* replaceNewTiles */ + +/* Turn the most recent move into a phony. + */ +void +model_rejectPreviousMove( ModelCtxt* model, PoolContext* pool, XP_U16* turn ) +{ + StackCtxt* stack = model->vol.stack; + StackEntry entry; + Tile blankTile = dict_getBlankTile( model_getDictionary(model) ); + + stack_popEntry( stack, &entry ); + XP_ASSERT( entry.moveType == MOVE_TYPE ); + + replaceNewTiles( model, pool, entry.playerNum, &entry.u.move.newTiles ); + undoFromMoveInfo( model, entry.playerNum, blankTile, + &entry.u.move.moveInfo ); + + stack_addPhony( stack, entry.playerNum, &entry.u.phony.moveInfo ); + + *turn = entry.playerNum; +} /* model_rejectPreviousMove */ + +/* Undo a move, but only if it's the move we're expecting to undo (as + * indicated by *moveNumP, if >= 0). + */ +XP_Bool +model_undoLatestMoves( ModelCtxt* model, PoolContext* pool, + XP_U16 nMovesSought, XP_U16* turnP, XP_S16* moveNumP ) +{ + StackCtxt* stack = model->vol.stack; + StackEntry entry; + XP_U16 turn = 0; + Tile blankTile = dict_getBlankTile( model_getDictionary(model) ); + XP_Bool success = XP_TRUE; + XP_S16 moveSought = *moveNumP; + XP_U16 nMovesUndone; + XP_U16 nStackEntries; + + nStackEntries = stack_getNEntries( stack ); + if ( nStackEntries < nMovesSought ) { + return XP_FALSE; + } else if ( nStackEntries <= model->nPlayers ) { + return XP_FALSE; + } + + for ( nMovesUndone = 0; success && nMovesUndone < nMovesSought; ) { + + success = stack_popEntry( stack, &entry ); + if ( success ) { + ++nMovesUndone; + + if ( moveSought < 0 ) { + moveSought = entry.moveNum - 1; + } else if ( moveSought-- != entry.moveNum ) { + success = XP_FALSE; + break; + } + + turn = entry.playerNum; + model_resetCurrentTurn( model, turn ); + + if ( entry.moveType == MOVE_TYPE ) { + + /* get the tiles out of player's tray and back into the + pool */ + replaceNewTiles( model, pool, turn, &entry.u.move.newTiles); + + undoFromMoveInfo( model, turn, blankTile, + &entry.u.move.moveInfo ); + } else if ( entry.moveType == TRADE_TYPE ) { + + if ( pool != NULL ) { + /* If there's no pool, assume we're doing this for + scoring purposes only. */ + replaceNewTiles( model, pool, turn, + &entry.u.trade.newTiles ); + + pool_removeTiles( pool, &entry.u.trade.oldTiles ); + assignPlayerTiles( model, turn, &entry.u.trade.oldTiles ); + } + + } else if ( entry.moveType == PHONY_TYPE ) { + + /* nothing to do, since nothing happened */ + + } else { + XP_ASSERT( entry.moveType == ASSIGN_TYPE ); + success = XP_FALSE; + break; + } + } + } + + /* Find the first MOVE still on the stack and highlight its tiles since + they're now the most recent move. Trades and lost turns ignored. */ + nStackEntries = stack_getNEntries( stack ); + for ( ; ; ) { + StackEntry entry; + if ( nStackEntries == 0 || + !stack_getNthEntry( stack, nStackEntries - 1, &entry ) ) { + break; + } + if ( entry.moveType == MOVE_TYPE ) { + XP_U16 nTiles = entry.u.move.moveInfo.nTiles; + XP_U16 col, row; + XP_U16* varies; + + if ( entry.u.move.moveInfo.isHorizontal ) { + row = entry.u.move.moveInfo.commonCoord; + varies = &col; + } else { + col = entry.u.move.moveInfo.commonCoord; + varies = &row; + } + + while ( nTiles-- ) { + CellTile tile; + + *varies = entry.u.move.moveInfo.tiles[nTiles].varCoord; + tile = getModelTileRaw( model, col, row ); + setModelTileRaw( model, col, row, + (CellTile)(tile | PREV_MOVE_BIT) ); + notifyBoardListeners( model, entry.playerNum, col, row, + XP_FALSE ); + } + break; + } else if ( entry.moveType == ASSIGN_TYPE ) { + break; + } else { + --nStackEntries; /* look at the next one */ + } + } + + if ( nMovesUndone != nMovesSought ) { + success = XP_FALSE; + } + + if ( success ) { + *turnP = turn; + *moveNumP = entry.moveNum; + } else { + while ( nMovesUndone-- ) { + stack_redo( stack ); + } + } + + return success; +} /* model_undoLatestMoves */ + +void +model_trayToStream( ModelCtxt* model, XP_S16 turn, XWStreamCtxt* stream ) +{ + PlayerCtxt* player = &model->players[turn]; + + traySetToStream( stream, &player->trayTiles ); +} /* model_trayToStream */ + +void +model_currentMoveToStream( ModelCtxt* model, XP_S16 turn, + XWStreamCtxt* stream ) +{ + PlayerCtxt* player = &model->players[turn]; + XP_S16 numTiles = player->nPending; + + stream_putBits( stream, NTILES_NBITS, numTiles ); + + while ( numTiles-- ) { + Tile tile; + XP_U16 col, row; + XP_Bool isBlank; + + model_getCurrentMoveTile( model, turn, &numTiles, &tile, + &col, &row, &isBlank ); + XP_ASSERT( numTiles >= 0 ); + stream_putBits( stream, TILE_NBITS, tile ); + stream_putBits( stream, NUMCOLS_NBITS, col ); + stream_putBits( stream, NUMCOLS_NBITS, row ); + stream_putBits( stream, 1, isBlank ); + } +} /* model_turnToStream */ + +/* Take stream as the source of info about what tiles to move from tray to + * board. Undo any current move first -- a player on this device might be + * using the board as scratch during another player's turn. For each tile, + * assert that it's in the tray, remove it from the tray, and place it on the + * board. + */ +void +model_makeTurnFromStream( ModelCtxt* model, XP_U16 playerNum, + XWStreamCtxt* stream ) +{ + XP_U16 numTiles; + Tile blank = dict_getBlankTile( model->vol.dict ); + + model_resetCurrentTurn( model, playerNum ); + + numTiles = (XP_U16)stream_getBits( stream, NTILES_NBITS ); + + XP_STATUSF( "model_makeTurnFromStream: numTiles=%d\n", numTiles ); + + while ( numTiles-- ) { + XP_S16 foundAt; + Tile moveTile; + Tile tileFace = (Tile)stream_getBits( stream, TILE_NBITS ); + XP_U16 col = (XP_U16)stream_getBits( stream, NUMCOLS_NBITS ); + XP_U16 row = (XP_U16)stream_getBits( stream, NUMCOLS_NBITS ); + XP_Bool isBlank = stream_getBits( stream, 1 ); + + /* This code gets called both for the server, which has all the + tiles in its tray, and for a client, which has "EMPTY" tiles + only. If it's the empty case, we stuff a real tile into the + tray before falling through to the normal case */ + + if ( isBlank ) { + moveTile = blank; + } else { + moveTile = tileFace; + } + + foundAt = model_trayContains( model, playerNum, moveTile ); + if ( foundAt == -1 ) { + XP_ASSERT( EMPTY_TILE==model_getPlayerTile(model, playerNum, 0)); + + (void)model_removePlayerTile( model, playerNum, -1 ); + model_addPlayerTile( model, playerNum, -1, moveTile ); + } + + model_moveTrayToBoard( model, playerNum, col, row, foundAt, tileFace); + } +} /* model_makeMoveFromStream */ + +void +model_makeTurnFromMoveInfo( ModelCtxt* model, XP_U16 playerNum, + MoveInfo* newMove ) +{ + XP_U16 col, row, i; + XP_U16* other; + MoveInfoTile* tinfo; + Tile blank; + XP_U16 numTiles; + + blank = dict_getBlankTile( model->vol.dict ); + numTiles = newMove->nTiles; + + col = row = newMove->commonCoord; /* just assign both */ + other = newMove->isHorizontal? &col: &row; + + for ( tinfo = newMove->tiles, i = 0; i < numTiles; ++i, ++tinfo ) { + XP_S16 tileIndex; + Tile tile = tinfo->tile; + + if ( IS_BLANK(tile) ) { + tile = blank; + } + + tileIndex = model_trayContains( model, playerNum, tile ); + + XP_ASSERT( tileIndex >= 0 ); + + *other = tinfo->varCoord; + model_moveTrayToBoard( model, (XP_S16)playerNum, col, row, tileIndex, + (Tile)(tinfo->tile & TILE_VALUE_MASK) ); + } +} /* model_makeTurnFromMoveInfo */ + +void +model_countAllTrayTiles( ModelCtxt* model, XP_U16* counts, + XP_S16 excludePlayer ) +{ + PlayerCtxt* player; + XP_S16 nPlayers = model->nPlayers; + XP_S16 i; + Tile blank; + + XP_ASSERT( !!model->vol.dict ); + blank = dict_getBlankTile( model->vol.dict ); + + for ( i = 0, player = model->players; i < nPlayers; ++i, ++player ) { + if ( i != excludePlayer ) { + XP_U16 nTiles = player->nPending; + PendingTile* pt; + Tile* tiles; + + /* first the pending tiles */ + for ( pt = player->pendingTiles; nTiles--; ++pt ) { + Tile tile = pt->tile; + if ( IS_BLANK(tile) ) { + tile = blank; + } else { + tile &= TILE_VALUE_MASK; + } + XP_ASSERT( tile <= MAX_UNIQUE_TILES ); + ++counts[tile]; + } + + /* then the tiles still in the tray */ + nTiles = player->trayTiles.nTiles; + tiles = player->trayTiles.tiles; + while ( nTiles-- ) { + ++counts[*tiles++]; + } + } + } +} /* model_countAllTrayTiles */ + +XP_S16 +model_trayContains( ModelCtxt* model, XP_S16 turn, Tile tile ) +{ + PlayerCtxt* player; + XP_S16 i; + XP_S16 result = -1; + + XP_ASSERT( turn >= 0 ); + XP_ASSERT( turn < model->nPlayers ); + + player = &model->players[turn]; + + /* search from top down so don't pull out of below divider */ + for ( i = player->trayTiles.nTiles - 1; i >= 0 ; --i ) { + Tile playerTile = player->trayTiles.tiles[i]; + if ( playerTile == tile ) { + result = i; + break; + } + } + + return result; +} /* model_trayContains */ + +XP_U16 +model_getCurrentMoveCount( ModelCtxt* model, XP_S16 turn ) +{ + PlayerCtxt* player; + XP_ASSERT( turn >= 0 ); + player = &model->players[turn]; + return player->nPending; +} /* model_getCurrentMoveCount */ + +void +model_getCurrentMoveTile( ModelCtxt* model, XP_S16 turn, XP_S16* index, + Tile* tile, XP_U16* col, XP_U16* row, + XP_Bool* isBlank ) +{ + PlayerCtxt* player; + PendingTile* pt; + XP_ASSERT( turn >= 0 ); + + player = &model->players[turn]; + XP_ASSERT( *index < player->nPending ); + + if ( *index < 0 ) { + *index = player->nPending - 1; + } + + pt = &player->pendingTiles[*index]; + + *col = pt->col; + *row = pt->row; + *isBlank = (pt->tile & TILE_BLANK_BIT) != 0; + *tile = pt->tile & TILE_VALUE_MASK; +} /* model_getCurrentMoveTile */ + +static Tile +removePlayerTile( ModelCtxt* model, XP_S16 turn, XP_S16 index ) +{ + PlayerCtxt* player = &model->players[turn]; + Tile tile; + short i; + + XP_ASSERT( index < player->trayTiles.nTiles ); + + tile = player->trayTiles.tiles[index]; + + --player->trayTiles.nTiles; + for ( i = index; i < player->trayTiles.nTiles; ++i ) { + player->trayTiles.tiles[i] = player->trayTiles.tiles[i+1]; + } + + return tile; +} /* removePlayerTile */ + +Tile +model_removePlayerTile( ModelCtxt* model, XP_S16 turn, XP_S16 index ) +{ + Tile tile; + PlayerCtxt* player = &model->players[turn]; + if ( index < 0 ) { + index = player->trayTiles.nTiles - 1; + } + tile = removePlayerTile( model, turn, index ); + notifyTrayListeners( model, turn, index, player->trayTiles.nTiles ); + return tile; +} /* model_removePlayerTile */ + +void +model_packTilesUtil( ModelCtxt* model, PoolContext* pool, + XP_Bool includeBlank, + XP_U16* nUsed, XP_UCHAR4* texts, + Tile* tiles ) +{ + DictionaryCtxt* dict = model->vol.dict; + XP_U16 nFaces = dict_numTileFaces( dict ); + Tile blankFace = dict_getBlankTile( dict ); + Tile tile; + XP_U16 nFacesAvail = 0; + + XP_ASSERT( nFaces <= *nUsed ); + + for ( tile = 0; tile < nFaces; ++tile ) { + XP_U16 nChars; + + if ( includeBlank ) { + XP_ASSERT( !!pool ); + if ( pool_getNTilesLeftFor( pool, tile ) == 0 ) { + continue; + } + } else if ( tile == blankFace ) { + continue; + } + + tiles[nFacesAvail] = tile; + nChars = dict_tilesToString( dict, &tile, 1, + (XP_UCHAR*)&texts[nFacesAvail], + sizeof(texts[0]) ); + XP_ASSERT( nChars < sizeof(texts[0]) ); + ++nFacesAvail; + } + + *nUsed = nFacesAvail; + +} /* model_packTilesUtil */ + +static Tile +askBlankTile( ModelCtxt* model, XP_U16 turn ) +{ + XP_U16 nUsed = MAX_UNIQUE_TILES; + XP_S16 chosen; + XP_UCHAR4 tfaces[MAX_UNIQUE_TILES]; + Tile tiles[MAX_UNIQUE_TILES]; + PickInfo pi; + + pi.why = PICK_FOR_BLANK; + pi.nTotal = 1; + pi.thisPick = 1; + + model_packTilesUtil( model, NULL, XP_FALSE, + &nUsed, tfaces, tiles ); + + chosen = util_userPickTile( model->vol.util, &pi, + turn, (const XP_UCHAR4*)tfaces, nUsed ); + + if ( chosen < 0 ) { + chosen = 0; + } + return tiles[chosen]; +} /* askBlankTile */ + +void +model_moveTrayToBoard( ModelCtxt* model, XP_S16 turn, XP_U16 col, XP_U16 row, + XP_S16 tileIndex, Tile blankFace ) +{ + PlayerCtxt* player; + PendingTile* pt; + + Tile tile = model_removePlayerTile( model, turn, tileIndex ); + + if ( tile == dict_getBlankTile(model->vol.dict) ) { + if ( blankFace != EMPTY_TILE ) { + tile = blankFace; + } else { + XP_ASSERT( turn >= 0 ); + tile = askBlankTile( model, (XP_U16)turn ); + } + tile |= TILE_BLANK_BIT; + } + + player = &model->players[turn]; + + if ( player->nPending == 0 ) { + invalLastMove( model ); + } + + pt = &player->pendingTiles[player->nPending++]; + XP_ASSERT( player->nPending <= MAX_TRAY_TILES ); + + pt->tile = tile; + pt->col = (XP_U8)col; + pt->row = (XP_U8)row; + + invalidateScore( model, turn ); + incrPendingTileCountAt( model, col, row ); + + notifyBoardListeners( model, turn, col, row, XP_TRUE ); +} /* model_moveTrayToBoard */ + +void +model_moveBoardToTray( ModelCtxt* model, XP_S16 turn, + XP_U16 col, XP_U16 row, XP_U16 trayOffset ) +{ + XP_S16 index; + PlayerCtxt* player; + short i; + PendingTile* pt; + Tile tile; + + player = &model->players[turn]; + for ( pt = player->pendingTiles, index = 0; + index < player->nPending; + ++index, ++pt ) { + if ( pt->col == col && pt->row == row ) { + break; + } + } + + /* if we're called from putBackOtherPlayersTiles there may be nothing + here */ + if ( index < player->nPending ) { + decrPendingTileCountAt( model, col, row ); + notifyBoardListeners( model, turn, col, row, XP_FALSE ); + + tile = pt->tile; + if ( (tile & TILE_BLANK_BIT) != 0 ) { + tile = dict_getBlankTile( model->vol.dict ); + } + + model_addPlayerTile( model, turn, trayOffset, tile ); + + --player->nPending; + for ( i = index; i < player->nPending; ++i ) { + player->pendingTiles[i] = player->pendingTiles[i+1]; + } + + if ( player->nPending == 0 ) { + invalLastMove( model ); + } + + invalidateScore( model, turn ); + } +} /* model_moveBoardToTray */ + +XP_Bool +model_moveTileOnBoard( ModelCtxt* model, XP_S16 turn, XP_U16 colCur, + XP_U16 rowCur, XP_U16 colNew, XP_U16 rowNew ) +{ + XP_Bool found = XP_FALSE; + PlayerCtxt* player = &model->players[turn]; + XP_S16 index = player->nPending; + + while ( index-- ) { + Tile tile; + XP_U16 tcol, trow; + XP_Bool isBlank; + model_getCurrentMoveTile( model, turn, &index, &tile, &tcol, &trow, + &isBlank ); + if ( colCur == tcol && rowCur == trow ) { + PendingTile* pt = &player->pendingTiles[index]; + pt->col = colNew; + pt->row = rowNew; + if ( isBlank ) { + pt->tile = TILE_BLANK_BIT | askBlankTile( model, turn ); + } + + decrPendingTileCountAt( model, colCur, rowCur ); + incrPendingTileCountAt( model, colNew, rowNew ); + + invalidateScore( model, turn ); + found = XP_TRUE; + break; + } + } + return found; +} /* model_moveTileOnBoard */ + +void +model_resetCurrentTurn( ModelCtxt* model, XP_S16 whose ) +{ + PlayerCtxt* player; + + XP_ASSERT( whose >= 0 && whose < model->nPlayers ); + player = &model->players[whose]; + + while ( player->nPending > 0 ) { + model_moveBoardToTray( model, whose, + player->pendingTiles[0].col, + player->pendingTiles[0].row, + -1 ); + } +} /* model_resetCurrentTurn */ + +static void +incrPendingTileCountAt( ModelCtxt* model, XP_U16 col, XP_U16 row ) +{ + XP_U16 val = getModelTileRaw( model, col, row ); + + if ( TILE_IS_EMPTY(val) ) { + val = 0; + } else { + XP_ASSERT( (val & TILE_PENDING_BIT) != 0 ); + XP_ASSERT( (val & TILE_VALUE_MASK) > 0 ); + } + + ++val; + XP_ASSERT( (val & TILE_VALUE_MASK) > 0 && + (val & TILE_VALUE_MASK) <= MAX_NUM_PLAYERS ); + setModelTileRaw( model, col, row, (CellTile)(val | TILE_PENDING_BIT) ); +} /* incrPendingTileCountAt */ + +static void +setPendingCounts( ModelCtxt* model, XP_S16 turn ) +{ + PlayerCtxt* player = &model->players[turn]; + PendingTile* pending = player->pendingTiles; + XP_U16 nPending; + + for ( nPending = player->nPending; nPending--; ) { + incrPendingTileCountAt( model, pending->col, pending->row ); + ++pending; + } + +} /* setPendingCounts */ + +static void +decrPendingTileCountAt( ModelCtxt* model, XP_U16 col, XP_U16 row ) +{ + XP_U16 val = getModelTileRaw( model, col, row ); + + /* for pending tiles, the value is defined in the players array, so what + we keep here is a refcount of how many players have put tiles there. */ + val &= TILE_VALUE_MASK; /* the refcount */ + XP_ASSERT( val <= MAX_NUM_PLAYERS && val > 0 ); + if ( --val > 0 ) { + val |= TILE_PENDING_BIT; + } else { + val = EMPTY_TILE; + } + setModelTileRaw( model, col, row, val ); +} /* decrPendingTileCountAt */ + +static void +putBackOtherPlayersTiles( ModelCtxt* model, XP_U16 notMyTurn, + XP_U16 col, XP_U16 row ) +{ + XP_S16 turn; + + for ( turn = 0; turn < model->nPlayers; ++turn ) { + if ( turn == notMyTurn ) { + continue; + } + model_moveBoardToTray( model, turn, col, row, -1 ); + } +} /* putBackOtherPlayersTiles */ + +/* Make those tiles placed by 'turn' a permanent part of the board. If any + * other players have placed pending tiles on those same squares, replace them + * in their trays. + */ +static XP_S16 +commitTurn( ModelCtxt* model, XP_S16 turn, TrayTileSet* newTiles, + XWStreamCtxt* stream, XP_Bool useStack ) +{ + short i; + PlayerCtxt* player; + PendingTile* pt; + XP_S16 score; + XP_Bool inLine, isHorizontal; + Tile* newTilesP; + XP_U16 nTiles; + + nTiles = newTiles->nTiles; + +#ifdef DEBUG + XP_ASSERT( getCurrentMoveScoreIfLegal( model, turn, (XWStreamCtxt*)NULL, + &score ) ); + invalidateScore( model, turn ); +#endif + + XP_ASSERT( turn >= 0 && turn < MAX_NUM_PLAYERS); + + clearLastMoveInfo( model ); + + player = &model->players[turn]; + + if ( useStack ) { + MoveInfo moveInfo; + inLine = tilesInLine( model, turn, &isHorizontal ); + XP_ASSERT( inLine ); + normalizeMoves( model, turn, isHorizontal, &moveInfo ); + + stack_addMove( model->vol.stack, turn, &moveInfo, newTiles ); + } + + for ( i = 0, pt=player->pendingTiles; i < player->nPending; ++i, ++pt ) { + XP_U16 col, row; + CellTile tile; + XP_U16 val; + + col = pt->col; + row = pt->row; + tile = getModelTileRaw( model, col, row ); + + XP_ASSERT( (tile & TILE_PENDING_BIT) != 0 ); + + val = tile & TILE_VALUE_MASK; + if ( val > 1 ) { /* somebody else is using this square too! */ + putBackOtherPlayersTiles( model, turn, col, row ); + } + + tile = pt->tile; + tile |= PREV_MOVE_BIT; + tile |= turn << CELL_OWNER_OFFSET; + + setModelTileRaw( model, col, row, tile ); + + notifyBoardListeners( model, turn, col, row, XP_FALSE ); + } + + (void)getCurrentMoveScoreIfLegal( model, turn, stream, &score ); + XP_ASSERT( score >= 0 ); + player->score += score; + + /* Why is this next loop necessary? */ + for ( i = 0; i < model->nPlayers; ++i ) { + invalidateScore( model, i ); + } + + player->nPending = 0; + + newTilesP = newTiles->tiles; + while ( nTiles-- ) { + model_addPlayerTile( model, turn, -1, *newTilesP++ ); + } + + return score; +} /* commitTurn */ + +void +model_commitTurn( ModelCtxt* model, XP_S16 turn, TrayTileSet* newTiles ) +{ + (void)commitTurn( model, turn, newTiles, (XWStreamCtxt*)NULL, XP_TRUE ); +} /* model_commitTurn */ + +/* Given a rack of new tiles and of old, remove all the old from the tray and + * replace them with new. Replace in the same place so that user sees an + * in-place change. + */ +static void +makeTileTrade( ModelCtxt* model, XP_S16 player, TrayTileSet* oldTiles, + TrayTileSet* newTiles ) +{ + XP_U16 i; + XP_U16 nTiles; + + XP_ASSERT( newTiles->nTiles == oldTiles->nTiles ); + + for ( nTiles = newTiles->nTiles, i = 0; i < nTiles; ++i ) { + Tile oldTile = oldTiles->tiles[i]; + + XP_S16 tileIndex = model_trayContains( model, player, oldTile ); + XP_ASSERT( tileIndex >= 0 ); + model_removePlayerTile( model, player, tileIndex ); + model_addPlayerTile( model, player, tileIndex, newTiles->tiles[i] ); + } +} /* makeTileTrade */ + +void +model_makeTileTrade( ModelCtxt* model, XP_S16 player, + TrayTileSet* oldTiles, TrayTileSet* newTiles ) +{ + stack_addTrade( model->vol.stack, player, oldTiles, newTiles ); + + makeTileTrade( model, player, oldTiles, newTiles ); +} /* model_makeTileTrade */ + +Tile +model_getPlayerTile( ModelCtxt* model, XP_S16 turn, XP_S16 index ) +{ + PlayerCtxt* player = &model->players[turn]; + + if ( index < 0 ) { + index = player->trayTiles.nTiles-1; + } + + XP_ASSERT( index < player->trayTiles.nTiles ); + + return player->trayTiles.tiles[index]; +} /* model_getPlayerTile */ + +const TrayTileSet* +model_getPlayerTiles( ModelCtxt* model, XP_S16 turn ) +{ + PlayerCtxt* player = &model->players[turn]; + + return (const TrayTileSet*)&player->trayTiles; +} /* model_getPlayerTile */ + +static void +addPlayerTile( ModelCtxt* model, XP_S16 turn, XP_S16 index, Tile tile ) +{ + PlayerCtxt* player = &model->players[turn]; + short i; + + XP_ASSERT( player->trayTiles.nTiles < MAX_TRAY_TILES ); + XP_ASSERT( index >= 0 ); + + /* move tiles up to make room */ + for ( i = player->trayTiles.nTiles; i > index; --i ) { + player->trayTiles.tiles[i] = player->trayTiles.tiles[i-1]; + } + ++player->trayTiles.nTiles; + player->trayTiles.tiles[index] = tile; +} /* addPlayerTile */ + +void +model_addPlayerTile( ModelCtxt* model, XP_S16 turn, XP_S16 index, Tile tile ) +{ + PlayerCtxt* player = &model->players[turn]; + if ( index < 0 ) { + index = player->trayTiles.nTiles; + } + addPlayerTile( model, turn, index, tile ); + + notifyTrayListeners( model, turn, index, player->trayTiles.nTiles ); +} /* model_addPlayerTile */ + +void +model_moveTileOnTray( ModelCtxt* model, XP_S16 turn, XP_S16 indexCur, + XP_S16 indexNew ) +{ + Tile tile = removePlayerTile( model, turn, indexCur ); + addPlayerTile( model, turn, indexNew, tile ); + notifyTrayListeners( model, turn, indexCur, indexNew ); +} /* model_moveTileOnTray */ + +static void +assignPlayerTiles( ModelCtxt* model, XP_S16 turn, TrayTileSet* tiles ) +{ + Tile* tilep = tiles->tiles; + XP_U16 nTiles = tiles->nTiles; + while ( nTiles-- ) { + model_addPlayerTile( model, turn, -1, *tilep++ ); + } +} /* model_addPlayerTiles */ + +void +model_assignPlayerTiles( ModelCtxt* model, XP_S16 turn, TrayTileSet* tiles ) +{ + stack_addAssign( model->vol.stack, turn, tiles ); + + assignPlayerTiles( model, turn, tiles ); +} /* model_addPlayerTiles */ + +XP_U16 +model_getNumTilesInTray( ModelCtxt* model, XP_S16 turn ) +{ + PlayerCtxt* player; + XP_ASSERT( turn >= 0 ); + player = &model->players[turn]; + return player->trayTiles.nTiles; +} /* model_getNumPlayerTiles */ + +XP_U16 +model_getNumTilesTotal( ModelCtxt* model, XP_S16 turn ) +{ + PlayerCtxt* player = &model->players[turn]; + return player->trayTiles.nTiles + player->nPending; +} /* model_getNumTilesTotal */ + +XP_U16 +model_numRows( const ModelCtxt* model ) +{ + return model->nRows; +} /* model_numRows */ + +XP_U16 +model_numCols( const ModelCtxt* model ) +{ + return model->nCols; +} /* model_numCols */ + +void +model_setBoardListener( ModelCtxt* model, BoardListener bl, void* data ) +{ + model->vol.boardListenerFunc = bl; + model->vol.boardListenerData = data; +} /* model_setBoardListener */ + +void +model_setTrayListener( ModelCtxt* model, TrayListener tl, void* data ) +{ + model->vol.trayListenerFunc = tl; + model->vol.trayListenerData = data; +} /* model_setBoardListener */ + +void +model_setDictListener( ModelCtxt* model, DictListener dl, void* data ) +{ + model->vol.dictListenerFunc = dl; + model->vol.dictListenerData = data; +} /* model_setBoardListener */ + +static void +notifyBoardListeners( ModelCtxt* model, XP_U16 turn, XP_U16 col, XP_U16 row, + XP_Bool added ) +{ + if ( model->vol.boardListenerFunc != NULL ) { + (*model->vol.boardListenerFunc)( model->vol.boardListenerData, turn, + col, row, added ); + } +} /* notifyBoardListeners */ + +static void +notifyTrayListeners( ModelCtxt* model, XP_U16 turn, XP_S16 index1, + XP_S16 index2 ) +{ + if ( model->vol.trayListenerFunc != NULL ) { + (*model->vol.trayListenerFunc)( model->vol.trayListenerData, turn, + index1, index2 ); + } +} /* notifyTrayListeners */ + +static void +notifyDictListeners( ModelCtxt* model, DictionaryCtxt* oldDict, + DictionaryCtxt* newDict ) +{ + XP_ASSERT( !!newDict ); + if ( model->vol.dictListenerFunc != NULL ) { + (*model->vol.dictListenerFunc)( model->vol.dictListenerData, oldDict, + newDict ); + } +} /* notifyDictListeners */ + +static void +printString( XWStreamCtxt* stream, const XP_UCHAR* str ) +{ + stream_putString( stream, str ); +} /* printString */ + +static XP_UCHAR* +formatTray( const TrayTileSet* tiles, DictionaryCtxt* dict, XP_UCHAR* buf, + XP_U16 bufSize, XP_Bool keepHidden ) +{ + if ( keepHidden ) { + XP_U16 i; + for ( i = 0; i < tiles->nTiles; ++i ) { + buf[i] = '?'; + } + buf[i] = '\0'; + } else { + dict_tilesToString( dict, (Tile*)tiles->tiles, tiles->nTiles, + buf, bufSize ); + } + + return buf; +} /* formatTray */ + +typedef struct MovePrintClosure { + XWStreamCtxt* stream; + DictionaryCtxt* dict; + XP_U16 nPrinted; + XP_Bool keepHidden; +} MovePrintClosure; + +static void +printMovePre( ModelCtxt* model, XP_U16 XP_UNUSED(moveN), StackEntry* entry, + void* p_closure ) +{ + XWStreamCtxt* stream; + const XP_UCHAR* format; + XP_UCHAR buf[32]; + XP_UCHAR traybuf[MAX_TRAY_TILES+1]; + MovePrintClosure* closure = (MovePrintClosure*)p_closure; + + if ( entry->moveType == ASSIGN_TYPE ) { + return; + } + + stream = closure->stream; + + XP_SNPRINTF( buf, sizeof(buf), (XP_UCHAR*)"%d:%d ", ++closure->nPrinted, + entry->playerNum+1 ); + printString( stream, (XP_UCHAR*)buf ); + + if ( entry->moveType == TRADE_TYPE ) { + } else { + XP_UCHAR letter[2] = {'\0','\0'}; + XP_Bool isHorizontal = entry->u.move.moveInfo.isHorizontal; + XP_U16 col, row; + MoveInfo* mi; + XP_Bool isPass = XP_FALSE; + + if ( entry->moveType == PHONY_TYPE ) { + mi = &entry->u.phony.moveInfo; + } else { + mi = &entry->u.move.moveInfo; + if ( mi->nTiles == 0 ) { + isPass = XP_TRUE; + } + } + + if ( isPass ) { + format = util_getUserString( model->vol.util, STR_PASS ); + XP_SNPRINTF( buf, sizeof(buf), "%s", format ); + } else { + if ( isHorizontal ) { + format = util_getUserString( model->vol.util, STRS_MOVE_ACROSS ); + } else { + format = util_getUserString( model->vol.util, STRS_MOVE_DOWN ); + } + + row = mi->commonCoord; + col = mi->tiles[0].varCoord; + if ( !isHorizontal ) { + XP_U16 tmp = col; col = row; row = tmp; + } + letter[0] = 'A' + col; + + XP_SNPRINTF( traybuf, sizeof(traybuf), (XP_UCHAR *)"%s%d", + letter, row + 1 ); + XP_SNPRINTF( buf, sizeof(buf), format, traybuf ); + } + printString( stream, (XP_UCHAR*)buf ); + } + + if ( !closure->keepHidden ) { + format = util_getUserString( model->vol.util, STRS_TRAY_AT_START ); + formatTray( model_getPlayerTiles( model, entry->playerNum ), + closure->dict, (XP_UCHAR*)traybuf, sizeof(traybuf), + XP_FALSE ); + XP_SNPRINTF( buf, sizeof(buf), format, traybuf ); + printString( stream, buf ); + } + +} /* printMovePre */ + +static void +printMovePost( ModelCtxt* model, XP_U16 XP_UNUSED(moveN), StackEntry* entry, + XP_S16 XP_UNUSED(score), void* p_closure ) +{ + MovePrintClosure* closure = (MovePrintClosure*)p_closure; + XWStreamCtxt* stream = closure->stream; + DictionaryCtxt* dict = closure->dict; + const XP_UCHAR* format; + XP_U16 nTiles; + XP_S16 totalScore; + XP_UCHAR buf[100]; + XP_UCHAR traybuf1[MAX_TRAY_TILES+1]; + XP_UCHAR traybuf2[MAX_TRAY_TILES+1]; + MoveInfo* mi; + + if ( entry->moveType == ASSIGN_TYPE ) { + return; + } + + totalScore = model_getPlayerScore( model, entry->playerNum ); + + switch( entry->moveType ) { + case TRADE_TYPE: + formatTray( (const TrayTileSet*)&entry->u.trade.oldTiles, + dict, traybuf1, sizeof(traybuf1), closure->keepHidden ); + formatTray( (const TrayTileSet*) &entry->u.trade.newTiles, + dict, traybuf2, sizeof(traybuf2), closure->keepHidden ); + + format = util_getUserString( model->vol.util, STRSS_TRADED_FOR ); + XP_SNPRINTF( buf, sizeof(buf), format, traybuf1, traybuf2 ); + printString( stream, buf ); + printString( stream, (XP_UCHAR*)XP_CR ); + break; + + case PHONY_TYPE: + format = util_getUserString( model->vol.util, STR_PHONY_REJECTED ); + printString( stream, format ); + case MOVE_TYPE: + format = util_getUserString( model->vol.util, STRD_CUMULATIVE_SCORE ); + XP_SNPRINTF( buf, sizeof(buf), format, totalScore ); + printString( stream, buf ); + + if ( entry->moveType == PHONY_TYPE ) { + mi = &entry->u.phony.moveInfo; + } else { + mi = &entry->u.move.moveInfo; + } + nTiles = mi->nTiles; + if ( nTiles > 0 ) { + + if ( entry->moveType == PHONY_TYPE ) { + /* printString( stream, (XP_UCHAR*)"phony rejected " ); */ + } else if ( !closure->keepHidden ) { + format = util_getUserString(model->vol.util, STRS_NEW_TILES); + XP_SNPRINTF( buf, sizeof(buf), format, + formatTray( &entry->u.move.newTiles, dict, + traybuf1, sizeof(traybuf1), + XP_FALSE ) ); + printString( stream, buf ); + } + } + + break; + } + + printString( stream, (XP_UCHAR*)XP_CR ); +} /* printMovePost */ + +static void +copyStack( ModelCtxt* model, StackCtxt* destStack, const StackCtxt* srcStack ) +{ + XWStreamCtxt* stream = mem_stream_make( MPPARM(model->vol.mpool) + util_getVTManager(model->vol.util), + NULL, 0, NULL ); + + stack_writeToStream( (StackCtxt*)srcStack, stream ); + stack_loadFromStream( destStack, stream ); + + stream_destroy( stream ); +} /* copyStack */ + +static ModelCtxt* +makeTmpModel( ModelCtxt* model, XWStreamCtxt* stream, + MovePrintFuncPre mpf_pre, MovePrintFuncPost mpf_post, + void* closure ) +{ + ModelCtxt* tmpModel = model_make( MPPARM(model->vol.mpool) + model_getDictionary(model), + model->vol.util, model_numCols(model), + model_numRows(model)); + model_setNPlayers( tmpModel, model->nPlayers ); + + buildModelFromStack( tmpModel, model->vol.stack, stream, + mpf_pre, mpf_post, closure ); + + return tmpModel; +} /* makeTmpModel */ + +void +model_writeGameHistory( ModelCtxt* model, XWStreamCtxt* stream, + ServerCtxt* server, XP_Bool gameOver ) +{ + ModelCtxt* tmpModel; + MovePrintClosure closure; + + closure.stream = stream; + closure.dict = model_getDictionary( model ); + closure.keepHidden = !gameOver; + closure.nPrinted = 0; + + tmpModel = makeTmpModel( model, stream, printMovePre, printMovePost, + &closure ); + model_destroy( tmpModel ); + + if ( gameOver ) { + /* if the game's over, it shouldn't matter which model I pass to this + method */ + server_writeFinalScores( server, stream ); + } +} /* model_writeGameHistory */ + +static void +scoreLastMove( ModelCtxt* model, MoveInfo* moveInfo, XP_U16 howMany, + XP_UCHAR* buf, XP_U16* bufLen ) +{ + + if ( moveInfo->nTiles == 0 ) { + const XP_UCHAR* str = util_getUserString( model->vol.util, STR_PASSED ); + XP_U16 len = XP_STRLEN( str ); + *bufLen = len; + XP_MEMCPY( buf, str, len+1 ); /* no XP_STRCPY yet */ + } else { + XP_U16 score; + XP_UCHAR wordBuf[MAX_ROWS+1]; + const XP_UCHAR* format; + + ModelCtxt* tmpModel = makeTmpModel( model, NULL, NULL, NULL, NULL ); + XP_U16 turn; + XP_S16 moveNum = -1; + + copyStack( model, tmpModel->vol.stack, model->vol.stack ); + + if ( !model_undoLatestMoves( tmpModel, NULL, howMany, &turn, + &moveNum ) ) { + XP_ASSERT( 0 ); + } + + score = figureMoveScore( tmpModel, moveInfo, (EngineCtxt*)NULL, + (XWStreamCtxt*)NULL, (WordNotifierInfo*)NULL, + wordBuf ); + + model_destroy( tmpModel ); + + format = util_getUserString( model->vol.util, STRSD_SUMMARYSCORED ); + *bufLen = XP_SNPRINTF( buf, *bufLen, format, wordBuf, score ); + } +} /* scoreLastMove */ + +static XP_U16 +model_getRecentPassCount( ModelCtxt* model ) +{ + StackCtxt* stack = model->vol.stack; + XP_U16 nPasses = 0; + XP_S16 nEntries, which; + StackEntry entry; + + XP_ASSERT( !!stack ); + + nEntries = stack_getNEntries( stack ); + for ( which = nEntries - 1; which >= 0; --which ) { + if ( stack_getNthEntry( stack, which, &entry ) ) { + if ( entry.moveType == MOVE_TYPE + && entry.u.move.moveInfo.nTiles == 0 ) { + ++nPasses; + } else { + break; + } + } else { + break; + } + } + return nPasses; +} /* model_getRecentPassCount */ + +XP_Bool +model_recentPassCountOk( ModelCtxt* model ) +{ + XP_U16 count = model_getRecentPassCount( model ); + XP_U16 okCount = model->nPlayers * MAX_PASSES; + XP_ASSERT( count <= okCount ); /* should never be more than 1 over */ + return count < okCount; +} + +XP_Bool +model_getPlayersLastScore( ModelCtxt* model, XP_S16 player, + XP_UCHAR* expl, XP_U16* explLen ) +{ + StackCtxt* stack = model->vol.stack; + XP_S16 nEntries, which; + StackEntry entry; + XP_Bool found = XP_FALSE; + + XP_ASSERT( !!stack ); + XP_ASSERT( player >= 0 ); + + nEntries = stack_getNEntries( stack ); + + for ( which = nEntries; which >= 0; ) { + if ( stack_getNthEntry( stack, --which, &entry ) ) { + if ( entry.playerNum == player ) { + found = XP_TRUE; + break; + } + } + } + + if ( found ) { /* success? */ + const XP_UCHAR* format; + XP_U16 nTiles; + switch ( entry.moveType ) { + case MOVE_TYPE: + scoreLastMove( model, &entry.u.move.moveInfo, + nEntries - which - 1, expl, explLen ); + break; + case TRADE_TYPE: + nTiles = entry.u.trade.oldTiles.nTiles; + format = util_getUserString( model->vol.util, STRD_TRADED ); + *explLen = XP_SNPRINTF( expl, *explLen, format, nTiles ); + break; + case PHONY_TYPE: + format = util_getUserString( model->vol.util, STR_LOSTTURN ); + *explLen = XP_STRLEN( format ); + XP_STRCAT( expl, format ); + break; + case ASSIGN_TYPE: + found = XP_FALSE; + break; + } + } + + return found; +} /* model_getPlayersLastScore */ + +static void +loadPlayerCtxt( XWStreamCtxt* stream, PlayerCtxt* pc ) +{ + XP_U16 i; + + pc->curMoveValid = stream_getBits( stream, 1 ); + + traySetFromStream( stream, &pc->trayTiles ); + + pc->nPending = (XP_U8)stream_getBits( stream, NTILES_NBITS ); + + for ( i = 0; i < pc->nPending; ++i ) { + PendingTile* pt = &pc->pendingTiles[i]; + XP_U16 nBits; + pt->col = (XP_U8)stream_getBits( stream, NUMCOLS_NBITS ); + pt->row = (XP_U8)stream_getBits( stream, NUMCOLS_NBITS ); + + nBits = (stream_getVersion( stream ) <= STREAM_VERS_RELAY) ? 6 : 7; + pt->tile = (Tile)stream_getBits( stream, nBits ); + } + +} /* loadPlayerCtxt */ + +static void +writePlayerCtxt( XWStreamCtxt* stream, PlayerCtxt* pc ) +{ + XP_U16 i; + + stream_putBits( stream, 1, pc->curMoveValid ); + + traySetToStream( stream, &pc->trayTiles ); + + stream_putBits( stream, NTILES_NBITS, pc->nPending ); + + for ( i = 0; i < pc->nPending; ++i ) { + PendingTile* pt = &pc->pendingTiles[i]; + stream_putBits( stream, NUMCOLS_NBITS, pt->col ); + stream_putBits( stream, NUMCOLS_NBITS, pt->row ); + stream_putBits( stream, 7, pt->tile ); + } + +} /* writePlayerCtxt */ + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/model.h b/xwords4/common/model.h new file mode 100644 index 000000000..178d77ade --- /dev/null +++ b/xwords4/common/model.h @@ -0,0 +1,261 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _MODEL_H_ +#define _MODEL_H_ + +#include "comtypes.h" +#include "dictnry.h" +#include "mempool.h" + +#ifdef CPLUS +extern "C" { +#endif + +#define MAX_ROWS 16 +#define MAX_COLS 16 +#define NUMCOLS_NBITS 4 + +#ifdef EIGHT_TILES +#define MAX_TRAY_TILES 8 +#define NTILES_NBITS 4 +#else +#define MAX_TRAY_TILES 7 +#define NTILES_NBITS 3 +#endif + +#define MIN_TRADE_TILES MAX_TRAY_TILES +/* apply to CellTile */ +#define TILE_VALUE_MASK 0x003F +#define TILE_BLANK_BIT 0x0040 +#define IS_BLANK(t) (((t)&TILE_BLANK_BIT)!= 0) +#define TILE_EMPTY_BIT 0x0080 +#define TILE_PENDING_BIT 0x0100 +#define PREV_MOVE_BIT 0x200 + +#define CELL_OWNER_MASK 0x0C00 +#define CELL_OWNER_OFFSET 10 +#define CELL_OWNER(t) (((t)&CELL_OWNER_MASK) >> CELL_OWNER_OFFSET) + +#define MAX_UNIQUE_TILES 64 /* max tile non-blank faces */ +#define MAX_NUM_BLANKS 4 + +/* Used by scoring code and engine as fast representation of moves. */ +typedef struct MoveInfoTile { + XP_U8 varCoord; /* 5 bits ok (0-16 for 17x17 board) */ + Tile tile; /* 6 bits will do */ +} MoveInfoTile; + +typedef struct MoveInfo { + XP_U8 nTiles; /* 4 bits: 0-7 */ + XP_U8 commonCoord; /* 5 bits: 0-16 if 17x17 possible */ + XP_Bool isHorizontal; /* 1 bit */ + /* If this is to go on an undo stack, we need player num here, or the code + has to keep track of it *and* there must be exactly one entry per + player per turn. */ + MoveInfoTile tiles[MAX_TRAY_TILES]; +} MoveInfo; + +typedef XP_U8 TrayTile; +typedef struct TrayTileSet { + XP_U8 nTiles; + TrayTile tiles[MAX_TRAY_TILES]; +} TrayTileSet; + +typedef struct BlankQueue { + XP_U16 nBlanks; + XP_U8 col[MAX_NUM_BLANKS]; + XP_U8 row[MAX_NUM_BLANKS]; +} BlankQueue; + +typedef XP_U8 TileBit; /* bits indicating selection of tiles in tray */ +#define ALLTILES ((TileBit)~(0xFF<<(MAX_TRAY_TILES))) + +#define ILLEGAL_MOVE_SCORE (-1) + +#define EMPTY_TILE TILE_EMPTY_BIT +#define TILE_IS_EMPTY(t) (((t)&TILE_EMPTY_BIT)!=0) +#define REVERSED_TILE TILE_PENDING_BIT /* reuse that bit for tile drawing + only */ + + +ModelCtxt* model_make( MPFORMAL DictionaryCtxt* dict, XW_UtilCtxt* util, + XP_U16 nCols, XP_U16 nRows ); + +ModelCtxt* model_makeFromStream( MPFORMAL XWStreamCtxt* stream, + DictionaryCtxt* dict, XW_UtilCtxt* util ); + +void model_writeToStream( ModelCtxt* model, XWStreamCtxt* stream ); + +void model_init( ModelCtxt* model, XP_U16 nCols, XP_U16 nRows ); +void model_destroy( ModelCtxt* model ); +void model_setNPlayers( ModelCtxt* model, XP_U16 numPlayers ); + +void model_setDictionary( ModelCtxt* model, DictionaryCtxt* dict ); +DictionaryCtxt* model_getDictionary( ModelCtxt* model ); + +XP_Bool model_getTile( const ModelCtxt* model, XP_U16 col, XP_U16 row, + XP_Bool getPending, XP_S16 turn, + Tile* tile, XP_Bool* isBlank, + XP_Bool* isPending, XP_Bool* isRecent ); + +void model_listPlacedBlanks( ModelCtxt* model, XP_U16 turn, + XP_Bool includePending, BlankQueue* bcp ); + +XP_U16 model_getCellOwner( ModelCtxt* model, XP_U16 col, XP_U16 row ); + +void model_assignPlayerTiles( ModelCtxt* model, XP_S16 turn, + TrayTileSet* tiles ); +Tile model_getPlayerTile( ModelCtxt* model, XP_S16 turn, XP_S16 index ); + +Tile model_removePlayerTile( ModelCtxt* model, XP_S16 turn, XP_S16 index ); +void model_addPlayerTile( ModelCtxt* model, XP_S16 turn, XP_S16 index, + Tile tile ); +void model_moveTileOnTray( ModelCtxt* model, XP_S16 turn, XP_S16 indexCur, + XP_S16 indexNew ); + +/* As an optimization, return a pointer to the model's array of tiles for a + player. Don't even think about modifying the array!!!! */ +const TrayTileSet* model_getPlayerTiles( ModelCtxt* model, XP_S16 turn ); + +XP_U16 model_getNumTilesInTray( ModelCtxt* model, XP_S16 turn ); +XP_U16 model_getNumTilesTotal( ModelCtxt* model, XP_S16 turn ); +void model_moveBoardToTray( ModelCtxt* model, XP_S16 turn, + XP_U16 col, XP_U16 row, XP_U16 trayOffset ); +void model_moveTrayToBoard( ModelCtxt* model, XP_S16 turn, XP_U16 col, + XP_U16 row, XP_S16 tileIndex, Tile blankFace ); +XP_Bool model_moveTileOnBoard( ModelCtxt* model, XP_S16 turn, XP_U16 colCur, + XP_U16 rowCur, XP_U16 colNew, XP_U16 rowNew ); + +XP_S16 model_trayContains( ModelCtxt* model, XP_S16 turn, Tile tile ); + + +XP_U16 model_numRows( const ModelCtxt* model ); +XP_U16 model_numCols( const ModelCtxt* model ); + +/* XP_U16 model_numTilesCurrentTray( ModelCtxt* model ); */ +/* Tile model_currentTrayTile( ModelCtxt* model, XP_U16 index ); */ +void model_addToCurrentMove( ModelCtxt* model, XP_S16 turn, + XP_U16 col, XP_U16 row, + Tile tile, XP_Bool isBlank ); +XP_U16 model_getCurrentMoveCount( ModelCtxt* model, XP_S16 turn ); + +void model_getCurrentMoveTile( ModelCtxt* model, XP_S16 turn, XP_S16* index, + Tile* tile, XP_U16* col, XP_U16* row, + XP_Bool* isBlank ); + +void model_commitTurn( ModelCtxt* model, XP_S16 player, + TrayTileSet* newTiles ); +void model_commitRejectedPhony( ModelCtxt* model, XP_S16 player ); +void model_makeTileTrade( ModelCtxt* model, XP_S16 player, + TrayTileSet* oldTiles, TrayTileSet* newTiles ); + +XP_Bool model_undoLatestMoves( ModelCtxt* model, PoolContext* pool, + XP_U16 nMovesSought, XP_U16* turn, + XP_S16* moveNum ); +void model_rejectPreviousMove( ModelCtxt* model, PoolContext* pool, + XP_U16* turn ); + +void model_trayToStream( ModelCtxt* model, XP_S16 turn, + XWStreamCtxt* stream ); +void model_currentMoveToStream( ModelCtxt* model, XP_S16 turn, + XWStreamCtxt* stream); +void model_makeTurnFromStream( ModelCtxt* model, XP_U16 playerNum, + XWStreamCtxt* stream ); +void model_makeTurnFromMoveInfo( ModelCtxt* model, XP_U16 playerNum, + MoveInfo* newMove ); + +void model_resetCurrentTurn( ModelCtxt* model, XP_S16 turn ); + +/********************* notification ********************/ +typedef void (*BoardListener)(void* data, XP_U16 turn, XP_U16 col, + XP_U16 row, XP_Bool added ); +void model_setBoardListener( ModelCtxt* model, BoardListener bl, + void* data ); +typedef void (*TrayListener)( void* data, XP_U16 turn, + XP_S16 index1, XP_S16 index2 ); +void model_setTrayListener( ModelCtxt* model, TrayListener bl, + void* data ); +typedef void (*DictListener)( void* data, const DictionaryCtxt* oldDict, + const DictionaryCtxt* newDict ); +void model_setDictListener( ModelCtxt* model, DictListener dl, + void* data ); +void model_foreachPendingCell( ModelCtxt* model, XP_S16 turn, + BoardListener bl, void* data ); +void model_foreachPrevCell( ModelCtxt* model, BoardListener bl, void* data ); + +void model_writeGameHistory( ModelCtxt* model, XWStreamCtxt* stream, + ServerCtxt* server, /* for player names */ + XP_Bool gameOver ); + +/* for the tile values dialog: total all the tiles in players trays and + tentatively placed on the board. */ +void model_countAllTrayTiles( ModelCtxt* model, XP_U16* counts, + XP_S16 excludePlayer ); + +/********************* scoring ********************/ + +typedef XP_Bool (*WordNotifierProc)( XP_UCHAR* word, void* closure ); +typedef struct WordNotifierInfo { + WordNotifierProc proc; + void* closure; +} WordNotifierInfo; + +XP_Bool getCurrentMoveScoreIfLegal( ModelCtxt* model, XP_S16 turn, + XWStreamCtxt* stream, XP_S16* score ); +XP_S16 model_getPlayerScore( ModelCtxt* model, XP_S16 player ); + +XP_Bool model_getPlayersLastScore( ModelCtxt* model, XP_S16 player, + XP_UCHAR* expl, XP_U16* explLen ); + +/* Have there been too many passes (so game should end)? */ +XP_Bool model_recentPassCountOk( ModelCtxt* model ); + +XP_Bool model_checkMoveLegal( ModelCtxt* model, XP_S16 player, + XWStreamCtxt* stream, + WordNotifierInfo* notifyInfo ); + +void model_figureFinalScores( ModelCtxt* model, XP_S16* scores, + XP_S16* tilePenalties ); + +/* figureMoveScore is meant only for the engine's use */ +XP_U16 figureMoveScore( const ModelCtxt* model, MoveInfo* moveInfo, + EngineCtxt* engine, XWStreamCtxt* stream, + WordNotifierInfo* notifyInfo, XP_UCHAR* mainWord ); + +/********************* persistence ********************/ +#ifdef INCLUDE_IO_SUPPORT +void model_load( ModelCtxt* model, XP_Stream* inStream ); +void model_store( ModelCtxt* model, XP_Stream* outStream ); +#endif + + +/* a utility function needed by server too. Not a clean design, this. */ +void model_packTilesUtil( ModelCtxt* model, PoolContext* pool, + XP_Bool includeBlank, + XP_U16* nUsed, XP_UCHAR4* texts, + Tile* tiles ); + + +#ifdef CPLUS +} +#endif + +#endif + diff --git a/xwords4/common/modelp.h b/xwords4/common/modelp.h new file mode 100644 index 000000000..40bbaaa0b --- /dev/null +++ b/xwords4/common/modelp.h @@ -0,0 +1,80 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _MODELP_H_ +#define _MODELP_H_ + +#include "model.h" +#include "movestak.h" + +#ifdef CPLUS +extern "C" { +#endif + +typedef struct PendingTile { + XP_U8 col; + XP_U8 row; + Tile tile; /* includes face and blank bit */ +} PendingTile; + +typedef struct PlayerCtxt { + XP_S16 score; + XP_S16 curMoveScore; /* negative means illegal */ + XP_Bool curMoveValid; + TrayTileSet trayTiles; + XP_U8 nPending; /* still in tray but "on board" */ + PendingTile pendingTiles[MAX_TRAY_TILES]; +} PlayerCtxt; + +typedef struct ModelVolatiles { + XW_UtilCtxt* util; + struct CurGameInfo* gi; + DictionaryCtxt* dict; + BoardListener boardListenerFunc; + StackCtxt* stack; + void* boardListenerData; + TrayListener trayListenerFunc; + void* trayListenerData; + DictListener dictListenerFunc; + void* dictListenerData; + MPSLOT +} ModelVolatiles; + +struct ModelCtxt { + + ModelVolatiles vol; + + CellTile tiles[MAX_COLS][MAX_ROWS]; + + PlayerCtxt players[MAX_NUM_PLAYERS]; + XP_U16 nPlayers; + XP_U16 nCols; + XP_U16 nRows; +}; + +void invalidateScore( ModelCtxt* model, XP_S16 player ); +XP_Bool tilesInLine( ModelCtxt* model, XP_S16 turn, XP_Bool* isHorizontal ); +void normalizeMoves( ModelCtxt* model, XP_S16 turn, + XP_Bool isHorizontal, MoveInfo* moveInfo ); +void adjustScoreForUndone( ModelCtxt* model, MoveInfo* mi, XP_U16 turn ); +#ifdef CPLUS +} +#endif + +#endif diff --git a/xwords4/common/movestak.c b/xwords4/common/movestak.c new file mode 100644 index 000000000..7976a516f --- /dev/null +++ b/xwords4/common/movestak.c @@ -0,0 +1,386 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001, 2006 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +//#include "modelp.h" + +#include "mempool.h" +#include "xwstream.h" +#include "movestak.h" +#include "memstream.h" +#include "strutils.h" + +#ifdef CPLUS +extern "C" { +#endif + +struct StackCtxt { + VTableMgr* vtmgr; + + XWStreamCtxt* data; + + XWStreamPos top; + + XWStreamPos cachedPos; + + XP_U16 cacheNext; + XP_U16 nEntries; + XP_U16 bitsPerTile; + XP_U16 highWaterMark; + + MPSLOT +}; + +void +stack_init( StackCtxt* stack ) +{ + stack->nEntries = stack->highWaterMark = 0; + stack->top = START_OF_STREAM; + + /* I see little point in freeing or shrinking stack->data. It'll get + shrunk to fit as soon as we serialize/deserialize anyway. */ +} /* stack_init */ + +void +stack_setBitsPerTile( StackCtxt* stack, XP_U16 bitsPerTile ) +{ + XP_ASSERT( !!stack ); + stack->bitsPerTile = bitsPerTile; +} + +StackCtxt* +stack_make( MPFORMAL VTableMgr* vtmgr ) +{ + StackCtxt* result = (StackCtxt*)XP_MALLOC( mpool, sizeof( *result ) ); + if ( !!result ) { + XP_MEMSET( result, 0, sizeof(*result) ); + MPASSIGN(result->mpool, mpool); + result->vtmgr = vtmgr; + } + + return result; +} /* stack_make */ + +void +stack_destroy( StackCtxt* stack ) +{ + if ( !!stack->data ) { + stream_destroy( stack->data ); + } + XP_FREE( stack->mpool, stack ); +} /* stack_destroy */ + +void +stack_loadFromStream( StackCtxt* stack, XWStreamCtxt* stream ) +{ + XP_U16 nBytes = stream_getU16( stream ); + + if ( nBytes > 0 ) { + stack->highWaterMark = stream_getU16( stream ); + stack->nEntries = stream_getU16( stream ); + stack->top = stream_getU32( stream ); + stack->data = mem_stream_make( MPPARM(stack->mpool) stack->vtmgr, + NULL, 0, + (MemStreamCloseCallback)NULL ); + + stream_copyFromStream( stack->data, stream, nBytes ); + } else { + XP_ASSERT( stack->nEntries == 0 ); + XP_ASSERT( stack->top == 0 ); + } +} /* stack_makeFromStream */ + +void +stack_writeToStream( StackCtxt* stack, XWStreamCtxt* stream ) +{ + XP_U16 nBytes; + XWStreamCtxt* data = stack->data; + XWStreamPos oldPos = START_OF_STREAM; + + if ( !!data ) { + oldPos = stream_setPos( data, START_OF_STREAM, POS_READ ); + nBytes = stream_getSize( data ); + } else { + nBytes = 0; + } + + stream_putU16( stream, nBytes ); + + if ( nBytes > 0 ) { + stream_putU16( stream, stack->highWaterMark ); + stream_putU16( stream, stack->nEntries ); + stream_putU32( stream, stack->top ); + + stream_setPos( data, START_OF_STREAM, POS_READ ); + stream_copyFromStream( stream, data, nBytes ); + } + + if ( !!data ) { + /* in case it'll be used further */ + (void)stream_setPos( data, oldPos, POS_READ ); + } +} /* stack_writeToStream */ + +static void +pushEntry( StackCtxt* stack, const StackEntry* entry ) +{ + XP_U16 i, bitsPerTile; + XWStreamPos oldLoc; + XP_U16 nTiles = entry->u.move.moveInfo.nTiles; + XWStreamCtxt* stream = stack->data; + + if ( !stream ) { + stream = mem_stream_make( MPPARM(stack->mpool) stack->vtmgr, NULL, 0, + (MemStreamCloseCallback)NULL ); + stack->data = stream; + } + + oldLoc = stream_setPos( stream, stack->top, POS_WRITE ); + + stream_putBits( stream, 2, entry->moveType ); + stream_putBits( stream, 2, entry->playerNum ); + + switch( entry->moveType ) { + case MOVE_TYPE: + case PHONY_TYPE: + + stream_putBits( stream, NTILES_NBITS, nTiles ); + stream_putBits( stream, 5, entry->u.move.moveInfo.commonCoord ); + stream_putBits( stream, 1, entry->u.move.moveInfo.isHorizontal ); + bitsPerTile = stack->bitsPerTile; + XP_ASSERT( bitsPerTile == 5 || bitsPerTile == 6 ); + for ( i = 0; i < nTiles; ++i ) { + Tile tile; + stream_putBits( stream, 5, + entry->u.move.moveInfo.tiles[i].varCoord ); + + tile = entry->u.move.moveInfo.tiles[i].tile; + stream_putBits( stream, bitsPerTile, tile & TILE_VALUE_MASK ); + stream_putBits( stream, 1, (tile & TILE_BLANK_BIT) != 0 ); + } + if ( entry->moveType == MOVE_TYPE ) { + traySetToStream( stream, &entry->u.move.newTiles ); + } + break; + + case ASSIGN_TYPE: + traySetToStream( stream, &entry->u.assign.tiles ); + break; + + case TRADE_TYPE: + XP_ASSERT( entry->u.trade.newTiles.nTiles + == entry->u.trade.oldTiles.nTiles ); + traySetToStream( stream, &entry->u.trade.oldTiles ); + /* could save three bits per trade by just writing the tiles of the + second guy */ + traySetToStream( stream, &entry->u.trade.newTiles ); + break; + } + + ++stack->nEntries; + stack->highWaterMark = stack->nEntries; + stack->top = stream_setPos( stream, oldLoc, POS_WRITE ); +} /* pushEntry */ + +static void +readEntry( StackCtxt* stack, StackEntry* entry ) +{ + XP_U16 nTiles, i, bitsPerTile; + XWStreamCtxt* stream = stack->data; + + entry->moveType = (StackMoveType)stream_getBits( stream, 2 ); + entry->playerNum = (XP_U8)stream_getBits( stream, 2 ); + + switch( entry->moveType ) { + + case MOVE_TYPE: + case PHONY_TYPE: + nTiles = entry->u.move.moveInfo.nTiles = + (XP_U8)stream_getBits( stream, NTILES_NBITS ); + XP_ASSERT( nTiles <= MAX_TRAY_TILES ); + entry->u.move.moveInfo.commonCoord = (XP_U8)stream_getBits(stream, 5); + entry->u.move.moveInfo.isHorizontal = (XP_U8)stream_getBits(stream, 1); + bitsPerTile = stack->bitsPerTile; + XP_ASSERT( bitsPerTile == 5 || bitsPerTile == 6 ); + for ( i = 0; i < nTiles; ++i ) { + Tile tile; + entry->u.move.moveInfo.tiles[i].varCoord = + (XP_U8)stream_getBits(stream, 5); + tile = (Tile)stream_getBits( stream, bitsPerTile ); + if ( 0 != stream_getBits( stream, 1 ) ) { + tile |= TILE_BLANK_BIT; + } + entry->u.move.moveInfo.tiles[i].tile = tile; + } + + if ( entry->moveType == MOVE_TYPE ) { + traySetFromStream( stream, &entry->u.move.newTiles ); + } + break; + + case ASSIGN_TYPE: + traySetFromStream( stream, &entry->u.assign.tiles ); + break; + + case TRADE_TYPE: + traySetFromStream( stream, &entry->u.trade.oldTiles ); + traySetFromStream( stream, &entry->u.trade.newTiles ); + XP_ASSERT( entry->u.trade.newTiles.nTiles + == entry->u.trade.oldTiles.nTiles ); + break; + } + +} /* readEntry */ + +void +stack_addMove( StackCtxt* stack, XP_U16 turn, MoveInfo* moveInfo, + TrayTileSet* newTiles ) +{ + StackEntry move; + + move.playerNum = (XP_U8)turn; + move.moveType = MOVE_TYPE; + + XP_MEMCPY( &move.u.move.moveInfo, moveInfo, sizeof(move.u.move.moveInfo)); + move.u.move.newTiles = *newTiles; + + pushEntry( stack, &move ); +} /* stack_addMove */ + +void +stack_addPhony( StackCtxt* stack, XP_U16 turn, MoveInfo* moveInfo ) +{ + StackEntry move; + + move.playerNum = (XP_U8)turn; + move.moveType = PHONY_TYPE; + + XP_MEMCPY( &move.u.phony.moveInfo, moveInfo, + sizeof(move.u.phony.moveInfo)); + + pushEntry( stack, &move ); +} /* stack_addPhony */ + +void +stack_addTrade( StackCtxt* stack, XP_U16 turn, + TrayTileSet* oldTiles, TrayTileSet* newTiles ) +{ + StackEntry move; + + move.playerNum = (XP_U8)turn; + move.moveType = TRADE_TYPE; + + move.u.trade.oldTiles = *oldTiles; + move.u.trade.newTiles = *newTiles; + + pushEntry( stack, &move ); +} /* stack_addTrade */ + +void +stack_addAssign( StackCtxt* stack, XP_U16 turn, TrayTileSet* tiles ) +{ + StackEntry move; + + move.playerNum = (XP_U8)turn; + move.moveType = ASSIGN_TYPE; + + move.u.assign.tiles = *tiles; + + pushEntry( stack, &move ); +} /* stack_addAssign */ + +static XP_Bool +setCacheReadyFor( StackCtxt* stack, XP_U16 n ) +{ + StackEntry dummy; + XP_U16 i; + + stream_setPos( stack->data, START_OF_STREAM, POS_READ ); + for ( i = 0; i < n; ++i ) { + readEntry( stack, &dummy ); + } + + stack->cacheNext = n; + stack->cachedPos = stream_getPos( stack->data, XP_FALSE ); + + return XP_TRUE; +} /* setCacheReadyFor */ + +XP_U16 +stack_getNEntries( StackCtxt* stack ) +{ + return stack->nEntries; +} /* stack_getNEntries */ + +XP_Bool +stack_getNthEntry( StackCtxt* stack, XP_U16 n, StackEntry* entry ) +{ + XP_Bool found; + + if ( n >= stack->nEntries ) { + found = XP_FALSE; + } else if ( stack->cacheNext != n ) { + XP_ASSERT( !!stack->data ); + found = setCacheReadyFor( stack, n ); + XP_ASSERT( stack->cacheNext == n ); + } else { + found = XP_TRUE; + } + + if ( found ) { + XWStreamPos oldPos = stream_setPos( stack->data, stack->cachedPos, + POS_READ ); + + readEntry( stack, entry ); + entry->moveNum = (XP_U8)n; + + stack->cachedPos = stream_setPos( stack->data, oldPos, POS_READ ); + ++stack->cacheNext; + } + + return found; +} /* stack_getNthEntry */ + +XP_Bool +stack_popEntry( StackCtxt* stack, StackEntry* entry ) +{ + XP_U16 n = stack->nEntries - 1; + XP_Bool found = stack_getNthEntry( stack, n, entry ); + if ( found ) { + stack->nEntries = n; + + setCacheReadyFor( stack, n ); /* set cachedPos by side-effect */ + stack->top = stack->cachedPos; + } + return found; +} /* stack_popEntry */ + +void +stack_redo( StackCtxt* stack ) +{ + if( (stack->nEntries + 1) <= stack->highWaterMark ) { + ++stack->nEntries; + setCacheReadyFor( stack, stack->nEntries ); + stack->top = stack->cachedPos; + } +} /* stack_redo */ + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/movestak.h b/xwords4/common/movestak.h new file mode 100644 index 000000000..1bc0a3aad --- /dev/null +++ b/xwords4/common/movestak.h @@ -0,0 +1,95 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _MOVESTAK_H_ +#define _MOVESTAK_H_ + +#include "comtypes.h" +#include "model.h" +#include "vtabmgr.h" + +#ifdef CPLUS +extern "C" { +#endif + +enum { ASSIGN_TYPE, MOVE_TYPE, TRADE_TYPE, PHONY_TYPE }; +typedef XP_U8 StackMoveType; + +typedef struct AssignRec { + TrayTileSet tiles; +} AssignRec; + +typedef struct TradeRec { + TrayTileSet oldTiles; + TrayTileSet newTiles; +} TradeRec; + +typedef struct MoveRec { + MoveInfo moveInfo; + TrayTileSet newTiles; +} MoveRec; + +typedef struct PhonyRec { + MoveInfo moveInfo; +} PhonyRec; + +typedef union EntryData { + AssignRec assign; + TradeRec trade; + MoveRec move; + PhonyRec phony; +} EntryData; + +typedef struct StackEntry { + StackMoveType moveType; + XP_U8 playerNum; + XP_U8 moveNum; + EntryData u; +} StackEntry; + +typedef struct StackCtxt StackCtxt; + +StackCtxt* stack_make( MPFORMAL VTableMgr* vtmgr ); +void stack_destroy( StackCtxt* stack ); + +void stack_init( StackCtxt* stack ); +void stack_setBitsPerTile( StackCtxt* stack, XP_U16 bitsPerTile ); + +void stack_loadFromStream( StackCtxt* stack, XWStreamCtxt* stream ); +void stack_writeToStream( StackCtxt* stack, XWStreamCtxt* stream ); + +void stack_addMove( StackCtxt* stack, XP_U16 turn, MoveInfo* moveInfo, + TrayTileSet* newTiles ); +void stack_addPhony( StackCtxt* stack, XP_U16 turn, MoveInfo* moveInfo ); +void stack_addTrade( StackCtxt* stack, XP_U16 turn, + TrayTileSet* oldTiles, TrayTileSet* newTiles ); +void stack_addAssign( StackCtxt* stack, XP_U16 turn, TrayTileSet* tiles ); + +XP_U16 stack_getNEntries( StackCtxt* stack ); + +XP_Bool stack_getNthEntry( StackCtxt* stack, XP_U16 n, StackEntry* entry ); + +XP_Bool stack_popEntry( StackCtxt* stack, StackEntry* entry ); +void stack_redo( StackCtxt* stack ); + +#ifdef CPLUS +} +#endif + +#endif diff --git a/xwords4/common/mscore.c b/xwords4/common/mscore.c new file mode 100644 index 000000000..03e254d97 --- /dev/null +++ b/xwords4/common/mscore.c @@ -0,0 +1,850 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1998-2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "modelp.h" +#include "util.h" +#include "engine.h" +#include "game.h" +#include "LocalizedStrIncludes.h" + +#ifdef CPLUS +extern "C" { +#endif + +#define IMPOSSIBLY_LOW_PENALTY (-20*MAX_TRAY_TILES) + +/****************************** prototypes ******************************/ +static XP_Bool isLegalMove( ModelCtxt* model, MoveInfo* moves, XP_Bool silent ); +static XP_U16 word_multiplier( const ModelCtxt* model, + XP_U16 col, XP_U16 row ); +static XP_U16 find_end( const ModelCtxt* model, XP_U16 col, XP_U16 row, + XP_Bool isHorizontal ); +static XP_U16 find_start( const ModelCtxt* model, XP_U16 col, XP_U16 row, + XP_Bool isHorizontal ); +static XP_S16 checkScoreMove( ModelCtxt* model, XP_S16 turn, + EngineCtxt* engine, XWStreamCtxt* stream, + XP_Bool silent, WordNotifierInfo* notifyInfo ); +static XP_U16 scoreWord( const ModelCtxt* model, MoveInfo* movei, + EngineCtxt* engine, XWStreamCtxt* stream, + WordNotifierInfo* notifyInfo, XP_UCHAR* mainWord ); + +/* for formatting when caller wants an explanation of the score. These live + in separate function called only when stream != NULL so that they'll have + as little impact as possible on the speed when the robot's looking for FAST + scoring */ +typedef struct WordScoreFormatter { + DictionaryCtxt* dict; + + XP_UCHAR fullBuf[80]; + XP_UCHAR wordBuf[MAX_ROWS+1]; + XP_U16 bufLen, nTiles; + + XP_Bool firstPass; +} WordScoreFormatter; +static void wordScoreFormatterInit( WordScoreFormatter* fmtr, + DictionaryCtxt* dict ); +static void wordScoreFormatterAddTile( WordScoreFormatter* fmtr, Tile tile, + XP_U16 tileMultiplier, + XP_Bool isBlank ); +static void wordScoreFormatterFinish( WordScoreFormatter* fmtr, Tile* word, + XWStreamCtxt* stream, XP_UCHAR* mainWord ); +static void formatWordScore( XWStreamCtxt* stream, XP_U16 wordScore, + XP_U16 moveMultiplier ); +static void formatSummary( XWStreamCtxt* stream, const ModelCtxt* model, + XP_U16 score ); + + +/* Calculate the score of the current move as it stands. Flag the score + * current so we won't have to do this again until something changes to + * invalidate the score. + */ +static void +scoreCurrentMove( ModelCtxt* model, XP_S16 turn, XWStreamCtxt* stream ) +{ + PlayerCtxt* player = &model->players[turn]; + XP_S16 score; + + XP_ASSERT( !player->curMoveValid ); + + /* recalc goes here */ + score = checkScoreMove( model, turn, (EngineCtxt*)NULL, stream, + XP_TRUE, (WordNotifierInfo*)NULL ); + XP_ASSERT( score >= 0 || score == ILLEGAL_MOVE_SCORE ); + + player->curMoveScore = score; + player->curMoveValid = XP_TRUE; +} /* scoreCurrentMove */ + +void +adjustScoreForUndone( ModelCtxt* model, MoveInfo* mi, XP_U16 turn ) +{ + XP_U16 moveScore; + PlayerCtxt* player = &model->players[turn]; + + if ( mi->nTiles == 0 ) { + moveScore = 0; + } else { + moveScore = figureMoveScore( model, mi, (EngineCtxt*)NULL, + (XWStreamCtxt*)NULL, + (WordNotifierInfo*)NULL, NULL ); + } + player->score -= moveScore; + player->curMoveScore = 0; + player->curMoveValid = XP_TRUE; +} /* adjustScoreForUndone */ + +XP_Bool +model_checkMoveLegal( ModelCtxt* model, XP_S16 turn, XWStreamCtxt* stream, + WordNotifierInfo* notifyInfo ) +{ + XP_S16 score; + score = checkScoreMove( model, turn, (EngineCtxt*)NULL, stream, XP_FALSE, + notifyInfo ); + return score != ILLEGAL_MOVE_SCORE; +} /* model_checkMoveLegal */ + +void +invalidateScore( ModelCtxt* model, XP_S16 turn ) +{ + model->players[turn].curMoveValid = XP_FALSE; +} /* invalidateScore */ + +XP_Bool +getCurrentMoveScoreIfLegal( ModelCtxt* model, XP_S16 turn, + XWStreamCtxt* stream, XP_S16* score ) +{ + PlayerCtxt* player = &model->players[turn]; + if ( !player->curMoveValid ) { + scoreCurrentMove( model, turn, stream ); + } + + *score = player->curMoveScore; + return player->curMoveScore != ILLEGAL_MOVE_SCORE; +} /* getCurrentMoveScoreIfLegal */ + +XP_S16 +model_getPlayerScore( ModelCtxt* model, XP_S16 player ) +{ + return model->players[player].score; +} /* model_getPlayerScore */ + +/* Based on the current scores based on tiles played and the tiles left in the + * tray, return an array giving the left-over-tile-adjusted scores for each + * player. + */ +void +model_figureFinalScores( ModelCtxt* model, XP_S16* finalScoresP, + XP_S16* tilePenalties ) +{ + XP_S16 i, j; + XP_S16 penalties[MAX_NUM_PLAYERS]; + XP_S16 totalPenalty; + XP_U16 nPlayers = model->nPlayers; + XP_S16 firstDoneIndex = -1; /* not set unless FIRST_DONE_BONUS is set */ + const TrayTileSet* tray; + PlayerCtxt* player; + DictionaryCtxt* dict = model_getDictionary( model ); + CurGameInfo* gi = model->vol.gi; + + XP_MEMSET( finalScoresP, 0, sizeof(*finalScoresP) * MAX_NUM_PLAYERS ); + + totalPenalty = 0; + for ( player = model->players, i = 0; i < nPlayers; ++player, ++i ) { + tray = model_getPlayerTiles( model, i ); + + penalties[i] = 0; + + /* if there are no tiles left and this guy's the first done, make a + note of it in case he's to get a bonus. Note that this assumes + only one player can be out of tiles. */ + if ( (tray->nTiles == 0) && (firstDoneIndex == -1) ) { + firstDoneIndex = i; + } else { + for ( j = tray->nTiles-1; j >= 0; --j ) { + penalties[i] += dict_getTileValue( dict, tray->tiles[j] ); + } + } + + /* include tiles in pending move too for the player whose turn it + is. */ + for ( j = player->nPending - 1; j >= 0; --j ) { + Tile tile = player->pendingTiles[j].tile; + penalties[i] += dict_getTileValue(dict, (Tile)(tile & TILE_VALUE_MASK)); + } + totalPenalty += penalties[i]; + } + + /* now total everybody's scores */ + for ( i = 0; i < nPlayers; ++i ) { + XP_S16 score = model_getPlayerScore( model, i ); + XP_S16 penalty; + + penalty = (i == firstDoneIndex)? totalPenalty: -penalties[i]; + finalScoresP[i] = score + penalty; + + if ( !!tilePenalties ) { + tilePenalties[i] = penalty; + } + + if ( gi->timerEnabled ) { + penalty = player_timePenalty( gi, i ); + finalScoresP[i] -= penalty; + } + } +} /* model_figureFinalScores */ + +/* checkScoreMove. + * Negative score means illegal. + */ +static XP_S16 +checkScoreMove( ModelCtxt* model, XP_S16 turn, EngineCtxt* engine, + XWStreamCtxt* stream, XP_Bool silent, + WordNotifierInfo* notifyInfo ) +{ + XP_Bool isHorizontal; + XP_S16 score = ILLEGAL_MOVE_SCORE; + PlayerCtxt* player = &model->players[turn]; + + XP_ASSERT( player->nPending <= MAX_TRAY_TILES ); + + if ( player->nPending == 0 ) { + score = 0; + + if ( !!stream ) { + formatSummary( stream, model, 0 ); + } + + } else if ( tilesInLine( model, turn, &isHorizontal ) ) { + MoveInfo moveInfo; + + normalizeMoves( model, turn, isHorizontal, &moveInfo ); + + if ( isLegalMove( model, &moveInfo, silent ) ) { + score = figureMoveScore( model, &moveInfo, engine, stream, + notifyInfo, NULL ); + } + } else if ( !silent ) { /* tiles out of line */ + util_userError( model->vol.util, ERR_TILES_NOT_IN_LINE ); + } + return score; +} /* checkScoreMove */ + +XP_Bool +tilesInLine( ModelCtxt* model, XP_S16 turn, XP_Bool* isHorizontal ) +{ + XP_Bool xIsCommon, yIsCommon; + PlayerCtxt* player = &model->players[turn]; + PendingTile* pt = player->pendingTiles; + XP_U16 commonX = pt->col; + XP_U16 commonY = pt->row; + short i; + + xIsCommon = yIsCommon = XP_TRUE; + + for ( i = 1; ++pt, i < player->nPending; ++i ) { + // test the boolean first in case it's already been made false + // (to save time) + if ( xIsCommon && (pt->col != commonX) ) { + xIsCommon = XP_FALSE; + } + if ( yIsCommon && (pt->row != commonY) ) { + yIsCommon = XP_FALSE; + } + } + *isHorizontal = !xIsCommon; // so will be vertical if both true + return xIsCommon || yIsCommon; +} /* tilesInLine */ + +void +normalizeMoves( ModelCtxt* model, XP_S16 turn, XP_Bool isHorizontal, + MoveInfo* moveInfo ) +{ + XP_S16 lowCol, i, j, thisCol; /* unsigned is a problem on palm */ + PlayerCtxt* player = &model->players[turn]; + XP_U16 nTiles = player->nPending; + XP_S16 lastTaken; + short lowIndex = 0; + PendingTile* pt; + + moveInfo->isHorizontal = isHorizontal; + moveInfo->nTiles = (XP_U8)nTiles; + + lastTaken = -1; + for ( i = 0; i < nTiles; ++i ) { + lowCol = 100; /* high enough to always be changed */ + for ( j = 0; j < nTiles; ++j ) { + pt = &player->pendingTiles[j]; + thisCol = isHorizontal? pt->col:pt->row; + if (thisCol < lowCol && thisCol > lastTaken ) { + lowCol = thisCol; + lowIndex = j; + } + } + /* we've found the next to transfer (4 bytes smaller without a temp + local ptr. */ + pt = &player->pendingTiles[lowIndex]; + lastTaken = lowCol; + moveInfo->tiles[i].varCoord = (XP_U8)lastTaken; + + moveInfo->tiles[i].tile = pt->tile; + } + + pt = &player->pendingTiles[0]; + moveInfo->commonCoord = isHorizontal? pt->row:pt->col; +} /* normalizeMoves */ + +static XP_Bool +modelIsEmptyAt( const ModelCtxt* model, XP_U16 col, XP_U16 row ) +{ + Tile tile; + XP_Bool ignore; + XP_Bool found; + + found = model_getTile( model, col, row, XP_FALSE, -1, &tile, + &ignore, &ignore, (XP_Bool*)NULL ); + return !found; +} /* modelIsEmptyAt */ + +/***************************************************************************** + * Called only after moves have been confirmed to be in the same row, this + * function works whether the word is horizontal or vertical. + * + * For a move to be legal, either of the following must be true: a) + * if there are squares between those added in this move they must be occupied + * by previously placed pieces; or b) if these pieces are contiguous then at + * least one must touch a previously played piece (unless this is the first + * move) NOTE: this function does not verify that a newly placed piece is on an + * empty square. It's assumed that the calling code, most likely that which + * handles dragging the tiles, will have taken care of that. + ****************************************************************************/ +static XP_Bool +isLegalMove( ModelCtxt* model, MoveInfo* mInfo, XP_Bool silent ) +{ + XP_S16 high, low; + XP_S16 col, row; + XP_S16* incr; + XP_S16* commonP; + XP_U16 star_row = model_numRows(model) / 2; + + XP_S16 nTiles = mInfo->nTiles; + MoveInfoTile* moves = mInfo->tiles; + XP_U16 commonCoord = mInfo->commonCoord; + + /* First figure out what the low and high coordinates are in the dimension + not in common */ + low = moves[0].varCoord; + high = moves[nTiles-1].varCoord; + XP_ASSERT( (nTiles == 1) || (low < high) ); + + if ( mInfo->isHorizontal ) { + row = commonCoord; + incr = &col; + commonP = &row; + } else { + col = commonCoord; + incr = &row; + commonP = &col; + } + + /* are we looking at 2a above? */ + if ( (high - low + 1) > nTiles ) { + /* there should be no empty tiles between the ends */ + MoveInfoTile* newTile = moves; /* the newly placed tile to be checked */ + for ( *incr = low; *incr <= high; ++*incr ) { + if ( newTile->varCoord == *incr ) { + ++newTile; + } else if ( modelIsEmptyAt( model, col, row ) ) { + if ( !silent ) { + util_userError( model->vol.util, ERR_NO_EMPTIES_IN_TURN ); + } + return XP_FALSE; + } + } + XP_ASSERT( newTile == &moves[nTiles] ); + return XP_TRUE; + + /* else we're looking at 2b: make sure there's some contact UNLESS + this is the first move */ + } else { + /* check the ends first */ + if ( low != 0 ) { + *incr = low - 1; + if ( !modelIsEmptyAt( model, col, row ) ) { + return XP_TRUE; + } + } + if ( high != MAX_ROWS-1 ) { + *incr = high+1; + if ( !modelIsEmptyAt( model, col, row ) ) { + return XP_TRUE; + } + } + /* now the neighbors above... */ + if ( commonCoord != 0 ) { + --*commonP; /* decrement whatever's not being looped over */ + for ( *incr = low; *incr <= high; ++*incr ) { + if ( !modelIsEmptyAt( model, col, row ) ) { + return XP_TRUE; + } + } + ++*commonP;/* undo the decrement */ + } + /* ...and below */ + if ( commonCoord <= MAX_ROWS - 1 ) { + ++*commonP; + for ( *incr = low; *incr <= high; ++*incr ) { + if ( !modelIsEmptyAt( model, col, row ) ) { + return XP_TRUE; + } + } + --*commonP; + } + + /* if we got here, it's illegal unless this is the first move -- i.e. + unless one of the tiles is on the STAR */ + if ( ( commonCoord == star_row) && + ( low <= star_row) && ( high >= star_row ) ) { + if ( nTiles > 1 ) { + return XP_TRUE; + } else { + if ( !silent ) { + util_userError(model->vol.util, ERR_TWO_TILES_FIRST_MOVE); + } + return XP_FALSE; + } + } else { + if ( !silent ) { + util_userError( model->vol.util, ERR_TILES_MUST_CONTACT ); + } + return XP_FALSE; + } + } + XP_ASSERT( XP_FALSE ); + return XP_FALSE; /* keep compiler happy */ +} /* isLegalMove */ + +XP_U16 +figureMoveScore( const ModelCtxt* model, MoveInfo* moveInfo, + EngineCtxt* engine, XWStreamCtxt* stream, + WordNotifierInfo* notifyInfo, XP_UCHAR* mainWord ) +{ + XP_U16 col, row; + XP_U16* incr; + XP_U16 oneScore; + XP_U16 score = 0; + short i; + short moveMultiplier = 1; + short multipliers[MAX_TRAY_TILES]; + MoveInfo tmpMI; + MoveInfoTile* tiles; + XP_U16 nTiles = moveInfo->nTiles; + + XP_ASSERT( nTiles > 0 ); + + if ( moveInfo->isHorizontal ) { + row = moveInfo->commonCoord; + incr = &col; + } else { + col = moveInfo->commonCoord; + incr = &row; + } + + for ( i = 0; i < nTiles; ++i ) { + *incr = moveInfo->tiles[i].varCoord; + moveMultiplier *= multipliers[i] = word_multiplier( model, col, row ); + } + + oneScore = scoreWord( model, moveInfo, (EngineCtxt*)NULL, stream, + notifyInfo, mainWord ); + if ( !!stream ) { + formatWordScore( stream, oneScore, moveMultiplier ); + } + oneScore *= moveMultiplier; + score += oneScore; + + /* set up the invariant slots in tmpMI */ + tmpMI.isHorizontal = !moveInfo->isHorizontal; + tmpMI.nTiles = 1; + tmpMI.tiles[0].varCoord = moveInfo->commonCoord; + + for ( i = 0, tiles = moveInfo->tiles; i < nTiles; ++i, ++tiles ) { + + /* Moves using only one tile will sometimes score only in the + crosscheck direction. Score may still be 0 after the call to + scoreWord above. Keep trying to get some text in mainWord until + something's been scored. */ + if ( score > 0 ) { + mainWord = NULL; + } + + tmpMI.commonCoord = tiles->varCoord; + tmpMI.tiles[0].tile = tiles->tile; + + oneScore = scoreWord( model, &tmpMI, engine, stream, + notifyInfo, mainWord ); + if ( !!stream ) { + formatWordScore( stream, oneScore, multipliers[i] ); + } + oneScore *= multipliers[i]; + score += oneScore; + } + + /* did he use all 7 tiles? */ + if ( nTiles == MAX_TRAY_TILES ) { + score += EMPTIED_TRAY_BONUS; + + if ( !!stream ) { + const XP_UCHAR* bstr = util_getUserString( model->vol.util, + STR_BONUS_ALL ); + stream_putString( stream, bstr ); + } + } + + if ( !!stream ) { + formatSummary( stream, model, score ); + } + + return score; +} /* figureMoveScore */ + +static XP_U16 +word_multiplier( const ModelCtxt* model, XP_U16 col, XP_U16 row ) +{ + XWBonusType bonus = util_getSquareBonus( model->vol.util, model, col, row ); + switch ( bonus ) { + case BONUS_DOUBLE_WORD: + return 2; + case BONUS_TRIPLE_WORD: + return 3; + default: + return 1; + } +} /* word_multiplier */ + +static XP_U16 +tile_multiplier( const ModelCtxt* model, XP_U16 col, XP_U16 row ) +{ + XWBonusType bonus = util_getSquareBonus( model->vol.util, model, + col, row ); + switch ( bonus ) { + case BONUS_DOUBLE_LETTER: + return 2; + case BONUS_TRIPLE_LETTER: + return 3; + default: + return 1; + } +} /* tile_multiplier */ + +static XP_U16 +scoreWord( const ModelCtxt* model, MoveInfo* movei, /* new tiles */ + EngineCtxt* engine,/* for crosswise caching */ + XWStreamCtxt* stream, + WordNotifierInfo* notifyInfo, + XP_UCHAR* mainWord ) +{ + XP_U16 tileMultiplier; + XP_U16 restScore = 0; + XP_U16 scoreFromCache; + XP_U16 thisTileValue; + XP_U16 nTiles = movei->nTiles; + Tile tile; + XP_U16 start, end; + XP_U16* incr; + XP_U16 col, row; + MoveInfoTile* tiles = movei->tiles; + XP_U16 firstCoord = tiles->varCoord; + DictionaryCtxt* dict = model->vol.dict; + + if ( movei->isHorizontal ) { + row = movei->commonCoord; + incr = &col; + } else { + col = movei->commonCoord; + incr = &row; + } + + *incr = tiles[nTiles-1].varCoord; + end = find_end( model, col, row, movei->isHorizontal ); + + /* This is the value *incr needs to start with below */ + *incr = tiles[0].varCoord; + start = find_start( model, col, row, movei->isHorizontal ); + + if ( (end - start) >= 1 ) { /* one-letter word: score 0 */ + WordScoreFormatter fmtr; + if ( !!stream || !!mainWord ) { + wordScoreFormatterInit( &fmtr, dict ); + } + + if ( IS_BLANK(tiles->tile) ) { + tile = dict_getBlankTile( dict ); + } else { + tile = tiles->tile & TILE_VALUE_MASK; + } + thisTileValue = dict_getTileValue( dict, tile ); + + XP_ASSERT( *incr == tiles[0].varCoord ); + thisTileValue *= tile_multiplier( model, col, row ); + + XP_ASSERT( engine == NULL || nTiles == 1 ); + + if ( engine != NULL ) { + XP_ASSERT( nTiles==1 ); + scoreFromCache = engine_getScoreCache( engine, + movei->commonCoord ); + } + + /* for a while, at least, calculate and use the cached crosscheck score + * each time through in the debug case */ + if ( 0 ) { /* makes keeping parens balanced easier */ +#ifdef DEBUG + } else if ( 1 ) { +#else + } else if ( engine == NULL ) { +#endif + Tile checkWordBuf[MAX_ROWS]; + Tile* curTile = checkWordBuf; + + for ( *incr = start; *incr <= end; ++*incr ) { + XP_U16 tileScore = 0; + XP_Bool isBlank; + + /* a new move? */ + if ( (nTiles > 0) && (*incr == tiles->varCoord) ) { + tile = tiles->tile & TILE_VALUE_MASK; + isBlank = IS_BLANK(tiles->tile); + /* don't call localGetBlankTile when in silent (robot called) + * mode, as the blank won't be known there. (Assert will + * fail.) */ + + tileMultiplier = tile_multiplier( model, col, row ); + ++tiles; + --nTiles; + } else { /* placed on the board before this move */ + XP_Bool ignore; + tileMultiplier = 1; + + (void)model_getTile( model, col, row, XP_FALSE, -1, &tile, + &isBlank, &ignore, (XP_Bool*)NULL ); + + XP_ASSERT( (tile & TILE_VALUE_MASK) == tile ); + } + + *curTile++ = tile; /* save in case we're checking phonies */ + + if ( !!stream || !!mainWord ) { + wordScoreFormatterAddTile( &fmtr, tile, tileMultiplier, + isBlank ); + } + + if ( isBlank ) { + tile = dict_getBlankTile( dict ); + } + tileScore = dict_getTileValue( dict, tile ); + + /* The first tile in the move is already accounted for in + thisTileValue, so skip it here. */ + if ( *incr != firstCoord ) { + restScore += tileScore * tileMultiplier; + } + } /* for each tile */ + + if ( !!notifyInfo ) { + XP_U16 len = curTile - checkWordBuf; + XP_Bool legal = engine_check( dict, checkWordBuf, len ); + + if ( !legal ) { + XP_UCHAR buf[(MAX_ROWS*2)+1]; + dict_tilesToString( dict, checkWordBuf, len, buf, + sizeof(buf) ); + (*notifyInfo->proc)( buf, notifyInfo->closure ); + } + } + + if ( !!stream || !!mainWord ) { + wordScoreFormatterFinish( &fmtr, checkWordBuf, stream, + mainWord ); + } +#ifdef DEBUG + + } else if ( engine != NULL ) { +#else + } else { /* non-debug case we know it's non-null */ +#endif + XP_ASSERT( nTiles==1 ); + XP_ASSERT( engine_getScoreCache( engine, movei->commonCoord ) + == restScore ); + restScore = engine_getScoreCache( engine, movei->commonCoord ); + } + + restScore += thisTileValue; + } + + return restScore; +} /* scoreWord */ + +static XP_U16 +find_start( const ModelCtxt* model, XP_U16 col, XP_U16 row, + XP_Bool isHorizontal ) +{ + XP_U16* incr = isHorizontal? &col: &row; + + for ( ; ; ) { + if ( *incr == 0 ) { + return 0; + } else { + --*incr; + if ( modelIsEmptyAt( model, col, row ) ) { + return *incr + 1; + } + } + } +} /* find_start */ + +static XP_U16 +find_end( const ModelCtxt* model, XP_U16 col, XP_U16 row, + XP_Bool isHorizontal ) +{ + XP_U16* incr = isHorizontal? &col: &row; + XP_U16 limit = isHorizontal? MAX_COLS-1:MAX_ROWS-1; + XP_U16 lastGood = *incr; + + XP_ASSERT( col < MAX_COLS ); + XP_ASSERT( row < MAX_ROWS ); + + for ( ; ; ) { + XP_ASSERT( *incr <= limit ); + if ( *incr == limit ) { + return limit; + } else { + ++*incr; + if ( modelIsEmptyAt( model, col, row ) ) { + return lastGood; + } else { + lastGood = *incr; + } + } + } +} /* find_end */ + +static void +wordScoreFormatterInit( WordScoreFormatter* fmtr, DictionaryCtxt* dict ) +{ + XP_MEMSET( fmtr, 0, sizeof(*fmtr) ); + + fmtr->dict = dict; + + fmtr->firstPass = XP_TRUE; +} /* initWordScoreFormatter */ + +static void +wordScoreFormatterAddTile( WordScoreFormatter* fmtr, Tile tile, + XP_U16 tileMultiplier, XP_Bool isBlank ) +{ + XP_UCHAR buf[4]; + XP_UCHAR* fullBufPtr; + XP_UCHAR* prefix; + XP_U16 tileScore; + + ++fmtr->nTiles; + + dict_tilesToString( fmtr->dict, &tile, 1, buf, sizeof(buf) ); + XP_ASSERT( XP_STRLEN(fmtr->wordBuf) + XP_STRLEN(buf) < sizeof(fmtr->wordBuf) ); + XP_STRCAT( fmtr->wordBuf, buf ); + if ( isBlank ) { + tile = dict_getBlankTile( fmtr->dict ); + } + + tileScore = dict_getTileValue( fmtr->dict, tile ); + + if ( fmtr->firstPass ) { + prefix = (XP_UCHAR*)" ["; + fmtr->firstPass = XP_FALSE; + } else { + prefix = (XP_UCHAR*)"+"; + } + + fullBufPtr = fmtr->fullBuf + fmtr->bufLen; + fmtr->bufLen += + XP_SNPRINTF( fullBufPtr, + (XP_U16)(sizeof(fmtr->fullBuf) - fmtr->bufLen), + (XP_UCHAR*)(tileMultiplier > 1?"%s(%dx%d)":"%s%d"), + prefix, tileScore, tileMultiplier ); + + XP_ASSERT( XP_STRLEN(fmtr->fullBuf) == fmtr->bufLen ); + XP_ASSERT( fmtr->bufLen < sizeof(fmtr->fullBuf) ); +} /* wordScoreFormatterAddTile */ + +static void +wordScoreFormatterFinish( WordScoreFormatter* fmtr, Tile* word, XWStreamCtxt* stream, + XP_UCHAR* mainWord ) +{ + XP_UCHAR buf[(MAX_ROWS*2)+1]; + XP_U16 len = dict_tilesToString( fmtr->dict, word, fmtr->nTiles, + buf, sizeof(buf) ); + + if ( !!stream ) { + stream_putBytes( stream, buf, len ); + + stream_putBytes( stream, fmtr->fullBuf, fmtr->bufLen ); + stream_putU8( stream, ']' ); + } + + if ( !!mainWord ) { + XP_MEMCPY( mainWord, fmtr->wordBuf, XP_STRLEN(fmtr->wordBuf) + 1 ); + } + +} /* wordScoreFormatterFinish */ + +static void +formatWordScore( XWStreamCtxt* stream, XP_U16 wordScore, + XP_U16 moveMultiplier ) +{ + if ( wordScore > 0 ) { + XP_U16 multipliedScore = wordScore * moveMultiplier; + XP_UCHAR tmpBuf[40]; + if ( moveMultiplier > 1 ) { + XP_SNPRINTF( tmpBuf, sizeof(tmpBuf), + (XP_UCHAR*)" => %d x %d = %d" XP_CR, + wordScore, moveMultiplier, multipliedScore ); + } else { + XP_SNPRINTF( tmpBuf, sizeof(tmpBuf), (XP_UCHAR*)" = %d" XP_CR, + multipliedScore ); + } + XP_ASSERT( XP_STRLEN(tmpBuf) < sizeof(tmpBuf) ); + + stream_putString( stream, tmpBuf ); + } +} /* formatWordScore */ + +static void +formatSummary( XWStreamCtxt* stream, const ModelCtxt* model, XP_U16 score ) +{ + XP_UCHAR buf[60]; + XP_SNPRINTF(buf, sizeof(buf), + util_getUserString(model->vol.util, STRD_TURN_SCORE), + score); + XP_ASSERT( XP_STRLEN(buf) < sizeof(buf) ); + stream_putString( stream, buf ); +} /* formatSummary */ + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/nwgamest.c b/xwords4/common/nwgamest.c new file mode 100644 index 000000000..2b97ad8c5 --- /dev/null +++ b/xwords4/common/nwgamest.c @@ -0,0 +1,581 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2007 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#include "nwgamest.h" +#include "strutils.h" +#include "LocalizedStrIncludes.h" + +#ifdef CPLUS +extern "C" { +#endif + +#define NG_NUM_COLS ((XP_U16)(NG_COL_PASSWD+1)) + +struct NewGameCtx { + NewGameEnableColProc enableColProc; + NewGameEnableAttrProc enableAttrProc; + NewGameSetColProc setColProc; + NewGameGetColProc getColProc; + NewGameSetAttrProc setAttrProc; + XW_UtilCtxt* util; + void* closure; + + /* Palm needs to store cleartext passwords separately in order to + store '***' in the visible field */ + XP_TriEnable enabled[NG_NUM_COLS][MAX_NUM_PLAYERS]; + XP_U16 nPlayersShown; /* real nPlayers lives in gi */ + XP_U16 nPlayersTotal; /* used only until changedNPlayers set */ + XP_U16 nLocalPlayers; /* not changed except in newg_load */ + DeviceRole role; + XP_Bool isNewGame; + XP_Bool changedNPlayers; + XP_TriEnable juggleEnabled; + + MPSLOT +}; + +static void enableOne( NewGameCtx* ngc, XP_U16 player, NewGameColumn col, + XP_TriEnable enable, XP_Bool force ); +static void adjustAllRows( NewGameCtx* ngc, XP_Bool force ); +static void adjustOneRow( NewGameCtx* ngc, XP_U16 player, XP_Bool force ); +static void setRoleStrings( NewGameCtx* ngc ); +static void considerEnableJuggle( NewGameCtx* ngc ); +static void storePlayer( NewGameCtx* ngc, XP_U16 player, LocalPlayer* lp ); +static void loadPlayer( NewGameCtx* ngc, XP_U16 player, + const LocalPlayer* lp ); +#ifndef XWFEATURE_STANDALONE_ONLY +static XP_Bool changeRole( NewGameCtx* ngc, DeviceRole role ); +static XP_Bool checkConsistent( NewGameCtx* ngc, XP_Bool warnUser ); +#else +# define checkConsistent( ngc, warnUser ) XP_TRUE +#endif + +NewGameCtx* +newg_make( MPFORMAL XP_Bool isNewGame, + XW_UtilCtxt* util, + NewGameEnableColProc enableColProc, + NewGameEnableAttrProc enableAttrProc, + NewGameGetColProc getColProc, NewGameSetColProc setColProc, + NewGameSetAttrProc setAttrProc, void* closure ) +{ + NewGameCtx* result = XP_MALLOC( mpool, sizeof(*result) ); + XP_MEMSET( result, 0, sizeof(*result) ); + + result->enableColProc = enableColProc; + result->enableAttrProc = enableAttrProc; + result->setColProc = setColProc; + result->getColProc = getColProc; + result->setAttrProc = setAttrProc; + result->closure = closure; + result->isNewGame = isNewGame; + result->util = util; + MPASSIGN(result->mpool, mpool); + + return result; +} /* newg_make */ + +void +newg_destroy( NewGameCtx* ngc ) +{ + XP_FREE( ngc->mpool, ngc ); +} /* newg_destroy */ + +void +newg_load( NewGameCtx* ngc, const CurGameInfo* gi ) +{ + void* closure = ngc->closure; + NGValue value; + XP_U16 nPlayers, nLoaded; + XP_S16 ii, jj; + DeviceRole role; + XP_Bool localOnly; + XP_Bool shown[MAX_NUM_PLAYERS] = { XP_FALSE, XP_FALSE, XP_FALSE, XP_FALSE}; + + ngc->juggleEnabled = TRI_ENAB_NONE; + for ( ii = 0; ii < NG_NUM_COLS; ++ii ) { + for ( jj = 0; jj < MAX_NUM_PLAYERS; ++jj ) { + ngc->enabled[ii][jj] = TRI_ENAB_NONE; + } + } + + ngc->role = role = gi->serverRole; + localOnly = role == SERVER_ISCLIENT && ngc->isNewGame; +#ifndef XWFEATURE_STANDALONE_ONLY + value.ng_role = role; + (*ngc->setAttrProc)( closure, NG_ATTR_ROLE, value ); + (*ngc->enableAttrProc)( closure, NG_ATTR_ROLE, ngc->isNewGame? + TRI_ENAB_ENABLED : TRI_ENAB_DISABLED ); +#endif + + nPlayers = gi->nPlayers; + ngc->nPlayersTotal = nPlayers; +#ifndef XWFEATURE_STANDALONE_ONLY + for ( ii = nPlayers - 1; ii >= 0; --ii ) { + if ( gi->players[ii].isLocal ) { + ++ngc->nLocalPlayers; + } + } +#endif + if ( localOnly ) { + nPlayers = ngc->nLocalPlayers; + } + ngc->nPlayersShown = nPlayers; + + value.ng_u16 = ngc->nPlayersShown; + (*ngc->setAttrProc)( closure, NG_ATTR_NPLAYERS, value ); + (*ngc->enableAttrProc)( closure, NG_ATTR_NPLAYERS, ngc->isNewGame? + TRI_ENAB_ENABLED : TRI_ENAB_DISABLED ); + + setRoleStrings( ngc ); + considerEnableJuggle( ngc ); + + /* Load local players first */ + nLoaded = 0; + do { + for ( ii = 0; ii < MAX_NUM_PLAYERS; ++ii ) { + if ( !shown[ii] ) { + const LocalPlayer* lp = &gi->players[ii]; + if ( !localOnly + || (lp->isLocal && (nLoaded < ngc->nLocalPlayers)) ) { + shown[ii] = XP_TRUE; + loadPlayer( ngc, nLoaded++, lp ); + } + } + } + XP_ASSERT( localOnly || nLoaded == MAX_NUM_PLAYERS ); + localOnly = XP_FALSE; /* for second pass */ + } while ( nLoaded < MAX_NUM_PLAYERS ); + + adjustAllRows( ngc, XP_TRUE ); +} /* newg_load */ + +typedef struct NGCopyClosure { + XP_U16 player; + NewGameColumn col; + NewGameCtx* ngc; + LocalPlayer* lp; +} NGCopyClosure; + +static void +cpToLP( NGValue value, const void* cbClosure ) +{ + NGCopyClosure* cpcl = (NGCopyClosure*)cbClosure; + LocalPlayer* lp = cpcl->lp; + XP_UCHAR** strAddr = NULL; + + switch ( cpcl->col ) { +#ifndef XWFEATURE_STANDALONE_ONLY + case NG_COL_REMOTE: + lp->isLocal = !value.ng_bool; + break; +#endif + case NG_COL_NAME: + strAddr = &lp->name; + break; + case NG_COL_PASSWD: + strAddr = &lp->password; + break; + case NG_COL_ROBOT: + lp->isRobot = value.ng_bool; + break; + } + + if ( !!strAddr ) { + /* This is leaking!!! But doesn't leak if am playing via IR, at least + in the simulator. */ + replaceStringIfDifferent( cpcl->ngc->mpool, strAddr, + value.ng_cp ); + } +} /* cpToLP */ + +XP_Bool +newg_store( NewGameCtx* ngc, CurGameInfo* gi, + XP_Bool XP_UNUSED_STANDALONE(warn) ) +{ + XP_U16 player; + XP_Bool consistent = checkConsistent( ngc, warn ); + + if ( consistent ) { + gi->nPlayers = ngc->nPlayersShown; +#ifndef XWFEATURE_STANDALONE_ONLY + gi->serverRole = ngc->role; +#endif + + for ( player = 0; player < MAX_NUM_PLAYERS; ++player ) { + storePlayer( ngc, player, &gi->players[player] ); + } + } + return consistent; +} /* newg_store */ + +void +newg_colChanged( NewGameCtx* ngc, XP_U16 player ) +{ + /* Sometimes we'll get this notification for inactive rows, e.g. when + setting default values. */ + if ( player < ngc->nPlayersShown ) { + adjustOneRow( ngc, player, XP_FALSE ); + } +} + +void +newg_attrChanged( NewGameCtx* ngc, NewGameAttr attr, NGValue value ) +{ + XP_Bool changed = XP_FALSE; + if ( attr == NG_ATTR_NPLAYERS ) { + if ( ngc->nPlayersShown != value.ng_u16 ) { + ngc->nPlayersShown = value.ng_u16; + ngc->changedNPlayers = XP_TRUE; + changed = XP_TRUE; + } +#ifndef XWFEATURE_STANDALONE_ONLY + } else if ( NG_ATTR_ROLE == attr ) { + changed = changeRole( ngc, value.ng_role ); +#endif + } else { + XP_ASSERT( 0 ); + } + + if ( changed ) { + considerEnableJuggle( ngc ); + adjustAllRows( ngc, XP_FALSE ); + } +} + +typedef struct DeepValue { + NGValue value; + NewGameColumn col; + MPSLOT +} DeepValue; + +static void +deepCopy( NGValue value, const void* closure ) +{ + DeepValue* dvp = (DeepValue*)closure; + switch ( dvp->col ) { + case NG_COL_ROBOT: +#ifndef XWFEATURE_STANDALONE_ONLY + case NG_COL_REMOTE: +#endif + dvp->value.ng_bool = value.ng_bool; + break; + case NG_COL_NAME: + case NG_COL_PASSWD: + dvp->value.ng_cp = copyString( dvp->mpool, value.ng_cp ); + break; + } +} + +XP_Bool +newg_juggle( NewGameCtx* ngc ) +{ + XP_Bool changed = XP_FALSE; + XP_U16 nPlayers = ngc->nPlayersShown; + + XP_ASSERT( ngc->isNewGame ); + + if ( nPlayers > 1 ) { + LocalPlayer tmpPlayers[MAX_NUM_PLAYERS]; + XP_U16 pos[MAX_NUM_PLAYERS]; + XP_U16 player; + + /* Get a randomly juggled array of numbers 0..nPlayers-1. Then the + number at pos[n] inicates where the entry currently at n should + be. */ + changed = randIntArray( pos, nPlayers ); + if ( changed ) { + + /* Deep-copy off to tmp storage. But skip lines that won't be moved + in the juggle. */ + XP_MEMSET( &tmpPlayers, 0, sizeof(tmpPlayers) ); + for ( player = 0; player < nPlayers; ++player ) { + if ( player != pos[player] ) { + storePlayer( ngc, player, &tmpPlayers[player] ); + } + } + + for ( player = 0; player < nPlayers; ++player ) { + if ( player != pos[player] ) { + LocalPlayer* lp = &tmpPlayers[player]; + XP_U16 dest = pos[player]; + + loadPlayer( ngc, dest, lp ); + + if ( !!lp->name ) { + XP_FREE( ngc->mpool, lp->name ); + } + if ( !!lp->password ) { + XP_FREE( ngc->mpool, lp->password ); + } + + adjustOneRow( ngc, dest, XP_FALSE ); + } + } + } + } + return changed; +} /* newg_juggle */ + +#ifndef XWFEATURE_STANDALONE_ONLY +static XP_Bool +checkConsistent( NewGameCtx* ngc, XP_Bool warnUser ) +{ + XP_Bool consistent; + XP_U16 i; + + /* If ISSERVER, make sure there's at least one non-local player. */ + consistent = ngc->role != SERVER_ISSERVER; + for ( i = 0; !consistent && i < ngc->nPlayersShown; ++i ) { + DeepValue dValue; + dValue.col = NG_COL_REMOTE; + (*ngc->getColProc)( ngc->closure, i, NG_COL_REMOTE, + deepCopy, &dValue ); + if ( dValue.value.ng_bool ) { + consistent = XP_TRUE; + } + } + if ( !consistent && warnUser ) { + util_userError( ngc->util, ERR_REG_SERVER_SANS_REMOTE ); + } + + /* Add other consistency checks, and error messages, here. */ + + return consistent; +} /* checkConsistent */ +#endif + +static void +enableOne( NewGameCtx* ngc, XP_U16 player, NewGameColumn col, + XP_TriEnable enable, XP_Bool force ) +{ + XP_TriEnable* esp = &ngc->enabled[col][player]; + if ( force || (*esp != enable) ) { + (*ngc->enableColProc)( ngc->closure, player, col, enable ); + } + *esp = enable; +} /* enableOne */ + +static void +adjustAllRows( NewGameCtx* ngc, XP_Bool force ) +{ + XP_U16 player; + for ( player = 0; player < MAX_NUM_PLAYERS; ++player ) { + adjustOneRow( ngc, player, force ); + } +} /* adjustAllRows */ + +static void +adjustOneRow( NewGameCtx* ngc, XP_U16 player, XP_Bool force ) +{ + XP_TriEnable enable[NG_NUM_COLS]; + NewGameColumn col; + XP_Bool isLocal = XP_TRUE; + XP_Bool isNewGame = ngc->isNewGame; + DeviceRole role = ngc->role; + DeepValue dValue; + + for ( col = 0; col < NG_NUM_COLS; ++col ) { + enable[col] = TRI_ENAB_HIDDEN; + } + + /* If there aren't this many players, all are disabled */ + if ( player >= ngc->nPlayersShown ) { + /* do nothing: all are hidden above */ + } else { +#ifndef XWFEATURE_STANDALONE_ONLY + /* If standalone or client, remote is hidden. If server but not + new game, it's disabled */ + if ( (role == SERVER_ISSERVER ) + || (role == SERVER_ISCLIENT && !isNewGame ) ) { + if ( isNewGame ) { + enable[NG_COL_REMOTE] = TRI_ENAB_ENABLED; + } else { + enable[NG_COL_REMOTE] = TRI_ENAB_DISABLED; + } + dValue.col = NG_COL_REMOTE; + (*ngc->getColProc)( ngc->closure, player, NG_COL_REMOTE, + deepCopy, &dValue ); + isLocal = !dValue.value.ng_bool; + } +#endif + + /* If remote is enabled and set, then if it's a new game all else is + hidden. But if it's not a new game, they're disabled. Password is + always hidden if robot is set. */ + if ( isLocal ) { + XP_TriEnable tmp; + + /* No changing name or robotness since they're sent to remote + host. */ + tmp = (isNewGame || role == SERVER_STANDALONE)? + TRI_ENAB_ENABLED:TRI_ENAB_DISABLED; + enable[NG_COL_NAME] = tmp; + enable[NG_COL_ROBOT] = tmp; + + /* Password and game info (the not isNewGame case): passwords are + not transmitted: they're local only. There's no harm in + allowing local players to change them. So passwords should be + enabled whenever it's not a robot regardless of both isNewGame + and role. */ + + dValue.col = NG_COL_ROBOT; + (*ngc->getColProc)( ngc->closure, player, NG_COL_ROBOT, deepCopy, + &dValue ); + if ( !dValue.value.ng_bool ) { + /* If it's a robot, leave it hidden */ + enable[NG_COL_PASSWD] = TRI_ENAB_ENABLED; + } + + } else { + if ( isNewGame ) { + /* leave 'em hidden */ + } else { + enable[NG_COL_NAME] = TRI_ENAB_DISABLED; + enable[NG_COL_ROBOT] = TRI_ENAB_DISABLED; + /* leave passwd hidden */ + } + } + } + + for ( col = 0; col < NG_NUM_COLS; ++col ) { + enableOne( ngc, player, col, enable[col], force ); + } +} /* adjustOneRow */ + +/* changeRole. When role changes, number of players displayed, and which + * players, may change. Host shows all players (up to nPlayers). Guest shows + * only local players, but if role changes should show the rest. Change from + * Host or Standalone to guest should reduce the number shown. + * + * Here's the fun part: what happens when user changes nPlayers, then changes + * role? Say we're a guest with one player. User makes it two, than makes us + * host. Do we pull in a new player? No. Let's not change any of this stuff + * ONCE USER'S CHANGED NPLAYERS. Goal is to prevent his having to do that for + * the most common case, which is playing again with the same players. In + * that case changing role then back again should not lose/change data. + */ +#ifndef XWFEATURE_STANDALONE_ONLY +static XP_Bool +changeRole( NewGameCtx* ngc, DeviceRole newRole ) +{ + DeviceRole oldRole = ngc->role; + XP_Bool changing = oldRole != newRole; + if ( changing ) { + if ( !ngc->changedNPlayers ) { + NGValue value; + if ( newRole == SERVER_ISCLIENT ) { + value.ng_u16 = ngc->nLocalPlayers; + } else { + value.ng_u16 = ngc->nPlayersTotal; + } + if ( value.ng_u16 != ngc->nPlayersShown ) { + ngc->nPlayersShown = value.ng_u16; + (*ngc->setAttrProc)( ngc->closure, NG_ATTR_NPLAYERS, value ); + } + } + ngc->role = newRole; + setRoleStrings( ngc ); + } + return changing; +} +#endif + +static void +setRoleStrings( NewGameCtx* ngc ) +{ + XP_U16 strID; + NGValue value; + void* closure = ngc->closure; + /* Tell client to set/change players label text, and also to add remote + checkbox column header if required. */ + +#ifndef XWFEATURE_STANDALONE_ONLY + (*ngc->enableAttrProc)( closure, NG_ATTR_REMHEADER, + ( (ngc->role == SERVER_ISSERVER) + || (!ngc->isNewGame + && (ngc->role != SERVER_STANDALONE)) )? + TRI_ENAB_ENABLED : TRI_ENAB_HIDDEN ); +#endif + + if ( 0 ) { +#ifndef XWFEATURE_STANDALONE_ONLY + } else if ( ngc->role == SERVER_ISCLIENT && ngc->isNewGame ) { + strID = STR_LOCALPLAYERS; +#endif + } else { + strID = STR_TOTALPLAYERS; + } + + value.ng_cp = util_getUserString( ngc->util, strID ); + (*ngc->setAttrProc)( closure, NG_ATTR_NPLAYHEADER, value ); +} /* setRoleStrings */ + +static void +considerEnableJuggle( NewGameCtx* ngc ) +{ + XP_TriEnable newEnable; + newEnable = (ngc->isNewGame && ngc->nPlayersShown > 1)? + TRI_ENAB_ENABLED : TRI_ENAB_HIDDEN; + + if ( newEnable != ngc->juggleEnabled ) { + (*ngc->enableAttrProc)( ngc->closure, NG_ATTR_CANJUGGLE, newEnable ); + ngc->juggleEnabled = newEnable; + } +} /* considerEnableJuggle */ + +static void +storePlayer( NewGameCtx* ngc, XP_U16 player, LocalPlayer* lp ) +{ + void* closure = ngc->closure; + NGCopyClosure cpcl; + cpcl.player = player; + cpcl.ngc = ngc; + cpcl.lp = lp; + + for ( cpcl.col = 0; cpcl.col < NG_NUM_COLS; ++cpcl.col ) { + (*ngc->getColProc)( closure, cpcl.player, cpcl.col, + cpToLP, &cpcl ); + } +} + +static void +loadPlayer( NewGameCtx* ngc, XP_U16 player, const LocalPlayer* lp ) +{ + NGValue value; + void* closure = ngc->closure; + +#ifndef XWFEATURE_STANDALONE_ONLY + value.ng_bool = !lp->isLocal; + (*ngc->setColProc)(closure, player, NG_COL_REMOTE, value ); +#endif + value.ng_cp = lp->name; + (*ngc->setColProc)(closure, player, NG_COL_NAME, value ); + + value.ng_cp = lp->password; + (*ngc->setColProc)(closure, player, NG_COL_PASSWD, value ); + + value.ng_bool = lp->isRobot; + (*ngc->setColProc)(closure, player, NG_COL_ROBOT, value ); +} + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/nwgamest.h b/xwords4/common/nwgamest.h new file mode 100644 index 000000000..afd7d1959 --- /dev/null +++ b/xwords4/common/nwgamest.h @@ -0,0 +1,115 @@ + /* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2006 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _NWGAMEST_H_ +#define _NWGAMEST_H_ + +/* The new game/game info dialog is complicated, especially in non + * XWFEATURE_STANDALONE_ONLY case. The number of rows must be changed + * as the number of players changes, and whether the password field is + * enabled changes with the robot status etc. This file encapsulates + * all that logic, reducint the platform's role to reporting UI events + * and reflecting state changes, as reported by callbacks, in the + * platform's widgets. + */ + +#include "comtypes.h" +EXTERN_C_START + +#include "mempool.h" +#include "server.h" +#include "comms.h" +#include "util.h" +#include "game.h" + +typedef struct NewGameCtx NewGameCtx; + + +typedef enum { +#ifndef XWFEATURE_STANDALONE_ONLY + NG_COL_REMOTE, +#endif + NG_COL_NAME + ,NG_COL_ROBOT + ,NG_COL_PASSWD +} NewGameColumn; + +typedef enum { +#ifndef XWFEATURE_STANDALONE_ONLY + NG_ATTR_ROLE, + NG_ATTR_REMHEADER, +#endif + NG_ATTR_NPLAYERS + ,NG_ATTR_NPLAYHEADER + ,NG_ATTR_CANJUGGLE +} NewGameAttr; + +typedef union NGValue { + const XP_UCHAR* ng_cp; + XP_U16 ng_u16; + XP_Bool ng_bool; + DeviceRole ng_role; +} NGValue; + +/* Enable or disable (show or hide) controls */ +typedef void (*NewGameEnableColProc)( void* closure, XP_U16 player, + NewGameColumn col, XP_TriEnable enable ); +typedef void (*NewGameEnableAttrProc)( void* closure, NewGameAttr attr, + XP_TriEnable enable ); +/* Get the contents of a control. Type of param "value" is either + boolean or char* */ +typedef void (*NgCpCallbk)( NGValue value, const void* cpClosure ); +typedef void (*NewGameGetColProc)( void* closure, XP_U16 player, + NewGameColumn col, + NgCpCallbk cpcb, const void* cbClosure ); +/* Set the contents of a control. Type of param "value" is either + boolean or char* */ +typedef void (*NewGameSetColProc)( void* closure, XP_U16 player, + NewGameColumn col, const NGValue value ); + +typedef void (*NewGameSetAttrProc)(void* closure, NewGameAttr attr, + const NGValue value ); + + +NewGameCtx* newg_make( MPFORMAL XP_Bool isNewGame, + XW_UtilCtxt* util, + NewGameEnableColProc enableColProc, + NewGameEnableAttrProc enableAttrProc, + NewGameGetColProc getColProc, + NewGameSetColProc setColProc, + NewGameSetAttrProc setAttrProc, + void* closure ); +void newg_destroy( NewGameCtx* ngc ); + +void newg_load( NewGameCtx* ngc, const CurGameInfo* gi ); +XP_Bool newg_store( NewGameCtx* ngc, CurGameInfo* gi, XP_Bool warn ); + +void newg_colChanged( NewGameCtx* ngc, XP_U16 player ); +void newg_attrChanged( NewGameCtx* ngc, NewGameAttr attr, + NGValue value ); + +/** newg_juggle: Return XP_TRUE if a juggle happened, XP_FALSE if randomness + * dictated that all players stay put. Platforms can call repeatedly until + * true if they want to force change. + */ +XP_Bool newg_juggle( NewGameCtx* ngc ); + +EXTERN_C_END + +#endif /* _NWGAMEST_H_ */ diff --git a/xwords4/common/pool.c b/xwords4/common/pool.c new file mode 100644 index 000000000..699bf59c4 --- /dev/null +++ b/xwords4/common/pool.c @@ -0,0 +1,240 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* #include */ + +#include "pool.h" +#include "dictnry.h" +#include "xwstream.h" + +#define pEND 0x70454e44 + +// #define BLANKS_FIRST 1 + +struct PoolContext { + XP_U8* lettersLeft; + XP_U16 numTilesLeft; + XP_U16 numFaces; +#ifdef BLANKS_FIRST + XP_S16 blankIndex; +#endif + MPSLOT +}; + +PoolContext* +pool_make( MPFORMAL_NOCOMMA ) +{ + PoolContext* result = (PoolContext*)XP_MALLOC(mpool, sizeof(*result) ); + + if ( result != NULL ) { + XP_MEMSET( result, 0, sizeof( *result ) ); + MPASSIGN(result->mpool, mpool); + +#ifdef BLANKS_FIRST + result->blankIndex = -1; +#endif + } + + return result; +} /* pool_make */ + +void +pool_writeToStream( PoolContext* pool, XWStreamCtxt* stream ) +{ + stream_putU16( stream, pool->numTilesLeft ); + stream_putU16( stream, pool->numFaces ); + stream_putBytes( stream, pool->lettersLeft, + (XP_U16)(pool->numFaces * sizeof(pool->lettersLeft[0])) ); +#ifdef DEBUG + stream_putU32( stream, pEND ); +#endif +} /* pool_writeToStream */ + +PoolContext* +pool_makeFromStream( MPFORMAL XWStreamCtxt* stream ) +{ + PoolContext* pool = pool_make( MPPARM_NOCOMMA(mpool) ); + + pool->numTilesLeft = stream_getU16( stream ); + pool->numFaces = stream_getU16( stream ); + pool->lettersLeft = (XP_U8*) + XP_MALLOC( mpool, pool->numFaces * sizeof(pool->lettersLeft[0]) ); + stream_getBytes( stream, pool->lettersLeft, + (XP_U16)(pool->numFaces * sizeof(pool->lettersLeft[0])) ); + + XP_ASSERT( stream_getU32( stream ) == pEND ); + + return pool; +} /* pool_makeFromStream */ + +void +pool_destroy( PoolContext* pool ) +{ + XP_ASSERT( pool != NULL ); + XP_FREE( pool->mpool, pool->lettersLeft ); + XP_FREE( pool->mpool, pool ); +} /* pool_destroy */ + +static Tile +getNthPoolTile( PoolContext* pool, short index ) +{ + Tile result; + + /* given an array of counts of remaining letters, subtract each in turn + from the total we seek until that total is at or below zero. The count + that put it (or would put it) under 0 is the one to pick. */ + + if ( 0 ) { +#ifdef BLANKS_FIRST + } else if ( pool->blankIndex >= 0 && pool->lettersLeft[pool->blankIndex] > 0 ) { + result = pool->blankIndex; +#endif + } else { + XP_S16 nextCount = index; + Tile curLetter = 0; + for ( ; ; ) { + nextCount -= pool->lettersLeft[(short)curLetter]; + if ( nextCount < 0 ) { + XP_ASSERT( pool->lettersLeft[(short)curLetter] > 0 ); + result = curLetter; + break; + } else { + ++curLetter; + } + } + } + return result; +} /* getNthPoolTile */ + +static Tile +getRandomTile( PoolContext* pool ) +{ + /* There's a good little article on shuffling algorithms here: + * http://en.wikipedia.org/wiki/Shuffle#Shuffling_algorithms This puppy + * can definitely be improved. PENDING. But note that what's here still + * works when tiles are re-inserted in the pool. Will need to reshuffle + * in that case if move to shuffling once and just taking tiles off the + * top thereafter. + */ + + XP_U16 r = (XP_U16)XP_RANDOM(); + XP_U16 index = (XP_U16)(r % pool->numTilesLeft); + Tile result = getNthPoolTile( pool, index ); + + --pool->lettersLeft[result]; + --pool->numTilesLeft; + return result; +} /* getRandomTile */ + +void +pool_requestTiles( PoolContext* pool, Tile* tiles, XP_U8* maxNum ) +{ + XP_S16 numWanted = *maxNum; + XP_U16 numWritten = 0; + + XP_ASSERT( numWanted >= 0 ); + + while ( pool->numTilesLeft > 0 && numWanted-- ) { + Tile t = getRandomTile( pool ); + *tiles++ = t; + ++numWritten; + } + *maxNum = (XP_U8)numWritten; +} /* pool_requestTiles */ + +void +pool_replaceTiles( PoolContext* pool, TrayTileSet* tiles ) +{ + XP_U16 nTiles = tiles->nTiles; + Tile* tilesP = tiles->tiles; + + while ( nTiles-- ) { + Tile tile = *tilesP++; /* do I need to filter off high bits? */ + + XP_ASSERT( nTiles < MAX_TRAY_TILES ); + XP_ASSERT( tile < pool->numFaces ); + + ++pool->lettersLeft[tile]; + ++pool->numTilesLeft; + } +} /* pool_replaceTiles */ + +void +pool_removeTiles( PoolContext* pool, TrayTileSet* tiles ) +{ + XP_U16 nTiles = tiles->nTiles; + Tile* tilesP = tiles->tiles; + + XP_ASSERT( nTiles <= MAX_TRAY_TILES ); + + while ( nTiles-- ) { + Tile tile = *tilesP++; /* do I need to filter off high bits? */ + + XP_ASSERT( tile < pool->numFaces ); + XP_ASSERT( pool->lettersLeft[tile] > 0 ); + XP_ASSERT( pool->numTilesLeft > 0 ); + + --pool->lettersLeft[tile]; + --pool->numTilesLeft; + } +} /* pool_removeTiles */ + +XP_U16 +pool_getNTilesLeft( PoolContext* pool ) +{ + return pool->numTilesLeft; +} /* pool_remainingTileCount */ + +XP_U16 +pool_getNTilesLeftFor( PoolContext* pool, Tile tile ) +{ + return pool->lettersLeft[tile]; +} /* pool_remainingTileCount */ + +void +pool_initFromDict( PoolContext* pool, DictionaryCtxt* dict ) +{ + XP_U16 numFaces = dict_numTileFaces( dict ); + Tile i; + + if ( pool->lettersLeft != NULL ) { + XP_FREE( pool->mpool, pool->lettersLeft ); + } + + pool->lettersLeft + = (XP_U8*)XP_MALLOC( pool->mpool, + numFaces * sizeof(pool->lettersLeft[0]) ); + pool->numTilesLeft = 0; + + for ( i = 0; i < numFaces; ++i ) { + XP_U16 numTiles = dict_numTiles( dict, i ); + pool->lettersLeft[i] = (XP_U8)numTiles; + pool->numTilesLeft += numTiles; + } + + pool->numFaces = numFaces; + +#ifdef BLANKS_FIRST + if ( dict_hasBlankTile( dict ) ) { + pool->blankIndex = dict_getBlankTile(dict); + } else { + pool->blankIndex = -1; + } +#endif +} /* pool_initFromDict */ + diff --git a/xwords4/common/pool.h b/xwords4/common/pool.h new file mode 100644 index 000000000..c55387250 --- /dev/null +++ b/xwords4/common/pool.h @@ -0,0 +1,43 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000-2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _POOL_H_ +#define _POOL_H_ + +#include "comtypes.h" +#include "mempool.h" +#include "model.h" + +void pool_requestTiles( PoolContext* pool, Tile* tiles, + /*in out*/ XP_U8* maxNum ); +void pool_replaceTiles( PoolContext* pool, TrayTileSet* tiles ); +void pool_removeTiles( PoolContext* pool, TrayTileSet* tiles ); + +XP_U16 pool_getNTilesLeft( PoolContext* pool ); +XP_U16 pool_getNTilesLeftFor( PoolContext* pool, Tile tile ); + +PoolContext* pool_make( MPFORMAL_NOCOMMA ); + +void pool_destroy( PoolContext* pool ); +void pool_initFromDict( PoolContext* pool, DictionaryCtxt* dict ); + +void pool_writeToStream( PoolContext* pool, XWStreamCtxt* stream ); +PoolContext* pool_makeFromStream( MPFORMAL XWStreamCtxt* stream ); + +#endif diff --git a/xwords4/common/rules.mk b/xwords4/common/rules.mk new file mode 100644 index 000000000..81c925bca --- /dev/null +++ b/xwords4/common/rules.mk @@ -0,0 +1,31 @@ +# -*- mode: makefile -*- +# Copyright 2002 by Eric House +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +all: $(TARGET) + +# Rule for (xplatform) objfiles in this directory +$(COMMONOBJDIR)/%.o: ../common/%.c + mkdir -p $(COMMONOBJDIR) + $(CC) -c $(CFLAGS) $(INCLUDES) $(DEFINES) -DPLATFORM=$(PLATFORM) \ + $< -o $@ + +# Rule for single-platform objfiles in directory of including Makefile +$(PLATFORM)/%.o: %.c + mkdir -p $(PLATFORM) + $(CC) -c -dD $(CFLAGS) $(INCLUDES) $(DEFINES) -DPLATFORM=$(PLATFORM) $< -o $@ + diff --git a/xwords4/common/scorebdp.c b/xwords4/common/scorebdp.c new file mode 100644 index 000000000..df1c1af3f --- /dev/null +++ b/xwords4/common/scorebdp.c @@ -0,0 +1,369 @@ +/* -*-mode: C; fill-column: 78; compile-command: "cd ../linux && make MEMDEBUG=TRUE"; -*- */ +/* + * Copyright 1997 - 2007 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "scorebdp.h" +#include "boardp.h" +#include "model.h" +#include "game.h" +#include "strutils.h" +#include "dbgutil.h" + +#ifdef CPLUS +extern "C" { +#endif + +static XP_Bool +board_ScoreCallback( void* closure, XP_S16 player, XP_UCHAR* expl, + XP_U16* explLen) +{ + ModelCtxt* model = (ModelCtxt*)closure; + return model_getPlayersLastScore( model, player, + expl, explLen ); +} /* board_ScoreCallback */ + +typedef struct DrawScoreData { + DrawScoreInfo dsi; + XP_U16 height; + XP_U16 width; +} DrawScoreData; + +void +drawScoreBoard( BoardCtxt* board ) +{ + if ( board->scoreBoardInvalid ) { + short i; + + XP_U16 nPlayers = board->gi->nPlayers; + + if ( nPlayers > 0 ) { + ModelCtxt* model = board->model; + XP_S16 curTurn = server_getCurrentTurn( board->server ); + XP_U16 selPlayer = board->selPlayer; + XP_S16 nTilesInPool = server_countTilesInPool( board->server ); + XP_Rect scoreRect = board->scoreBdBounds; + XP_S16* adjustDim; + XP_S16* adjustPt; + XP_U16 totalDim, extra, nShares, remWidth, remHeight, remDim; + DrawScoreData* dp; + DrawScoreData datum[MAX_NUM_PLAYERS]; + XP_S16 scores[MAX_NUM_PLAYERS]; + XP_Bool isVertical = !board->scoreSplitHor; +#ifdef KEYBOARD_NAV + XP_Rect cursorRect; + XP_Rect* cursorRectP = NULL; + XP_Bool focusAll = XP_FALSE; + XP_Bool remFocussed = XP_FALSE; + XP_S16 cursorIndex = -1; + + if ( (board->focussed == OBJ_SCORE) && !board->hideFocus ) { + focusAll = !board->focusHasDived; + if ( !focusAll ) { + cursorIndex = board->scoreCursorLoc; + remFocussed = CURSOR_LOC_REM == cursorIndex; + --cursorIndex; + } + } +#endif + draw_scoreBegin( board->draw, &board->scoreBdBounds, nPlayers, + dfsFor( board, OBJ_SCORE ) ); + + /* Let platform decide whether the rem: string should be given any + space once there are no tiles left. On Palm that space is + clickable to drop a menu, so will probably leave it. */ + draw_measureRemText( board->draw, &board->scoreBdBounds, + nTilesInPool, &remWidth, &remHeight ); + XP_ASSERT( remWidth <= board->scoreBdBounds.width ); + XP_ASSERT( remHeight <= board->scoreBdBounds.height ); + remDim = isVertical? remHeight : remWidth; + + if ( isVertical ) { + adjustPt = &scoreRect.top; + adjustDim = &scoreRect.height; + } else { + adjustPt = &scoreRect.left; + adjustDim = &scoreRect.width; + } + + /* Get the scores from the model or by calculating them based on + the end-of-game state. */ + if ( board->gameOver ) { + model_figureFinalScores( model, scores, (XP_S16*)NULL ); + } else { + for ( i = 0; i < nPlayers; ++i ) { + scores[i] = model_getPlayerScore( model, i ); + } + } + + totalDim = remDim; + + /* figure spacing for each scoreboard entry */ + XP_MEMSET( &datum, 0, sizeof(datum) ); + for ( dp = datum, i = 0; i < nPlayers; ++i, ++dp ) { + LocalPlayer* lp = &board->gi->players[i]; + + /* This is a hack! */ + dp->dsi.lsc = board_ScoreCallback; + dp->dsi.lscClosure = model; +#ifdef KEYBOARD_NAV + if ( (i == cursorIndex) || focusAll ) { + dp->dsi.flags |= CELL_ISCURSOR; + } +#endif + dp->dsi.playerNum = i; + dp->dsi.totalScore = scores[i]; + dp->dsi.isTurn = (i == curTurn); + dp->dsi.name = emptyStringIfNull(lp->name); + dp->dsi.selected = board->trayVisState != TRAY_HIDDEN + && i==selPlayer; + dp->dsi.isRobot = lp->isRobot; + dp->dsi.isRemote = !lp->isLocal; + dp->dsi.nTilesLeft = (nTilesInPool > 0)? -1: + model_getNumTilesTotal( model, i ); + draw_measureScoreText( board->draw, &scoreRect, + &dp->dsi, &dp->width, &dp->height ); + XP_ASSERT( dp->width <= scoreRect.width ); + XP_ASSERT( dp->height <= scoreRect.height ); + totalDim += isVertical ? dp->height : dp->width; + } + + /* break extra space into chunks, one to follow REM and another to + preceed the timer, and then one for each player. Generally the + player's score will be centered in the rect it's given, so in + effect we're putting half the chunk on either side. The goal + here is for the scores to be closer to each other than they are + to the rem: string and timer on the ends. */ + nShares = nPlayers; + XP_ASSERT( *adjustDim >= totalDim ); + extra = (*adjustDim - totalDim) / nShares; + + /* at this point, the scoreRect should be anchored at the + scoreboard rect's upper left. */ + + if ( remDim > 0 ) { + *adjustDim = remDim; + + draw_drawRemText( board->draw, &scoreRect, &scoreRect, + nTilesInPool, focusAll || remFocussed ); + board->remRect = scoreRect; + *adjustPt += remDim; +#ifdef KEYBOARD_NAV + /* Hack: don't let the cursor disappear if Rem: goes away */ + } else if ( board->scoreCursorLoc == CURSOR_LOC_REM ) { + board->scoreCursorLoc = selPlayer + 1; +#endif + } + + board->remDim = remDim; + + for ( dp = datum, i = 0; i < nPlayers; ++dp, ++i ) { + XP_Rect innerRect; + XP_U16 dim = isVertical? dp->height:dp->width; + *adjustDim = board->pti[i].scoreDims = dim + extra; + + innerRect.width = dp->width; + innerRect.height = dp->height; + innerRect.left = scoreRect.left + + ((scoreRect.width - innerRect.width) / 2); + innerRect.top = scoreRect.top + + ((scoreRect.height - innerRect.height) / 2); + + draw_score_drawPlayer( board->draw, &innerRect, &scoreRect, + &dp->dsi ); +#ifdef KEYBOARD_NAV + XP_MEMCPY( &board->pti[i].scoreRects, &scoreRect, + sizeof(scoreRect) ); + if ( i == cursorIndex ) { + cursorRect = scoreRect; + cursorRectP = &cursorRect; + } +#endif + *adjustPt += *adjustDim; + } + + draw_objFinished( board->draw, OBJ_SCORE, &board->scoreBdBounds, + dfsFor( board, OBJ_SCORE ) ); + } + + board->scoreBoardInvalid = XP_FALSE; + } + + drawTimer( board ); +} /* drawScoreBoard */ + +static XP_S16 +figureSecondsLeft( BoardCtxt* board ) +{ + CurGameInfo* gi = board->gi; + XP_U16 secondsUsed = gi->players[board->selPlayer].secondsUsed; + XP_U16 secondsAvailable = gi->gameSeconds / gi->nPlayers; + XP_ASSERT( gi->timerEnabled ); + return secondsAvailable - secondsUsed; +} /* figureSecondsLeft */ + +void +drawTimer( BoardCtxt* board ) +{ + if ( board->gi->timerEnabled ) { + XP_S16 secondsLeft = figureSecondsLeft( board ); + + draw_drawTimer( board->draw, &board->timerBounds, &board->timerBounds, + board->selPlayer, secondsLeft ); + } +} /* drawTimer */ + +void +board_setScoreboardLoc( BoardCtxt* board, XP_U16 scoreLeft, XP_U16 scoreTop, + XP_U16 scoreWidth, XP_U16 scoreHeight, + XP_Bool divideHorizontally ) +{ + board->scoreBdBounds.left = scoreLeft; + board->scoreBdBounds.top = scoreTop; + board->scoreBdBounds.width = scoreWidth; + board->scoreBdBounds.height = scoreHeight; + board->scoreSplitHor = divideHorizontally; +} /* board_setScoreboardLoc */ + +XP_S16 +figureScoreRectTapped( const BoardCtxt* board, XP_U16 xx, XP_U16 yy ) +{ + XP_S16 result = -1; + XP_S16 left; + XP_U16 nPlayers = board->gi->nPlayers; + + if ( board->scoreSplitHor ) { + left = xx - board->scoreBdBounds.left; + } else { + left = yy - board->scoreBdBounds.top; + } + + left -= board->remDim; + if ( left < 0 ) { + result = CURSOR_LOC_REM; + } else { + for ( result = 0; result < nPlayers; ) { + left -= board->pti[result].scoreDims; + ++result; /* increment before test to skip REM */ + if ( left < 0 ) { + break; /* found it! */ + } + } + if ( result > nPlayers ) { + result = -1; + } + } + return result; +} /* figureScoreRectTapped */ + +/* If the pen also went down on the scoreboard, make the selected player the + * one closest to the mouse up loc. + */ +#if defined POINTER_SUPPORT || defined KEYBOARD_NAV +XP_Bool +handlePenUpScore( BoardCtxt* board, XP_U16 xx, XP_U16 yy ) +{ + XP_Bool result = XP_TRUE; + + XP_S16 rectNum = figureScoreRectTapped( board, xx, yy ); + + if ( rectNum == CURSOR_LOC_REM ) { + util_remSelected( board->util ); + } else if ( --rectNum >= 0 ) { + board_selectPlayer( board, rectNum ); + } else { + result = XP_FALSE; + } + return result; +} /* handlePenUpScore */ +#endif + +#ifdef KEYBOARD_NAV +static XP_Key +flipKey( XP_Key key, XP_Bool flip ) +{ + XP_Key result = key; + if ( flip ) { + switch( key ) { + case XP_CURSOR_KEY_DOWN: + result = XP_CURSOR_KEY_RIGHT; break; + case XP_CURSOR_KEY_ALTDOWN: + result = XP_CURSOR_KEY_ALTRIGHT; break; + case XP_CURSOR_KEY_UP: + result = XP_CURSOR_KEY_LEFT; break; + case XP_CURSOR_KEY_ALTUP: + result = XP_CURSOR_KEY_ALTLEFT; break; + case XP_CURSOR_KEY_LEFT: + result = XP_CURSOR_KEY_UP; break; + case XP_CURSOR_KEY_ALTLEFT: + result = XP_CURSOR_KEY_ALTUP; break; + case XP_CURSOR_KEY_RIGHT: + result = XP_CURSOR_KEY_DOWN; break; + case XP_CURSOR_KEY_ALTRIGHT: + result = XP_CURSOR_KEY_ALTDOWN; break; + default: + /* not en error -- but we don't modify the key */ + break; + } + } + return result; +} /* flipKey */ + +XP_Bool +moveScoreCursor( BoardCtxt* board, XP_Key key, XP_Bool preflightOnly, + XP_Bool* pUp ) +{ + XP_Bool result = XP_TRUE; + XP_S16 scoreCursorLoc = board->scoreCursorLoc; + XP_U16 top = board->gi->nPlayers; + /* Don't let cursor be 0 if rem square's not shown */ + XP_U16 bottom = (board->remDim > 0) ? 0 : 1; + XP_Bool up = XP_FALSE; + + /* Depending on scoreboard layout, keys move cursor or leave. */ + key = flipKey( key, board->scoreSplitHor ); + + switch ( key ) { + case XP_CURSOR_KEY_RIGHT: + case XP_CURSOR_KEY_LEFT: + up = XP_TRUE; + break; + case XP_CURSOR_KEY_DOWN: + ++scoreCursorLoc; + break; + case XP_CURSOR_KEY_UP: + --scoreCursorLoc; + break; + default: + result = XP_FALSE; + } + if ( !up && ((scoreCursorLoc < bottom) || (scoreCursorLoc > top)) ) { + up = XP_TRUE; + } else if ( !preflightOnly ) { + board->scoreCursorLoc = scoreCursorLoc; + board->scoreBoardInvalid = result; + } + + *pUp = up; + + return result; +} /* moveScoreCursor */ +#endif /* KEYBOARD_NAV */ + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/scorebdp.h b/xwords4/common/scorebdp.h new file mode 100644 index 000000000..2cdf5354b --- /dev/null +++ b/xwords4/common/scorebdp.h @@ -0,0 +1,38 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2007 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _SCOREBDP_H_ +#define _SCOREBDP_H_ + +#include "boardp.h" + +void drawScoreBoard( BoardCtxt* board ); +XP_S16 figureScoreRectTapped( const BoardCtxt* board, XP_U16 x, XP_U16 y ); +void drawTimer( BoardCtxt* board ); + +#if defined POINTER_SUPPORT || defined KEYBOARD_NAV +XP_Bool handlePenUpScore( BoardCtxt* board, XP_U16 x, XP_U16 y ); +#endif + +#ifdef KEYBOARD_NAV +XP_Bool moveScoreCursor( BoardCtxt* board, XP_Key key, XP_Bool preflightOnly, + XP_Bool* up ); +#endif + +#endif diff --git a/xwords4/common/server.c b/xwords4/common/server.c new file mode 100644 index 000000000..63cd58d43 --- /dev/null +++ b/xwords4/common/server.c @@ -0,0 +1,2557 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2002 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* #include */ + +#include "comtypes.h" +#include "server.h" +#include "util.h" +#include "model.h" +#include "comms.h" +#include "memstream.h" +#include "game.h" +/* #include "board.h" */ +#include "states.h" +#include "xwproto.h" +#include "util.h" +#include "pool.h" +#include "engine.h" +#include "strutils.h" + +#include "LocalizedStrIncludes.h" + +#ifdef CPLUS +extern "C" { +#endif + +#define sEND 0x73454e44 + +#define LOCAL_ADDR NULL + +#define IS_ROBOT(p) ((p)->isRobot) +#define IS_LOCAL(p) ((p)->isLocal) + +enum { + END_REASON_USER_REQUEST, + END_REASON_OUT_OF_TILES, + END_REASON_TOO_MANY_PASSES +}; +typedef XP_U8 GameEndReason; + +typedef struct ServerPlayer { + EngineCtxt* engine; /* each needs his own so don't interfere each other */ + XP_S8 deviceIndex; /* 0 means local, -1 means unknown */ +} ServerPlayer; + +#define UNKNOWN_DEVICE -1 +#define SERVER_DEVICE 0 + +typedef struct RemoteAddress { + XP_PlayerAddr channelNo; +} RemoteAddress; + +/* These are the parts of the server's state that needs to be preserved + across a reset/new game */ +typedef struct ServerVolatiles { + ModelCtxt* model; + CommsCtxt* comms; + XW_UtilCtxt* util; + CurGameInfo* gi; + TurnChangeListener turnChangeListener; + void* turnChangeData; + GameOverListener gameOverListener; + void* gameOverData; + XWStreamCtxt* prevMoveStream; /* save it to print later */ + XW_State stateAfterShow; /* do I need to serialize this? What if + someone quits before I can show the + scores? PENDING(ehouse) */ + XP_Bool showPrevMove; +} ServerVolatiles; + +typedef struct ServerNonvolatiles { + XP_U8 nDevices; + XW_State gameState; + XP_S8 currentTurn; /* invalid when game is over */ + XP_U8 pendingRegistrations; + XP_Bool showRobotScores; + + RemoteAddress addresses[MAX_NUM_PLAYERS]; +} ServerNonvolatiles; + +struct ServerCtxt { + ServerVolatiles vol; + ServerNonvolatiles nv; + + PoolContext* pool; + + BadWordInfo illegalWordInfo; +#ifndef XWFEATURE_STANDALONE_ONLY + XP_U16 lastMoveSource; +#endif + + ServerPlayer players[MAX_NUM_PLAYERS]; + XP_Bool serverDoing; + MPSLOT +}; + + +#define NPASSES_OK(s) model_recentPassCountOk((s)->vol.model) + +/******************************* prototypes *******************************/ +static void assignTilesToAll( ServerCtxt* server ); +static void resetEngines( ServerCtxt* server ); +static void nextTurn( ServerCtxt* server, XP_S16 nxtTurn ); + +static void doEndGame( ServerCtxt* server ); +static void endGameInternal( ServerCtxt* server, GameEndReason why ); +static void badWordMoveUndoAndTellUser( ServerCtxt* server, + BadWordInfo* bwi ); +static XP_Bool tileCountsOk( ServerCtxt* server ); +static void setTurn( ServerCtxt* server, XP_S16 turn ); + +#ifndef XWFEATURE_STANDALONE_ONLY +static XP_Bool handleRegistrationMsg( ServerCtxt* server, + XWStreamCtxt* stream ); +static void registerRemotePlayer( ServerCtxt* server, XWStreamCtxt* stream ); +static void server_sendInitialMessage( ServerCtxt* server ); +static void sendBadWordMsgs( ServerCtxt* server ); +static XP_Bool handleIllegalWord( ServerCtxt* server, + XWStreamCtxt* incoming ); +static void tellMoveWasLegal( ServerCtxt* server ); +#endif + +#define PICK_NEXT -1 + +#if defined DEBUG && ! defined XWFEATURE_STANDALONE_ONLY +static char* +getStateStr( XW_State st ) +{ +# define CASESTR(c) case c: return #c + switch( st ) { + CASESTR(XWSTATE_NONE); + CASESTR(XWSTATE_BEGIN); + CASESTR(XWSTATE_NEED_SHOWSCORE); + CASESTR(XWSTATE_WAITING_ALL_REG); + CASESTR(XWSTATE_RECEIVED_ALL_REG); + CASESTR(XWSTATE_NEEDSEND_BADWORD_INFO); + CASESTR(XWSTATE_MOVE_CONFIRM_WAIT); + CASESTR(XWSTATE_MOVE_CONFIRM_MUSTSEND); + CASESTR(XWSTATE_NEEDSEND_ENDGAME); + CASESTR(XWSTATE_INTURN); + CASESTR(XWSTATE_GAMEOVER); + default: + return "unknown"; + } +# undef CASESTR +} +#endif + +#if 0 +static void +logNewState( XW_State old, XW_State newst ) +{ + if ( old != newst ) { + char* oldStr = getStateStr(old); + char* newStr = getStateStr(newst); + XP_LOGF( "state transition %s => %s", oldStr, newStr ); + } +} +# define SETSTATE( s, st ) { XW_State old = (s)->nv.gameState; \ + (s)->nv.gameState = (st); \ + logNewState(old, st); } +#else +# define SETSTATE( s, st ) (s)->nv.gameState = (st) +#endif + +/***************************************************************************** + * + ****************************************************************************/ +static void +initServer( ServerCtxt* server ) +{ + setTurn( server, -1 ); /* game isn't under way yet */ + + if ( 0 ) { +#ifndef XWFEATURE_STANDALONE_ONLY + } else if ( server->vol.gi->serverRole == SERVER_ISCLIENT ) { + SETSTATE( server, XWSTATE_NONE ); +#endif + } else { + SETSTATE( server, XWSTATE_BEGIN ); + } + +#ifndef XWFEATURE_STANDALONE_ONLY + { + XP_U16 ii; + CurGameInfo* gi = server->vol.gi; + LocalPlayer* lp = gi->players; + ServerPlayer* player = server->players; + for ( ii = 0; ii < gi->nPlayers; ++ii, ++lp, ++player ) { + if ( !lp->isLocal/* && !lp->name */ ) { + ++server->nv.pendingRegistrations; + } + player->deviceIndex = lp->isLocal? SERVER_DEVICE : UNKNOWN_DEVICE; + } + } +#endif + + server->nv.nDevices = 1; /* local device (0) is always there */ +} /* initServer */ + +ServerCtxt* +server_make( MPFORMAL ModelCtxt* model, CommsCtxt* comms, XW_UtilCtxt* util ) +{ + ServerCtxt* result = (ServerCtxt*)XP_MALLOC( mpool, sizeof(*result) ); + + if ( result != NULL ) { + XP_MEMSET( result, 0, sizeof(*result) ); + + MPASSIGN(result->mpool, mpool); + + result->vol.model = model; + result->vol.comms = comms; + result->vol.util = util; + result->vol.gi = util->gameInfo; + + initServer( result ); + } + return result; +} /* server_make */ + +static void +getNV( XWStreamCtxt* stream, ServerNonvolatiles* nv, XP_U16 nPlayers ) +{ + XP_U16 i; + + /* This should go away when stream format changes */ + (void)stream_getBits( stream, 3 ); /* was npassesinrow */ + + nv->nDevices = (XP_U8)stream_getBits( stream, NDEVICES_NBITS ); + if ( stream_getVersion( stream ) > STREAM_VERS_41B4 ) { + ++nv->nDevices; + } + + XP_ASSERT( XWSTATE_GAMEOVER < 1<<4 ); + nv->gameState = (XW_State)stream_getBits( stream, 4 ); + + nv->currentTurn = (XP_S8)stream_getBits( stream, NPLAYERS_NBITS ) - 1; + nv->pendingRegistrations = (XP_U8)stream_getBits( stream, NPLAYERS_NBITS ); + + for ( i = 0; i < nPlayers; ++i ) { + nv->addresses[i].channelNo = (XP_PlayerAddr)stream_getBits( stream, + 16 ); + } +} /* getNV */ + +static void +putNV( XWStreamCtxt* stream, ServerNonvolatiles* nv, XP_U16 nPlayers ) +{ + XP_U16 i; + + stream_putBits( stream, 3, 0 ); /* was nPassesInRow */ + /* number of players is upper limit on device count */ + stream_putBits( stream, NDEVICES_NBITS, nv->nDevices-1 ); + + XP_ASSERT( XWSTATE_GAMEOVER < 1<<4 ); + stream_putBits( stream, 4, nv->gameState ); + + /* +1: make -1 (NOTURN) into a positive number */ + stream_putBits( stream, NPLAYERS_NBITS, nv->currentTurn+1 ); + stream_putBits( stream, NPLAYERS_NBITS, nv->pendingRegistrations ); + + for ( i = 0; i < nPlayers; ++i ) { + stream_putBits( stream, 16, nv->addresses[i].channelNo ); + } +} /* putNV */ + +ServerCtxt* +server_makeFromStream( MPFORMAL XWStreamCtxt* stream, ModelCtxt* model, + CommsCtxt* comms, XW_UtilCtxt* util, XP_U16 nPlayers ) +{ + ServerCtxt* server; + short i; + CurGameInfo* gi = util->gameInfo; + + server = server_make( MPPARM(mpool) model, comms, util ); + getNV( stream, &server->nv, nPlayers ); + + if ( stream_getBits(stream, 1) != 0 ) { + server->pool = pool_makeFromStream( MPPARM(mpool) stream ); + } + + for ( i = 0; i < nPlayers; ++i ) { + ServerPlayer* player = &server->players[i]; + + player->deviceIndex = stream_getU8( stream ); + + if ( stream_getU8( stream ) != 0 ) { + LocalPlayer* lp = &gi->players[i]; + player->engine = engine_makeFromStream( MPPARM(mpool) + stream, util, + lp->isRobot ); + } + } + +#ifndef XWFEATURE_STANDALONE_ONLY + server->lastMoveSource = (XP_U16)stream_getBits( stream, 2 ); +#endif + + XP_ASSERT( stream_getU32( stream ) == sEND ); + + return server; +} /* server_makeFromStream */ + +void +server_writeToStream( ServerCtxt* server, XWStreamCtxt* stream ) +{ + XP_U16 i; + XP_U16 nPlayers = server->vol.gi->nPlayers; + + putNV( stream, &server->nv, nPlayers ); + + stream_putBits( stream, 1, server->pool != NULL ); + if ( server->pool != NULL ) { + pool_writeToStream( server->pool, stream ); + } + + for ( i = 0; i < nPlayers; ++i ) { + ServerPlayer* player = &server->players[i]; + + stream_putU8( stream, player->deviceIndex ); + + stream_putU8( stream, (XP_U8)(player->engine != NULL) ); + if ( player->engine != NULL ) { + engine_writeToStream( player->engine, stream ); + } + } + +#ifndef XWFEATURE_STANDALONE_ONLY + stream_putBits( stream, 2, server->lastMoveSource ); +#endif + +#ifdef DEBUG + stream_putU32( stream, sEND ); +#endif + +} /* server_writeToStream */ + +static void +cleanupServer( ServerCtxt* server ) +{ + XP_U16 i; + for ( i = 0; i < VSIZE(server->players); ++i ){ + ServerPlayer* player = &server->players[i]; + if ( player->engine != NULL ) { + engine_destroy( player->engine ); + } + } + XP_MEMSET( server->players, 0, sizeof(server->players) ); + + if ( server->pool != NULL ) { + pool_destroy( server->pool ); + server->pool = (PoolContext*)NULL; + } + + XP_MEMSET( &server->nv, 0, sizeof(server->nv) ); + + if ( !!server->vol.prevMoveStream ) { + stream_destroy( server->vol.prevMoveStream ); + server->vol.prevMoveStream = NULL; + } +} /* cleanupServer */ + +void +server_reset( ServerCtxt* server, CommsCtxt* comms ) +{ + ServerVolatiles vol = server->vol; + + cleanupServer( server ); + + vol.comms = comms; + server->vol = vol; + + initServer( server ); +} /* server_reset */ + +void +server_destroy( ServerCtxt* server ) +{ + cleanupServer( server ); + + XP_FREE( server->mpool, server ); +} /* server_destroy */ + +void +server_prefsChanged( ServerCtxt* server, CommonPrefs* cp ) +{ + server->nv.showRobotScores = cp->showRobotScores; +} /* server_prefsChanged */ + +XP_S16 +server_countTilesInPool( ServerCtxt* server ) +{ + XP_S16 result = -1; + PoolContext* pool = server->pool; + if ( !!pool ) { + result = pool_getNTilesLeft( pool ); + } + return result; +} /* server_countTilesInPool */ + +/* I'm a client device. It's my job to start the whole conversation by + * contacting the server and telling him that I exist (and some other stuff, + * including what the players here want to be called.) + */ +#define NAME_LEN_NBITS 6 +#define MAX_NAME_LEN ((1<<(NAME_LEN_NBITS-1))-1) +#ifndef XWFEATURE_STANDALONE_ONLY +void +server_initClientConnection( ServerCtxt* server, XWStreamCtxt* stream ) +{ + CurGameInfo* gi = server->vol.gi; + XP_U16 nPlayers; + LocalPlayer* lp; +#ifdef DEBUG + XP_U16 i = 0; +#endif + + XP_ASSERT( gi->serverRole == SERVER_ISCLIENT ); + XP_ASSERT( stream != NULL ); + XP_ASSERT( server->nv.gameState == XWSTATE_NONE ); + + stream_open( stream ); + + stream_putBits( stream, XWPROTO_NBITS, XWPROTO_DEVICE_REGISTRATION ); + + nPlayers = gi->nPlayers; + XP_ASSERT( nPlayers > 0 ); + stream_putBits( stream, NPLAYERS_NBITS, nPlayers ); + + for ( lp = gi->players; nPlayers-- > 0; ++lp ) { + XP_UCHAR* name; + XP_U8 len; + + XP_ASSERT( i++ < MAX_NUM_PLAYERS ); + + stream_putBits( stream, 1, lp->isRobot ); /* better not to send this */ + + /* The first nPlayers players are the ones we'll use. The local flag + doesn't matter when for SERVER_ISCLIENT. */ + name = emptyStringIfNull(lp->name); + len = XP_STRLEN(name); + if ( len > MAX_NAME_LEN ) { + len = MAX_NAME_LEN; + } + stream_putBits( stream, NAME_LEN_NBITS, len ); + stream_putBytes( stream, name, len ); + } + + stream_destroy( stream ); +} /* server_initClientConnection */ +#endif + +static void +callTurnChangeListener( ServerCtxt* server ) +{ + if ( server->vol.turnChangeListener != NULL ) { + (*server->vol.turnChangeListener)( server->vol.turnChangeData ); + } +} /* callTurnChangeListener */ + +#ifndef XWFEATURE_STANDALONE_ONLY +static XP_Bool +handleRegistrationMsg( ServerCtxt* server, XWStreamCtxt* stream ) +{ + XP_Bool success = XP_TRUE; + XP_U16 playersInMsg, i; + XP_STATUSF( "handleRegistrationMsg" ); + + /* code will have already been consumed */ + playersInMsg = (XP_U16)stream_getBits( stream, NPLAYERS_NBITS ); + XP_ASSERT( playersInMsg > 0 ); + + if ( server->nv.pendingRegistrations < playersInMsg ) { + util_userError( server->vol.util, ERR_REG_UNEXPECTED_USER ); + success = XP_FALSE; + } else { + for ( i = 0; i < playersInMsg; ++i ) { + registerRemotePlayer( server, stream ); + + /* This is abusing the semantics of turn change -- at least in the + case where there is another device yet to register -- but we + need to let the board know to redraw the scoreboard with more + players there. */ + callTurnChangeListener( server ); + } + + if ( server->nv.pendingRegistrations == 0 ) { + assignTilesToAll( server ); + SETSTATE( server, XWSTATE_RECEIVED_ALL_REG ); + } + } + return success; +} /* handleRegistrationMsg */ +#endif + +/* Just for grins....trade in all the tiles that weren't used in the + * move the robot manage to make. This is not meant to be strategy, but + * rather to force me to make the trade-communication stuff work well. + */ +#if 0 +static void +robotTradeTiles( ServerCtxt* server, MoveInfo* newMove ) +{ + Tile tradeTiles[MAX_TRAY_TILES]; + XP_S16 turn = server->nv.currentTurn; + Tile* curTiles = model_getPlayerTiles( server->model, turn ); + XP_U16 numInTray = model_getNumPlayerTiles( server->model, turn ); + XP_MEMCPY( tradeTiles, curTiles, numInTray ); + + for ( i = 0; i < numInTray; ++i ) { /* for each tile in tray */ + XP_Bool keep = XP_FALSE; + for ( j = 0; j < newMove->numTiles; ++j ) { /* for each in move */ + Tile movedTile = newMove->tiles[j].tile; + if ( newMove->tiles[j].isBlank ) { + movedTile |= TILE_BLANK_BIT; + } + if ( movedTile == curTiles[i] ) { /* it's in the move */ + keep = XP_TRUE; + break; + } + } + if ( !keep ) { + tradeTiles[numToTrade++] = curTiles[i]; + } + } + + +} /* robotTradeTiles */ +#endif + +#define FUDGE_RANGE 10 +#define MINIMUM_SCORE 5 +static XP_U16 +figureTargetScore( ServerCtxt* server, XP_U16 turn ) +{ + XP_S16 result = 1000; + XP_S16 highScore = 0; + ModelCtxt* model = server->vol.model; + XP_U16 nPlayers = server->vol.gi->nPlayers; + XP_U16 i; + + XP_ASSERT( IS_ROBOT(&server->vol.gi->players[turn]) ); + + if ( 1 /* server->nHumanPlayers > 0 */ ) { + result = 0; + + /* find the highest score anybody but the current player has */ + for ( i = 0; i < nPlayers; ++i ) { + if ( i != turn ) { + XP_S16 score = model_getPlayerScore( model, i ); + XP_ASSERT( score >= 0 ); + if ( highScore < score ) { + highScore = score; + } + } + } + + result = (XP_S16)(highScore - model_getPlayerScore( model, turn ) + + (FUDGE_RANGE-(XP_RANDOM() % (FUDGE_RANGE*2)))); + if ( result < 0 ) { + result = MINIMUM_SCORE; + } + } + + return result; +} /* figureTargetScore */ + +static XWStreamCtxt* +mkServerStream( ServerCtxt* server ) +{ + XWStreamCtxt* stream; + stream = mem_stream_make( MPPARM(server->mpool) + util_getVTManager(server->vol.util), + NULL, CHANNEL_NONE, + (MemStreamCloseCallback)NULL ); + XP_ASSERT( !!stream ); + return stream; +} /* mkServerStream */ + +static XP_Bool +makeRobotMove( ServerCtxt* server ) +{ + XP_Bool result = XP_FALSE; + XP_Bool finished; + XP_S16 turn; + const TrayTileSet* tileSet; + MoveInfo newMove; + ModelCtxt* model = server->vol.model; + CurGameInfo* gi = server->vol.gi; + XP_Bool timerEnabled = gi->timerEnabled; + XP_Bool canMove; + XP_U32 time = 0L; /* stupid compiler.... */ + XP_U16 targetScore = NO_SCORE_LIMIT; + XW_UtilCtxt* util = server->vol.util; + + if ( timerEnabled ) { + time = util_getCurSeconds( util ); + } + + turn = server->nv.currentTurn; + XP_ASSERT( turn >= 0 ); + + /* If the player's been recently turned into a robot while he had some + pending tiles on the board we'll have problems. It'd be best to detect + this and put 'em back when that happens. But for now we'll just be + paranoid. PENDING(ehouse) */ + model_resetCurrentTurn( model, turn ); + + tileSet = model_getPlayerTiles( model, turn ); + + if ( gi->robotSmartness == DUMB_ROBOT ) { + targetScore = figureTargetScore( server, turn ); + } + + XP_ASSERT( !!server_getEngineFor( server, turn ) ); + finished = engine_findMove( server_getEngineFor( server, turn ), + model, model_getDictionary( model ), + tileSet->tiles, tileSet->nTiles, +#ifdef XWFEATURE_SEARCHLIMIT + NULL, XP_FALSE, +#endif + targetScore, &canMove, &newMove ); + if ( finished ) { + const XP_UCHAR* str; + XWStreamCtxt* stream = NULL; + + XP_Bool trade = (newMove.nTiles == 0) && canMove && + (server_countTilesInPool( server ) >= MAX_TRAY_TILES); + + server->vol.showPrevMove = XP_TRUE; + if ( server->nv.showRobotScores ) { + stream = mkServerStream( server ); + } + + /* trade if unable to find a move */ + if ( trade ) { + result = server_commitTrade( server, ALLTILES ); + + /* Quick hack to fix gremlin bug where all-robot game seen none + able to trade for tiles to move and blowing the undo stack. + This will stop them, and should have no effect if there are any + human players making real moves. */ + + if ( !!stream ) { + XP_UCHAR buf[64]; + str = util_getUserString(util, STRD_ROBOT_TRADED); + XP_SNPRINTF( buf, sizeof(buf), str, MAX_TRAY_TILES ); + + stream_putString( stream, buf ); + XP_ASSERT( !server->vol.prevMoveStream ); + server->vol.prevMoveStream = stream; + } + } else { + /* if canMove is false, this is a fake move, a pass */ + + if ( canMove || NPASSES_OK(server) ) { + model_makeTurnFromMoveInfo( model, turn, &newMove ); + + if ( !!stream ) { + (void)model_checkMoveLegal( model, turn, stream, NULL ); + XP_ASSERT( !server->vol.prevMoveStream ); + server->vol.prevMoveStream = stream; + } + result = server_commitMove( server ); + } else { + result = XP_FALSE; + } + } + } + + if ( timerEnabled ) { + gi->players[turn].secondsUsed += + (XP_U16)(util_getCurSeconds( util ) - time); + } else { + XP_ASSERT( gi->players[turn].secondsUsed == 0 ); + } + + return result; /* always return TRUE after robot move? */ +} /* makeRobotMove */ + +static XP_Bool +robotMovePending( ServerCtxt* server ) +{ + XP_S16 turn = server->nv.currentTurn; + if ( turn >= 0 && tileCountsOk(server) && NPASSES_OK(server) ) { + CurGameInfo* gi = server->vol.gi; + LocalPlayer* player = &gi->players[turn]; + return IS_ROBOT(player) && IS_LOCAL(player); + } + return XP_FALSE; +} /* robotMovePending */ + +static void +showPrevScore( ServerCtxt* server ) +{ + XW_UtilCtxt* util = server->vol.util; + XWStreamCtxt* stream; + const XP_UCHAR* str; + CurGameInfo* gi = server->vol.gi; + XP_U16 nPlayers = gi->nPlayers; + XP_U16 prevTurn; + XP_U16 strCode; + LocalPlayer* lp; + XP_Bool wasRobot; + XP_Bool wasLocal; + + XP_ASSERT( server->nv.showRobotScores ); + + prevTurn = (server->nv.currentTurn + nPlayers - 1) % nPlayers; + lp = &gi->players[prevTurn]; + wasRobot = lp->isRobot; + wasLocal = lp->isLocal; + + if ( wasLocal ) { + XP_ASSERT( wasRobot ); + strCode = STR_ROBOT_MOVED; + } else { + strCode = STR_REMOTE_MOVED; + } + + stream = mkServerStream( server ); + + str = util_getUserString( util, strCode ); + stream_putString( stream, str ); + + if ( !!server->vol.prevMoveStream ) { + XWStreamCtxt* prevStream = server->vol.prevMoveStream; + XP_U16 len = stream_getSize( prevStream ); + XP_UCHAR* buf = XP_MALLOC( server->mpool, len ); + + stream_getBytes( prevStream, buf, len ); + stream_destroy( prevStream ); + server->vol.prevMoveStream = NULL; + + stream_putBytes( stream, buf, len ); + XP_FREE( server->mpool, buf ); + } + + (void)util_userQuery( util, QUERY_ROBOT_MOVE, stream ); + stream_destroy( stream ); + + SETSTATE( server, server->vol.stateAfterShow ); +} /* showPrevScore */ + +XP_Bool +server_do( ServerCtxt* server ) +{ + XP_Bool result = XP_TRUE; + XP_Bool moreToDo = XP_FALSE; + + if ( server->serverDoing ) { + return XP_FALSE; + } + server->serverDoing = XP_TRUE; + + switch( server->nv.gameState ) { + case XWSTATE_BEGIN: + if ( server->nv.pendingRegistrations == 0 ) { /* all players on device */ + assignTilesToAll( server ); + SETSTATE( server, XWSTATE_INTURN ); + setTurn( server, 0 ); + moreToDo = XP_TRUE; + } + break; + + case XWSTATE_NEEDSEND_BADWORD_INFO: + XP_ASSERT( server->vol.gi->serverRole == SERVER_ISSERVER ); + badWordMoveUndoAndTellUser( server, &server->illegalWordInfo ); +#ifndef XWFEATURE_STANDALONE_ONLY + sendBadWordMsgs( server ); +#endif + nextTurn( server, PICK_NEXT ); /* sets server->nv.gameState */ + //moreToDo = XP_TRUE; /* why? */ + break; + +#ifndef XWFEATURE_STANDALONE_ONLY + case XWSTATE_RECEIVED_ALL_REG: + server_sendInitialMessage( server ); + /* PENDING isn't INTURN_OFFDEVICE possible too? Or just INTURN? */ + SETSTATE( server, XWSTATE_INTURN ); + setTurn( server, 0 ); + moreToDo = XP_TRUE; + break; + + case XWSTATE_MOVE_CONFIRM_MUSTSEND: + XP_ASSERT( server->vol.gi->serverRole == SERVER_ISSERVER ); + tellMoveWasLegal( server ); + nextTurn( server, PICK_NEXT ); + break; + +#endif /* XWFEATURE_STANDALONE_ONLY */ + + case XWSTATE_NEEDSEND_ENDGAME: + endGameInternal( server, END_REASON_OUT_OF_TILES ); + break; + + case XWSTATE_NEED_SHOWSCORE: + showPrevScore( server ); + moreToDo = XP_TRUE; /* either process turn or end game... */ + break; + case XWSTATE_INTURN: + if ( robotMovePending( server ) ) { + result = makeRobotMove( server ); + /* if robot was interrupted, we need to schedule again */ + moreToDo = !result || robotMovePending( server ); + } + break; + + default: + result = XP_FALSE; + break; + } + if ( moreToDo ) { + util_requestTime( server->vol.util ); + } + + server->serverDoing = XP_FALSE; + return result; +} /* server_do */ + +#ifndef XWFEATURE_STANDALONE_ONLY +static XP_S8 +getIndexForDevice( ServerCtxt* server, XP_PlayerAddr channelNo ) +{ + short i; + XP_S8 result = -1; + + for ( i = 0; i < server->nv.nDevices; ++i ) { + RemoteAddress* addr = &server->nv.addresses[i]; + if ( addr->channelNo == channelNo ) { + result = i; + break; + } + } + + return result; +} /* getIndexForDevice */ + +static LocalPlayer* +findFirstPending( ServerCtxt* server, ServerPlayer** playerP ) +{ + LocalPlayer* lp; + CurGameInfo* gi = server->vol.gi; + XP_U16 nPlayers = gi->nPlayers; + XP_U16 nPending = server->nv.pendingRegistrations; + + XP_ASSERT( nPlayers > 0 ); + lp = gi->players + nPlayers; + + while ( --lp >= gi->players ) { + --nPlayers; + if ( !lp->isLocal ) { + if ( --nPending == 0 ) { + break; + } + } + } + XP_ASSERT( lp >= gi->players ); /* did we find a slot? */ + *playerP = server->players + nPlayers; + return lp; +} /* findFirstPending */ + +static void +registerRemotePlayer( ServerCtxt* server, XWStreamCtxt* stream ) +{ + XP_S8 deviceIndex; + XP_PlayerAddr channelNo; + XP_UCHAR* name; + XP_U16 nameLen; + LocalPlayer* lp; + ServerPlayer* player = (ServerPlayer*)NULL; + + /* The player must already be there with a null name, or it's an error. + Take the first empty slot. */ + XP_ASSERT( server->nv.pendingRegistrations > 0 ); + + /* find the slot to use */ + lp = findFirstPending( server, &player ); + + /* get data from stream */ + lp->isRobot = stream_getBits( stream, 1 ); + nameLen = stream_getBits( stream, NAME_LEN_NBITS ); + name = (XP_UCHAR*)XP_MALLOC( server->mpool, nameLen + 1 ); + stream_getBytes( stream, name, nameLen ); + name[nameLen] = '\0'; + + replaceStringIfDifferent( server->mpool, &lp->name, name ); + XP_FREE( server->mpool, name ); + + channelNo = stream_getAddress( stream ); + deviceIndex = getIndexForDevice( server, channelNo ); + + --server->nv.pendingRegistrations; + + if ( deviceIndex == -1 ) { + RemoteAddress* addr; + addr = &server->nv.addresses[server->nv.nDevices]; + + deviceIndex = server->nv.nDevices++; + + XP_ASSERT( channelNo != 0 ); + addr->channelNo = channelNo; + } + + player->deviceIndex = deviceIndex; + +} /* registerRemotePlayer */ + +static void +clearLocalRobots( ServerCtxt* server ) +{ + XP_U16 i; + CurGameInfo* gi = server->vol.gi; + XP_U16 nPlayers = gi->nPlayers; + + for ( i = 0; i < nPlayers; ++i ) { + LocalPlayer* player = &gi->players[i]; + if ( IS_LOCAL( player ) ) { + player->isRobot = XP_FALSE; + } + } +} /* clearLocalRobots */ +#endif + +/* Called in response to message from server listing all the names of + * players in the game (in server-assigned order) and their initial + * tray contents. + */ +#ifndef XWFEATURE_STANDALONE_ONLY +static XP_Bool +client_readInitialMessage( ServerCtxt* server, XWStreamCtxt* stream ) +{ + XP_Bool accepted = 0 == server->nv.addresses[0].channelNo; + + /* We should never get this message a second time, but very rarely we do. + Drop it in that case. */ + XP_ASSERT( accepted ); + if ( accepted ) { + DictionaryCtxt* newDict; + DictionaryCtxt* curDict; + XP_U16 nPlayers, nCols; + XP_PlayerAddr channelNo; + short i; + ModelCtxt* model = server->vol.model; + CurGameInfo* gi = server->vol.gi; + CurGameInfo localGI; + XP_U32 gameID; + PoolContext* pool; + + /* version */ + XP_U8 streamVersion = stream_getU8( stream ); + XP_ASSERT( streamVersion == STREAM_VERS_41B4 ); + if ( streamVersion != STREAM_VERS_41B4 ) { + return XP_FALSE; + } + stream_setVersion( stream, streamVersion ); + + gameID = stream_getU32( stream ); + XP_STATUSF( "read gameID of %lx; calling comms_setConnID", gameID ); + server->vol.gi->gameID = gameID; + comms_setConnID( server->vol.comms, gameID ); + + XP_MEMSET( &localGI, 0, sizeof(localGI) ); + gi_readFromStream( MPPARM(server->mpool) stream, &localGI ); + localGI.serverRole = SERVER_ISCLIENT; + + /* so it's not lost (HACK!). Without this, a client won't have a default + dict name when a new game is started. */ + localGI.dictName = copyString( server->mpool, gi->dictName ); + gi_copy( MPPARM(server->mpool) gi, &localGI ); + + nCols = localGI.boardSize; + + newDict = util_makeEmptyDict( server->vol.util ); + dict_loadFromStream( newDict, stream ); + + channelNo = stream_getAddress( stream ); + XP_ASSERT( channelNo != 0 ); + server->nv.addresses[0].channelNo = channelNo; + + /* PENDING init's a bit harsh for setting the size */ + model_init( model, nCols, nCols ); + + nPlayers = localGI.nPlayers; + XP_STATUSF( "reading in %d players", localGI.nPlayers ); + + gi_disposePlayerInfo( MPPARM(server->mpool) &localGI ); + + gi->nPlayers = nPlayers; + model_setNPlayers( model, nPlayers ); + + curDict = model_getDictionary( model ); + + XP_ASSERT( !!newDict ); + + if ( curDict == NULL ) { + model_setDictionary( model, newDict ); + } else if ( dict_tilesAreSame( newDict, curDict ) ) { + /* keep the dict the local user installed */ + dict_destroy( newDict ); + } else { + dict_destroy( curDict ); + model_setDictionary( model, newDict ); + util_userError( server->vol.util, ERR_SERVER_DICT_WINS ); + clearLocalRobots( server ); + } + + XP_ASSERT( !server->pool ); + pool = server->pool = pool_make( MPPARM_NOCOMMA(server->mpool) ); + pool_initFromDict( server->pool, model_getDictionary(model)); + + /* now read the assigned tiles for each player from the stream, and remove + them from the newly-created local pool. */ + for ( i = 0; i < nPlayers; ++i ) { + TrayTileSet tiles; + + traySetFromStream( stream, &tiles ); + XP_ASSERT( tiles.nTiles <= MAX_TRAY_TILES ); + + XP_STATUSF( "got %d tiles for player %d", tiles.nTiles, i ); + + model_assignPlayerTiles( model, i, &tiles ); + + /* remove what the server's assigned so we won't conflict later. */ + pool_removeTiles( pool, &tiles ); + } + + SETSTATE( server, XWSTATE_INTURN ); + + /* Give board a chance to redraw self with the full compliment of known + players */ + setTurn( server, 0 ); + } else { + XP_LOGF( "wanted 0; got %d", server->nv.addresses[0].channelNo ); + } + return accepted; +} /* client_readInitialMessage */ +#endif + +/* For each remote device, send a message containing the dictionary and the + * names of all the players in the game (including those on the device itself, + * since they might have been changed in the case of conflicts), in the order + * that all must use for the game. Then for each player on the device give + * the starting tray. + */ +#ifndef XWFEATURE_STANDALONE_ONLY + +static void +makeSendableGICopy( ServerCtxt* server, CurGameInfo* giCopy, + XP_U16 deviceIndex ) +{ + XP_U16 nPlayers; + LocalPlayer* clientPl; + XP_U16 i; + XP_MEMCPY( giCopy, server->vol.gi, sizeof(*giCopy) ); + + nPlayers = giCopy->nPlayers; + + for ( clientPl = giCopy->players, i = 0; + i < nPlayers; ++clientPl, ++i ) { + /* adjust isLocal to client's perspective */ + clientPl->isLocal = server->players[i].deviceIndex == deviceIndex; + } + + giCopy->dictName = (XP_UCHAR*)NULL; /* so we don't sent the bytes; Isn't this + a leak? PENDING */ +} /* makeSendableGICopy */ + +static void +server_sendInitialMessage( ServerCtxt* server ) +{ + short i; + XP_U16 deviceIndex; + ModelCtxt* model = server->vol.model; + XP_U16 nPlayers = server->vol.gi->nPlayers; + CurGameInfo localGI; + XP_U32 gameID = server->vol.gi->gameID; + + XP_STATUSF( "server_sendInitialMessage" ); + + for ( deviceIndex = 1; deviceIndex < server->nv.nDevices; + ++deviceIndex ) { + RemoteAddress* addr = &server->nv.addresses[deviceIndex]; + XWStreamCtxt* stream = util_makeStreamFromAddr( server->vol.util, + addr->channelNo ); + XP_ASSERT( !!stream ); + stream_open( stream ); + stream_putBits( stream, XWPROTO_NBITS, XWPROTO_CLIENT_SETUP ); + + /* write version for server's benefit; use old version until format + changes */ + stream_putU8( stream, STREAM_VERS_41B4 ); + + XP_STATUSF( "putting gameID %lx into msg", gameID ); + stream_putU32( stream, gameID ); + + makeSendableGICopy( server, &localGI, deviceIndex ); + gi_writeToStream( stream, &localGI ); + + dict_writeToStream( model_getDictionary(model), stream ); + + /* send tiles currently in tray */ + for ( i = 0; i < nPlayers; ++i ) { + model_trayToStream( model, i, stream ); + } + + stream_destroy( stream ); + } + + /* Set after messages are built so their connID will be 0, but all + non-initial messages will have a non-0 connID. */ + comms_setConnID( server->vol.comms, gameID ); +} /* server_sendInitialMessage */ +#endif + +static void +freeBWI( MPFORMAL BadWordInfo* bwi ) +{ + /* BadWordInfo* bwi = &server->illegalWordInfo; */ + XP_U16 nWords = bwi->nWords; + + while ( nWords-- ) { + XP_FREE( mpool, bwi->words[nWords] ); + bwi->words[nWords] = (XP_UCHAR*)NULL; + } + + bwi->nWords = 0; +} /* freeBWI */ + +#ifndef XWFEATURE_STANDALONE_ONLY +static void +bwiToStream( XWStreamCtxt* stream, BadWordInfo* bwi ) +{ + XP_U16 nWords = bwi->nWords; + XP_UCHAR** sp; + + stream_putBits( stream, 4, nWords ); + + for ( sp = bwi->words; nWords > 0; --nWords, ++sp ) { + stringToStream( stream, *sp ); + } + +} /* bwiToStream */ + +static void +bwiFromStream( MPFORMAL XWStreamCtxt* stream, BadWordInfo* bwi ) +{ + XP_U16 nWords = stream_getBits( stream, 4 ); + XP_UCHAR** sp = bwi->words; + + bwi->nWords = nWords; + for ( sp = bwi->words; nWords; ++sp, --nWords ) { + *sp = stringFromStream( mpool, stream ); + } +} /* bwiFromStream */ + +#ifdef DEBUG +#define caseStr(var, s) case s: var = #s; break; +static void +printCode(char* intro, XW_Proto code) +{ + char* str = (char*)NULL; + + switch( code ) { + caseStr( str, XWPROTO_ERROR ); + caseStr( str, XWPROTO_CHAT ); + caseStr( str, XWPROTO_DEVICE_REGISTRATION ); + caseStr( str, XWPROTO_CLIENT_SETUP ); + caseStr( str, XWPROTO_MOVEMADE_INFO_CLIENT ); + caseStr( str, XWPROTO_MOVEMADE_INFO_SERVER ); + caseStr( str, XWPROTO_UNDO_INFO_CLIENT ); + caseStr( str, XWPROTO_UNDO_INFO_SERVER ); + caseStr( str, XWPROTO_BADWORD_INFO ); + caseStr( str, XWPROTO_MOVE_CONFIRM ); + caseStr( str, XWPROTO_CLIENT_REQ_END_GAME ); + caseStr( str, XWPROTO_END_GAME ); + } + + XP_STATUSF( "\t%s for %s", intro, str ); +} /* printCode */ +#undef caseStr +#else +#define printCode(intro, code) +#endif + +static XWStreamCtxt* +messageStreamWithHeader( ServerCtxt* server, XP_U16 devIndex, XW_Proto code ) +{ + XWStreamCtxt* stream; + XP_PlayerAddr channelNo = server->nv.addresses[devIndex].channelNo; + + printCode("making", code); + + stream = util_makeStreamFromAddr( server->vol.util, channelNo ); + + stream_open( stream ); + stream_putBits( stream, XWPROTO_NBITS, code ); + + return stream; +} /* messageStreamWithHeader */ + +/* Check that the message belongs to this game, whatever. Pull out the data + * put in by messageStreamWithHeader -- except for the code, which will have + * already come out. + */ +static XP_Bool +readStreamHeader( ServerCtxt* XP_UNUSED(server), + XWStreamCtxt* XP_UNUSED(stream) ) +{ + return XP_TRUE; +} /* readStreamHeader */ + +static void +sendBadWordMsgs( ServerCtxt* server ) +{ + XWStreamCtxt* stream; + + stream = messageStreamWithHeader( server, server->lastMoveSource, + XWPROTO_BADWORD_INFO ); + stream_putBits( stream, PLAYERNUM_NBITS, server->nv.currentTurn ); + + XP_ASSERT( server->illegalWordInfo.nWords > 0 ); + bwiToStream( stream, &server->illegalWordInfo ); + + stream_destroy( stream ); + + freeBWI( MPPARM(server->mpool) &server->illegalWordInfo ); +} /* sendBadWordMsgs */ +#endif + +static void +badWordMoveUndoAndTellUser( ServerCtxt* server, BadWordInfo* bwi ) +{ + XP_U16 turn; + ModelCtxt* model = server->vol.model; + /* I'm the server. I need to send a message to everybody else telling + them the move's rejected. Then undo it on this side, replacing it with + model_commitRejectedPhony(); */ + + model_rejectPreviousMove( model, server->pool, &turn ); + + util_warnIllegalWord( server->vol.util, bwi, turn, XP_TRUE ); +} /* badWordMoveUndoAndTellUser */ + +EngineCtxt* +server_getEngineFor( ServerCtxt* server, XP_U16 playerNum ) +{ + ServerPlayer* player; + EngineCtxt* engine; + + XP_ASSERT( playerNum < server->vol.gi->nPlayers ); + + player = &server->players[playerNum]; + engine = player->engine; + if ( !engine && server->vol.gi->players[playerNum].isLocal ) { + engine = engine_make( MPPARM(server->mpool) + server->vol.util, + server->vol.gi->players[playerNum].isRobot ); + player->engine = engine; + } + + return engine; +} /* server_getEngineFor */ + +void +server_resetEngine( ServerCtxt* server, XP_U16 playerNum ) +{ + ServerPlayer* player = &server->players[playerNum]; + if ( !!player->engine ) { + XP_ASSERT( player->deviceIndex == 0 ); + engine_reset( player->engine ); + } +} /* server_resetEngine */ + +static void +resetEngines( ServerCtxt* server ) +{ + XP_U16 i; + XP_U16 nPlayers = server->vol.gi->nPlayers; + + for ( i = 0; i < nPlayers; ++i ) { + server_resetEngine( server, i ); + } +} /* resetEngines */ + +#ifdef TEST_ROBOT_TRADE +static void +makeNotAVowel( ServerCtxt* server, Tile* newTile ) +{ + char face[4]; + Tile tile = *newTile; + PoolContext* pool = server->pool; + TrayTileSet set; + DictionaryCtxt* dict = model_getDictionary( server->vol.model ); + XP_U8 numGot = 1; + + set.nTiles = 1; + + for ( ; ; ) { + + XP_U16 len = dict_tilesToString( dict, &tile, 1, face ); + + if ( len == 1 ) { + switch ( face[0] ) { + case 'A': + case 'E': + case 'I': + case 'O': + case 'U': + case '_': + break; + default: + *newTile = tile; + return; + } + } + + set.tiles[0] = tile; + pool_replaceTiles( pool, &set ); + + pool_requestTiles( pool, &tile, &numGot ); + + } + +} /* makeNotAVowel */ +#endif + +static void +curTrayAsTexts( ServerCtxt* server, XP_U16 turn, const TrayTileSet* notInTray, + XP_U16* nUsedP, XP_UCHAR4* curTrayText ) +{ + const TrayTileSet* tileSet = model_getPlayerTiles( server->vol.model, turn ); + DictionaryCtxt* dict = model_getDictionary( server->vol.model ); + XP_U16 i, j; + XP_U16 size = tileSet->nTiles; + const Tile* tp = tileSet->tiles; + XP_U16 tradedTiles[MAX_TRAY_TILES]; + XP_U16 nNotInTray = 0; + XP_U16 nUsed = 0; + + XP_MEMSET( tradedTiles, 0xFF, sizeof(tradedTiles) ); + if ( !!notInTray ) { + const Tile* tp = notInTray->tiles; + nNotInTray = notInTray->nTiles; + for ( i = 0; i < nNotInTray; ++i ) { + tradedTiles[i] = *tp++; + } + } + + for ( i = 0; i < size; ++i ) { + Tile tile = *tp++; + XP_Bool toBeTraded = XP_FALSE; + + for ( j = 0; j < nNotInTray; ++j ) { + if ( tradedTiles[j] == tile ) { + tradedTiles[j] = 0xFFFF; + toBeTraded = XP_TRUE; + break; + } + } + + if ( !toBeTraded ) { + dict_tilesToString( dict, &tile, 1, + (XP_UCHAR*)&curTrayText[nUsed++], + sizeof(curTrayText[0]) ); + } + } + *nUsedP = nUsed; +} /* curTrayAsTexts */ + +/* Get tiles for one user. If picking is available, let user pick until + * cancels. Otherwise, and after cancel, pick for 'im. + */ +static void +fetchTiles( ServerCtxt* server, XP_U16 playerNum, XP_U16 nToFetch, + const TrayTileSet* tradedTiles, TrayTileSet* resultTiles ) +{ + XP_Bool ask; + XP_U16 nSoFar = 0; + XP_U16 nLeft; + PoolContext* pool = server->pool; + TrayTileSet oneTile; + PickInfo pi; + XP_UCHAR4 curTray[MAX_TRAY_TILES]; +#ifdef FEATURE_TRAY_EDIT + DictionaryCtxt* dict = model_getDictionary( server->vol.model ); +#endif + + XP_ASSERT( !!pool ); +#ifdef FEATURE_TRAY_EDIT + ask = server->vol.gi->allowPickTiles + && !server->vol.gi->players[playerNum].isRobot; +#else + ask = XP_FALSE; +#endif + + nLeft = pool_getNTilesLeft( pool ); + if ( nLeft < nToFetch ) { + nToFetch = nLeft; + } + + oneTile.nTiles = 1; + + pi.why = PICK_FOR_CHEAT; + pi.nTotal = nToFetch; + pi.thisPick = 0; + pi.curTiles = curTray; + + curTrayAsTexts( server, playerNum, tradedTiles, &pi.nCurTiles, curTray ); + +#ifdef FEATURE_TRAY_EDIT /* good compiler would note ask==0, but... */ + /* First ask until cancelled */ + for ( nSoFar = 0; ask && nSoFar < nToFetch; ) { + XP_UCHAR4 texts[MAX_UNIQUE_TILES]; + Tile tiles[MAX_UNIQUE_TILES]; + XP_S16 chosen; + XP_U16 nUsed = MAX_UNIQUE_TILES; + + model_packTilesUtil( server->vol.model, pool, + XP_TRUE, &nUsed, texts, tiles ); + + chosen = util_userPickTile( server->vol.util, &pi, playerNum, + (const XP_UCHAR4*)texts, nUsed ); + + if ( chosen == PICKER_PICKALL ) { + ask = XP_FALSE; + } else if ( chosen == PICKER_BACKUP ) { + if ( nSoFar > 0 ) { + TrayTileSet tiles; + tiles.nTiles = 1; + tiles.tiles[0] = resultTiles->tiles[--nSoFar]; + pool_replaceTiles( server->pool, &tiles ); + --pi.nCurTiles; + --pi.thisPick; + } + } else { + Tile tile = tiles[chosen]; + oneTile.tiles[0] = tile; + pool_removeTiles( pool, &oneTile ); + + (void)dict_tilesToString( dict, &tile, 1, + (XP_UCHAR*)&curTray[pi.nCurTiles++], + sizeof(curTray[0]) ); + resultTiles->tiles[nSoFar++] = tile; + ++pi.thisPick; + } + } +#endif + + /* Then fetch the rest without asking */ + if ( nSoFar < nToFetch ) { + XP_U8 nLeft = nToFetch - nSoFar; + Tile tiles[MAX_TRAY_TILES]; + + pool_requestTiles( pool, tiles, &nLeft ); + + XP_MEMCPY( &resultTiles->tiles[nSoFar], tiles, + nLeft * sizeof(resultTiles->tiles[0]) ); + nSoFar += nLeft; + } + + XP_ASSERT( nSoFar < 0x00FF ); + resultTiles->nTiles = (XP_U8)nSoFar; +} /* fetchTiles */ + +static void +assignTilesToAll( ServerCtxt* server ) +{ + XP_U16 numAssigned; + short i; + ModelCtxt* model = server->vol.model; + XP_U16 nPlayers = server->vol.gi->nPlayers; + + XP_ASSERT( server->vol.gi->serverRole != SERVER_ISCLIENT ); + XP_ASSERT( model_getDictionary(model) != NULL ); + if ( server->pool == NULL ) { + server->pool = pool_make( MPPARM_NOCOMMA(server->mpool) ); + XP_STATUSF( "initing pool" ); + pool_initFromDict( server->pool, model_getDictionary(model)); + } + + XP_STATUSF( "assignTilesToAll" ); + + model_setNPlayers( model, nPlayers ); + + numAssigned = pool_getNTilesLeft( server->pool ) / nPlayers; + if ( numAssigned > MAX_TRAY_TILES ) { + numAssigned = MAX_TRAY_TILES; + } + for ( i = 0; i < nPlayers; ++i ) { + TrayTileSet newTiles; + fetchTiles( server, i, numAssigned, NULL, &newTiles ); + model_assignPlayerTiles( model, i, &newTiles ); + } + +} /* assignTilesToAll */ + +#ifndef XWFEATURE_STANDALONE_ONLY +static void +getPlayerTime( ServerCtxt* server, XWStreamCtxt* stream, XP_U16 turn ) +{ + CurGameInfo* gi = server->vol.gi; + + if ( gi->timerEnabled ) { + XP_U16 secondsUsed = stream_getU16( stream ); + + gi->players[turn].secondsUsed = secondsUsed; + } +} /* getPlayerTime */ +#endif + +static void +nextTurn( ServerCtxt* server, XP_S16 nxtTurn ) +{ + ServerPlayer* player; + XP_U16 nPlayers = server->vol.gi->nPlayers; + XP_U16 playerTilesLeft; + XP_S16 currentTurn = server->nv.currentTurn; + XP_Bool moreToDo = XP_FALSE; + + if ( nxtTurn == PICK_NEXT ) { + XP_ASSERT( currentTurn >= 0 ); + playerTilesLeft = model_getNumTilesTotal(server->vol.model, + currentTurn); + nxtTurn = (currentTurn+1) % nPlayers; + } else { + /* We're doing an undo, and so won't bother figuring out who the + previous turn was or how many tiles he had: it's a sure thing he + "has" enough to be allowed to take the turn just undone. */ + playerTilesLeft = MAX_TRAY_TILES; + } + SETSTATE( server, XWSTATE_INTURN ); /* even if game over, if undoing */ + + if ( (playerTilesLeft > 0) && tileCountsOk(server) && NPASSES_OK(server) ){ + + player = &server->players[nxtTurn]; + setTurn( server, nxtTurn ); + + } else { + /* I discover that the game should end. If I'm the client, + though, should I wait for the server to deduce this and send + out a message? I think so. Yes, but be sure not to compute + another PASS move. Just don't do anything! */ + if ( server->vol.gi->serverRole != SERVER_ISCLIENT ) { + SETSTATE( server, XWSTATE_NEEDSEND_ENDGAME ); + moreToDo = XP_TRUE; + } else { + XP_LOGF( "Doing nothing; waiting for server to end game." ); + /* I'm the client. Do ++nothing++. */ + } + } + + if ( server->vol.showPrevMove ) { + server->vol.showPrevMove = XP_FALSE; + if ( server->nv.showRobotScores ) { + server->vol.stateAfterShow = server->nv.gameState; + SETSTATE( server, XWSTATE_NEED_SHOWSCORE ); + moreToDo = XP_TRUE; + } + } + + /* It's safer, if perhaps not always necessary, to do this here. */ + resetEngines( server ); + + XP_ASSERT( server->nv.gameState != XWSTATE_GAMEOVER ); + callTurnChangeListener( server ); + + if ( robotMovePending(server) ) { + moreToDo = XP_TRUE; + } + + if ( moreToDo ) { + util_requestTime( server->vol.util ); + } +} /* nextTurn */ + +void +server_setTurnChangeListener( ServerCtxt* server, TurnChangeListener tl, + void* data ) +{ + server->vol.turnChangeListener = tl; + server->vol.turnChangeData = data; +} /* server_setTurnChangeListener */ + +void +server_setGameOverListener( ServerCtxt* server, GameOverListener gol, + void* data ) +{ + server->vol.gameOverListener = gol; + server->vol.gameOverData = data; +} /* server_setTurnChangeListener */ + +static XP_Bool +storeBadWords( XP_UCHAR* word, void* closure ) +{ + ServerCtxt* server = (ServerCtxt*)closure; + + XP_STATUSF( "storeBadWords called with \"%s\"", word ); + + server->illegalWordInfo.words[server->illegalWordInfo.nWords++] + = copyString( server->mpool, word ); + + return XP_TRUE; +} /* storeBadWords */ + +static XP_Bool +checkMoveAllowed( ServerCtxt* server, XP_U16 playerNum ) +{ + CurGameInfo* gi = server->vol.gi; + XP_ASSERT( server->illegalWordInfo.nWords == 0 ); + + if ( gi->phoniesAction == PHONIES_DISALLOW ) { + WordNotifierInfo info; + info.proc = storeBadWords; + info.closure = server; + (void)model_checkMoveLegal( server->vol.model, playerNum, + (XWStreamCtxt*)NULL, &info ); + } + + return server->illegalWordInfo.nWords == 0; +} /* checkMoveAllowed */ + +#ifndef XWFEATURE_STANDALONE_ONLY +static void +sendMoveTo( ServerCtxt* server, XP_U16 devIndex, XP_U16 turn, + XP_Bool legal, TrayTileSet* newTiles, + TrayTileSet* tradedTiles ) /* null if a move, set if a trade */ +{ + XWStreamCtxt* stream; + XP_Bool isTrade = !!tradedTiles; + CurGameInfo* gi = server->vol.gi; + XW_Proto code = gi->serverRole == SERVER_ISCLIENT? + XWPROTO_MOVEMADE_INFO_CLIENT : XWPROTO_MOVEMADE_INFO_SERVER; + + stream = messageStreamWithHeader( server, devIndex, code ); + + stream_putBits( stream, PLAYERNUM_NBITS, turn ); /* who made the move */ + + traySetToStream( stream, newTiles ); + + stream_putBits( stream, 1, isTrade ); + + if ( isTrade ) { + + traySetToStream( stream, tradedTiles ); + + } else { + stream_putBits( stream, 1, legal ); + + model_currentMoveToStream( server->vol.model, turn, stream ); + + if ( gi->timerEnabled ) { + stream_putU16( stream, gi->players[turn].secondsUsed ); + XP_STATUSF("*** wrote secondsUsed for player %d: %d", + turn, gi->players[turn].secondsUsed ); + } else { + XP_ASSERT( gi->players[turn].secondsUsed == 0 ); + } + + if ( !legal ) { + XP_ASSERT( server->illegalWordInfo.nWords > 0 ); + bwiToStream( stream, &server->illegalWordInfo ); + } + } + + stream_destroy( stream ); +} /* sendMoveTo */ + +static void +readMoveInfo( ServerCtxt* server, XWStreamCtxt* stream, + XP_U16* whoMovedP, XP_Bool* isTradeP, + TrayTileSet* newTiles, TrayTileSet* tradedTiles, + XP_Bool* legalP ) +{ + XP_U16 whoMoved = stream_getBits( stream, PLAYERNUM_NBITS ); + XP_Bool legalMove = XP_TRUE; + XP_Bool isTrade; + + traySetFromStream( stream, newTiles ); + isTrade = stream_getBits( stream, 1 ); + + if ( isTrade ) { + traySetFromStream( stream, tradedTiles ); + } else { + legalMove = stream_getBits( stream, 1 ); + model_makeTurnFromStream( server->vol.model, whoMoved, stream ); + + getPlayerTime( server, stream, whoMoved ); + } + + pool_removeTiles( server->pool, newTiles ); + + *whoMovedP = whoMoved; + *isTradeP = isTrade; + *legalP = legalMove; +} /* readMoveInfo */ + +static void +sendMoveToClientsExcept( ServerCtxt* server, XP_U16 whoMoved, XP_Bool legal, + TrayTileSet* newTiles, TrayTileSet* tradedTiles, + XP_U16 skip ) +{ + XP_U16 devIndex; + + for ( devIndex = 1; devIndex < server->nv.nDevices; ++devIndex ) { + if ( devIndex != skip ) { + sendMoveTo( server, devIndex, whoMoved, legal, + newTiles, tradedTiles ); + } + } +} /* sendMoveToClientsExcept */ + +static XWStreamCtxt* +makeTradeReportIf( ServerCtxt* server, const TrayTileSet* tradedTiles ) +{ + XWStreamCtxt* stream = NULL; + if ( server->nv.showRobotScores ) { + XP_UCHAR tradeBuf[64]; + const XP_UCHAR* tradeStr = util_getUserString( server->vol.util, + STRD_ROBOT_TRADED ); + XP_SNPRINTF( tradeBuf, sizeof(tradeBuf), tradeStr, + tradedTiles->nTiles ); + stream = mkServerStream( server ); + stream_putString( stream, tradeBuf ); + } + return stream; +} /* makeTradeReportIf */ + +static XWStreamCtxt* +makeMoveReportIf( ServerCtxt* server ) +{ + XWStreamCtxt* stream = NULL; + if ( server->nv.showRobotScores ) { + stream = mkServerStream( server ); + (void)model_checkMoveLegal( server->vol.model, + server->nv.currentTurn, stream, + NULL ); + } + return stream; +} /* makeMoveReportIf */ + +/* Client is reporting a move made, complete with new tiles and time taken by + * the player. Update the model with that information as a tentative move, + * then sent info about it to all the clients, and finally commit the move + * here. + */ +static XP_Bool +reflectMoveAndInform( ServerCtxt* server, XWStreamCtxt* stream ) +{ + ModelCtxt* model = server->vol.model; + XP_U16 whoMoved; + XP_U16 nTilesMoved = 0; /* trade case */ + XP_Bool isTrade; + XP_Bool isLegalMove; + XP_Bool doRequest = XP_FALSE; + TrayTileSet newTiles; + TrayTileSet tradedTiles; + CurGameInfo* gi = server->vol.gi; + XP_U16 sourceClientIndex = + getIndexForDevice( server, stream_getAddress( stream ) ); + XWStreamCtxt* mvStream = NULL; + + XP_ASSERT( gi->serverRole == SERVER_ISSERVER ); + + readMoveInfo( server, stream, &whoMoved, &isTrade, &newTiles, + &tradedTiles, &isLegalMove ); /* modifies model */ + XP_ASSERT( isLegalMove ); /* client should always report as true */ + isLegalMove = XP_TRUE; + + if ( isTrade ) { + + sendMoveToClientsExcept( server, whoMoved, XP_TRUE, &newTiles, + &tradedTiles, sourceClientIndex ); + + model_makeTileTrade( model, whoMoved, + &tradedTiles, &newTiles ); + pool_replaceTiles( server->pool, &tradedTiles ); + + server->vol.showPrevMove = XP_TRUE; + mvStream = makeTradeReportIf( server, &tradedTiles ); + + } else { + nTilesMoved = model_getCurrentMoveCount( model, whoMoved ); + isLegalMove = (nTilesMoved == 0) + || checkMoveAllowed( server, whoMoved ); + + /* I don't think this will work if there are more than two devices in + a palm game; need to change state and get out of here before + returning to send additional messages. PENDING(ehouse) */ + sendMoveToClientsExcept( server, whoMoved, isLegalMove, &newTiles, + (TrayTileSet*)NULL, sourceClientIndex ); + + server->vol.showPrevMove = XP_TRUE; + mvStream = makeMoveReportIf( server ); + + model_commitTurn( model, whoMoved, &newTiles ); + resetEngines( server ); + } + + if ( isLegalMove ) { + XP_U16 nTilesLeft = model_getNumTilesTotal( model, whoMoved ); + + if ( (gi->phoniesAction == PHONIES_DISALLOW) && (nTilesMoved > 0) ) { + server->lastMoveSource = sourceClientIndex; + SETSTATE( server, XWSTATE_MOVE_CONFIRM_MUSTSEND ); + doRequest = XP_TRUE; + } else if ( nTilesLeft > 0 ) { + nextTurn( server, PICK_NEXT ); + } else { + SETSTATE(server, XWSTATE_NEEDSEND_ENDGAME ); + doRequest = XP_TRUE; + } + + if ( !!mvStream ) { + XP_ASSERT( !server->vol.prevMoveStream ); + server->vol.prevMoveStream = mvStream; + } + + } else { + /* The client from which the move came still needs to be told. But we + can't send a message now since we're burried in a message handler. + (Palm, at least, won't manage.) So set up state to tell that + client again in a minute. */ + SETSTATE( server, XWSTATE_NEEDSEND_BADWORD_INFO ); + server->lastMoveSource = sourceClientIndex; + doRequest = XP_TRUE; + } + + if ( doRequest ) { + util_requestTime( server->vol.util ); + } + + return XP_TRUE; +} /* reflectMoveAndInform */ + +static XP_Bool +reflectMove( ServerCtxt* server, XWStreamCtxt* stream ) +{ + XP_Bool moveOk; + XP_Bool isTrade; + XP_Bool isLegal; + XP_U16 whoMoved; + TrayTileSet newTiles; + TrayTileSet tradedTiles; + ModelCtxt* model = server->vol.model; + XWStreamCtxt* mvStream = NULL; + + moveOk = XWSTATE_INTURN == server->nv.gameState; + if ( moveOk ) { + readMoveInfo( server, stream, &whoMoved, &isTrade, &newTiles, + &tradedTiles, &isLegal ); /* modifies model */ + + if ( isTrade ) { + model_makeTileTrade( model, whoMoved, &tradedTiles, &newTiles ); + pool_replaceTiles( server->pool, &tradedTiles ); + + server->vol.showPrevMove = XP_TRUE; + mvStream = makeTradeReportIf( server, &tradedTiles ); + } else { + server->vol.showPrevMove = XP_TRUE; + mvStream = makeMoveReportIf( server ); + model_commitTurn( model, whoMoved, &newTiles ); + } + + if ( !!mvStream ) { + XP_ASSERT( !server->vol.prevMoveStream ); + server->vol.prevMoveStream = mvStream; + } + + resetEngines( server ); + + if ( !isLegal ) { + XP_ASSERT( server->vol.gi->serverRole == SERVER_ISCLIENT ); + handleIllegalWord( server, stream ); + } + } else { + XP_LOGF( "%s: dropping move: state=%s", __func__, + getStateStr(server->nv.gameState ) ); + } + return moveOk; +} /* reflectMove */ +#endif /* XWFEATURE_STANDALONE_ONLY */ + +/* A local player is done with his turn. If a client device, broadcast + * the move to the server (after which a response should be coming soon.) + * If the server, then that step can be skipped: go straight to doing what + * the server does after learning of a move on a remote device. + * + * Second cut. Whether server or client, be responsible for checking the + * basic legality of the move and taking new tiles out of the pool. If + * client, send the move and new tiles to the server. If the server, fall + * back to what will do after hearing from client: tell everybody who doesn't + * already know what's happened: move and new tiles together. + * + * What about phonies when DISALLOW is set? The server's ultimately + * responsible, since it has the dictionary, so the client can't check. So + * when server, check and send move together with a flag indicating legality. + * Client is responsible for first posting the move to the model and then + * undoing it. When client, send the move and go into a state waiting to hear + * if it was legal -- but only if DISALLOW is set. + */ +XP_Bool +server_commitMove( ServerCtxt* server ) +{ + XP_S16 turn = server->nv.currentTurn; + ModelCtxt* model = server->vol.model; + CurGameInfo* gi = server->vol.gi; + TrayTileSet newTiles; + XP_U16 nTilesMoved; + XP_Bool isLegalMove = XP_TRUE; + XP_Bool isClient = gi->serverRole == SERVER_ISCLIENT; + +#ifdef DEBUG + if ( IS_ROBOT( &gi->players[turn] ) ) { + XP_ASSERT( model_checkMoveLegal( model, turn, (XWStreamCtxt*)NULL, + (WordNotifierInfo*)NULL ) ); + } +#endif + + /* commit the move. get new tiles. if server, send to everybody. + if client, send to server. */ + XP_ASSERT( turn >= 0 ); + + nTilesMoved = model_getCurrentMoveCount( model, turn ); + fetchTiles( server, turn, nTilesMoved, NULL, &newTiles ); + +#ifndef XWFEATURE_STANDALONE_ONLY + if ( isClient ) { + /* just send to server */ + sendMoveTo( server, SERVER_DEVICE, turn, XP_TRUE, &newTiles, + (TrayTileSet*)NULL ); + } else { + isLegalMove = checkMoveAllowed( server, turn ); + sendMoveToClientsExcept( server, turn, isLegalMove, &newTiles, + (TrayTileSet*)NULL, SERVER_DEVICE ); + } +#else + isLegalMove = checkMoveAllowed( server, turn ); +#endif + + model_commitTurn( model, turn, &newTiles ); + + if ( !isLegalMove && !isClient ) { + badWordMoveUndoAndTellUser( server, &server->illegalWordInfo ); + /* It's ok to free these guys. I'm the server, and the move was made + here, so I've notified all clients already by setting the flag (and + passing the word) in sendMoveToClientsExcept. */ + freeBWI( MPPARM(server->mpool) &server->illegalWordInfo ); + } + + if ( 0 ) { +#ifndef XWFEATURE_STANDALONE_ONLY + } else if (isClient && (gi->phoniesAction == PHONIES_DISALLOW) + && nTilesMoved > 0 ) { + SETSTATE( server, XWSTATE_MOVE_CONFIRM_WAIT ); +#endif + } else { + nextTurn( server, PICK_NEXT ); + } + + return XP_TRUE; +} /* server_commitMove */ + +static void +removeTradedTiles( ServerCtxt* server, TileBit selBits, TrayTileSet* tiles ) +{ + XP_U8 nTiles = 0; + XP_S16 index; + XP_S16 turn = server->nv.currentTurn; + + /* selBits: It's gross that server knows this much about tray's + implementation. PENDING(ehouse) */ + + for ( index = 0; selBits != 0; selBits >>= 1, ++index ) { + if ( (selBits & 0x01) != 0 ) { + Tile tile = model_getPlayerTile( server->vol.model, turn, index ); + tiles->tiles[nTiles++] = tile; + } + } + tiles->nTiles = nTiles; +} /* saveTradedTiles */ + +XP_Bool +server_commitTrade( ServerCtxt* server, TileBit selBits ) +{ + TrayTileSet oldTiles; + TrayTileSet newTiles; + XP_U16 turn = server->nv.currentTurn; + + removeTradedTiles( server, selBits, &oldTiles ); + + fetchTiles( server, turn, oldTiles.nTiles, &oldTiles, &newTiles ); + +#ifndef XWFEATURE_STANDALONE_ONLY + if ( server->vol.gi->serverRole == SERVER_ISCLIENT ) { + /* just send to server */ + sendMoveTo(server, SERVER_DEVICE, turn, XP_TRUE, &newTiles, &oldTiles); + } else { + sendMoveToClientsExcept( server, turn, XP_TRUE, &newTiles, &oldTiles, + SERVER_DEVICE ); + } +#endif + + pool_replaceTiles( server->pool, &oldTiles ); + model_makeTileTrade( server->vol.model, server->nv.currentTurn, + &oldTiles, &newTiles ); + nextTurn( server, PICK_NEXT ); + return XP_TRUE; +} /* server_commitTrade */ + +XP_S16 +server_getCurrentTurn( ServerCtxt* server ) +{ + return server->nv.currentTurn; +} /* server_getCurrentTurn */ + +XP_Bool +server_getGameIsOver( ServerCtxt* server ) +{ + return server->nv.gameState == XWSTATE_GAMEOVER; +} /* server_getGameIsOver */ + +static void +doEndGame( ServerCtxt* server ) +{ + SETSTATE( server, XWSTATE_GAMEOVER ); + setTurn( server, -1 ); + + (*server->vol.gameOverListener)( server->vol.gameOverData ); +} /* doEndGame */ + +/* Somebody wants to end the game. + * + * If I'm the server, I send a END_GAME message to all clients. If I'm a + * client, I send the GAME_OVER_REQUEST message to the server. If I'm the + * server and this is called in response to a GAME_OVER_REQUEST, send the + * GAME_OVER message to all clients including the one that requested it. + */ +static void +endGameInternal( ServerCtxt* server, GameEndReason XP_UNUSED(why) ) +{ + XP_ASSERT( server->nv.gameState != XWSTATE_GAMEOVER ); + + if ( server->vol.gi->serverRole != SERVER_ISCLIENT ) { + +#ifndef XWFEATURE_STANDALONE_ONLY + XP_U16 devIndex; + for ( devIndex = 1; devIndex < server->nv.nDevices; ++devIndex ) { + XWStreamCtxt* stream; + stream = messageStreamWithHeader( server, devIndex, + XWPROTO_END_GAME ); + stream_destroy( stream ); + } +#endif + doEndGame( server ); + +#ifndef XWFEATURE_STANDALONE_ONLY + } else { + XWStreamCtxt* stream; + stream = messageStreamWithHeader( server, SERVER_DEVICE, + XWPROTO_CLIENT_REQ_END_GAME ); + stream_destroy( stream ); + + /* Do I want to change the state I'm in? I don't think so.... */ +#endif + } +} /* endGameInternal */ + +void +server_endGame( ServerCtxt* server ) +{ + XW_State gameState = server->nv.gameState; + if ( gameState < XWSTATE_GAMEOVER && gameState >= XWSTATE_INTURN ) { + endGameInternal( server, END_REASON_USER_REQUEST ); + } +} /* server_endGame */ + +/* If game is about to end because one player's out of tiles, we don't want to + * keep trying to move */ +static XP_Bool +tileCountsOk( ServerCtxt* server ) +{ + XP_Bool maybeOver = 0 == pool_getNTilesLeft( server->pool ); + if ( maybeOver ) { + ModelCtxt* model = server->vol.model; + XP_U16 nPlayers = server->vol.gi->nPlayers; + XP_Bool zeroFound = XP_FALSE; + + while ( nPlayers-- ) { + XP_U16 count = model_getNumTilesTotal( model, nPlayers ); + if ( count == 0 ) { + zeroFound = XP_TRUE; + break; + } + } + maybeOver = zeroFound; + } + return !maybeOver; +} /* tileCountsOk */ + +static void +setTurn( ServerCtxt* server, XP_S16 turn ) +{ + if ( server->nv.currentTurn != turn ) { + server->nv.currentTurn = turn; + callTurnChangeListener( server ); + } +} + +#ifndef XWFEATURE_STANDALONE_ONLY +static void +tellMoveWasLegal( ServerCtxt* server ) +{ + XWStreamCtxt* stream; + + stream = messageStreamWithHeader( server, server->lastMoveSource, + XWPROTO_MOVE_CONFIRM ); + stream_destroy( stream ); +} /* tellMoveWasLegal */ + +static XP_Bool +handleIllegalWord( ServerCtxt* server, XWStreamCtxt* incoming ) +{ + BadWordInfo bwi; + XP_U16 whichPlayer; + + whichPlayer = stream_getBits( incoming, PLAYERNUM_NBITS ); + bwiFromStream( MPPARM(server->mpool) incoming, &bwi ); + + badWordMoveUndoAndTellUser( server, &bwi ); + + freeBWI( MPPARM(server->mpool) &bwi ); + + return XP_TRUE; +} /* handleIllegalWord */ + +static XP_Bool +handleMoveOk( ServerCtxt* server, XWStreamCtxt* XP_UNUSED(incoming) ) +{ + XP_Bool accepted = XP_TRUE; + XP_ASSERT( server->vol.gi->serverRole == SERVER_ISCLIENT ); + XP_ASSERT( server->nv.gameState == XWSTATE_MOVE_CONFIRM_WAIT ); + + nextTurn( server, PICK_NEXT ); + + return accepted; +} /* handleMoveOk */ + +static void +sendUndoTo( ServerCtxt* server, XP_U16 devIndex, XP_U16 nUndone, + XP_U16 lastUndone ) +{ + XWStreamCtxt* stream; + CurGameInfo* gi = server->vol.gi; + XW_Proto code = gi->serverRole == SERVER_ISCLIENT? + XWPROTO_UNDO_INFO_CLIENT : XWPROTO_UNDO_INFO_SERVER; + + stream = messageStreamWithHeader( server, devIndex, code ); + + stream_putU16( stream, nUndone ); + stream_putU16( stream, lastUndone ); + + stream_destroy( stream ); +} /* sendUndoTo */ + +static void +sendUndoToClientsExcept( ServerCtxt* server, XP_U16 skip, + XP_U16 nUndone, XP_U16 lastUndone ) +{ + XP_U16 devIndex; + + for ( devIndex = 1; devIndex < server->nv.nDevices; ++devIndex ) { + if ( devIndex != skip ) { + sendUndoTo( server, devIndex, nUndone, lastUndone ); + } + } +} /* sendUndoToClientsExcept */ + +static XP_Bool +reflectUndos( ServerCtxt* server, XWStreamCtxt* stream, XW_Proto code ) +{ + XP_U16 nUndone, lastUndone; + XP_S16 moveNum; + XP_U16 turn; + ModelCtxt* model = server->vol.model; + XP_Bool success = XP_TRUE; + + nUndone = stream_getU16( stream ); + lastUndone = stream_getU16( stream ); + + moveNum = lastUndone + nUndone - 1; + + success = model_undoLatestMoves(model, server->pool, nUndone, &turn, + &moveNum); + + if ( success ) { + XP_ASSERT( moveNum == lastUndone ); + + if ( code == XWPROTO_UNDO_INFO_CLIENT ) { /* need to inform */ + XP_U16 sourceClientIndex = + getIndexForDevice( server, stream_getAddress( stream ) ); + + sendUndoToClientsExcept( server, sourceClientIndex, nUndone, + lastUndone ); + + } + nextTurn( server, turn ); + } + + return success; +} /* reflectUndos */ +#endif + +XP_Bool +server_handleUndo( ServerCtxt* server ) +{ + XP_Bool result = XP_FALSE; + XP_U16 lastTurnUndone = 0; /* quiet compiler good */ + XP_U16 nUndone = 0; + ModelCtxt* model; + CurGameInfo* gi; + XP_U16 lastUndone = 0xFFFF; + + model = server->vol.model; + gi = server->vol.gi; + XP_ASSERT( !!model ); + + /* Undo until we find we've just undone a non-robot move. The point is + not to stop with a robot about to act (since that's a bit pointless.) + The exception is that if the first move was a robot move we'll stop + there, and it will immediately move again. */ + for ( ; ; ) { + XP_S16 moveNum = -1; /* don't need it checked */ + if ( !model_undoLatestMoves( model, server->pool, 1, &lastTurnUndone, + &moveNum ) ) { + break; + } + ++nUndone; + XP_ASSERT( moveNum >= 0 ); + lastUndone = moveNum; + if ( !IS_ROBOT(&gi->players[lastTurnUndone]) ) { + break; + } + } + + result = nUndone > 0 ; + if ( result ) { +#ifndef XWFEATURE_STANDALONE_ONLY + XP_ASSERT( lastUndone != 0xFFFF ); + if ( server->vol.gi->serverRole == SERVER_ISCLIENT ) { + sendUndoTo( server, SERVER_DEVICE, nUndone, lastUndone ); + } else { + sendUndoToClientsExcept( server, SERVER_DEVICE, nUndone, + lastUndone ); + } +#endif + nextTurn( server, lastTurnUndone ); + } else { + /* I'm a bit nervous about this. Is this the ONLY thing that cause + nUndone to come back 0? */ + util_userError( server->vol.util, ERR_CANT_UNDO_TILEASSIGN ); + } + + return result; +} /* server_handleUndo */ + +#ifndef XWFEATURE_STANDALONE_ONLY +XP_Bool +server_receiveMessage( ServerCtxt* server, XWStreamCtxt* incoming ) +{ + XW_Proto code; + XP_Bool accepted = XP_FALSE; + + code = (XW_Proto)stream_getBits( incoming, XWPROTO_NBITS ); + + printCode("Receiving", code); + + if ( code == XWPROTO_DEVICE_REGISTRATION ) { + /* This message is special: doesn't have the header that's possible + once the game's in progress and communication's been + established. */ + XP_STATUSF( "somebody's registering!!!" ); + accepted = handleRegistrationMsg( server, incoming ); + + } else if ( code == XWPROTO_CLIENT_SETUP ) { + + XP_STATUSF( "client got XWPROTO_CLIENT_SETUP" ); + XP_ASSERT( server->vol.gi->serverRole == SERVER_ISCLIENT ); + accepted = client_readInitialMessage( server, incoming ); + + } else if ( readStreamHeader( server, incoming ) ) { + + switch( code ) { +/* case XWPROTO_MOVEMADE_INFO: */ +/* accepted = client_reflectMoveMade( server, incoming ); */ +/* if ( accepted ) { */ +/* nextTurn( server ); */ +/* } */ +/* break; */ +/* case XWPROTO_TRADEMADE_INFO: */ +/* accepted = client_reflectTradeMade( server, incoming ); */ +/* if ( accepted ) { */ +/* nextTurn( server ); */ +/* } */ +/* break; */ +/* case XWPROTO_CLIENT_MOVE_INFO: */ +/* accepted = handleClientMoved( server, incoming ); */ +/* break; */ +/* case XWPROTO_CLIENT_TRADE_INFO: */ +/* accepted = handleClientTraded( server, incoming ); */ +/* break; */ + + case XWPROTO_MOVEMADE_INFO_CLIENT: /* client is reporting a move */ + accepted = (XWSTATE_INTURN == server->nv.gameState) + && reflectMoveAndInform( server, incoming ); + break; + + case XWPROTO_MOVEMADE_INFO_SERVER: /* server telling me about a move */ + accepted = reflectMove( server, incoming ); + if ( accepted ) { + nextTurn( server, PICK_NEXT ); + } + break; + + case XWPROTO_UNDO_INFO_CLIENT: + case XWPROTO_UNDO_INFO_SERVER: + accepted = reflectUndos( server, incoming, code ); + /* nextTurn is called by reflectUndos */ + break; + + case XWPROTO_BADWORD_INFO: + accepted = handleIllegalWord( server, incoming ); + if ( accepted && server->nv.gameState != XWSTATE_GAMEOVER ) { + nextTurn( server, PICK_NEXT ); + } + break; + + case XWPROTO_MOVE_CONFIRM: + accepted = handleMoveOk( server, incoming ); + break; + + case XWPROTO_CLIENT_REQ_END_GAME: + endGameInternal( server, END_REASON_USER_REQUEST ); + accepted = XP_TRUE; + break; + case XWPROTO_END_GAME: + doEndGame( server ); + accepted = XP_TRUE; + break; + default: + XP_WARNF( "Unknown code on incoming message: %d\n", code ); + break; + } /* switch */ + } + + stream_close( incoming ); + return accepted; +} /* server_receiveMessage */ +#endif + +void +server_formatDictCounts( ServerCtxt* server, XWStreamCtxt* stream, + XP_U16 nCols ) +{ + DictionaryCtxt* dict; + Tile tile; + XP_U16 nChars, nPrinted; + XP_UCHAR buf[48]; + const XP_UCHAR* fmt = util_getUserString( server->vol.util, + STRS_VALUES_HEADER ); + const XP_UCHAR* dname; + + XP_ASSERT( !!server->vol.model ); + + dict = model_getDictionary( server->vol.model ); + dname = dict_getShortName( dict ); + XP_SNPRINTF( buf, sizeof(buf), fmt, dname ); + stream_putString( stream, buf ); + + nChars = dict_numTileFaces( dict ); + + for ( tile = 0, nPrinted = 0; ; ) { + XP_UCHAR buf[24]; + XP_UCHAR face[4]; + XP_U16 count, value; + + count = dict_numTiles( dict, tile ); + + if ( count > 0 ) { + dict_tilesToString( dict, &tile, 1, face, sizeof(face) ); + value = dict_getTileValue( dict, tile ); + + XP_SNPRINTF( buf, sizeof(buf), (XP_UCHAR*)"%s: %d/%d", + face, count, value ); + stream_putString( stream, buf ); + } + + if ( ++tile >= nChars ) { + break; + } else if ( count > 0 ) { + if ( ++nPrinted % nCols == 0 ) { + stream_putString( stream, XP_CR ); + } else { + stream_putString( stream, (void*)" " ); + } + } + } +} /* server_formatDictCounts */ + +/* Print the faces of all tiles left in the pool, including those currently in + * trays !unless! player is >= 0, in which case his tiles get removed from the + * pool. The idea is to show him what tiles are left in play. + */ +void +server_formatRemainingTiles( ServerCtxt* server, XWStreamCtxt* stream, + XP_S16 player ) +{ + DictionaryCtxt* dict; + Tile tile; + XP_U16 nChars; + XP_U16 counts[MAX_UNIQUE_TILES+1]; /* 1 for the blank */ + PoolContext* pool = server->pool; + + if ( !pool ) { + return; /* might want to print an explanation in the stream */ + } + + XP_ASSERT( !!server->vol.model ); + + XP_MEMSET( counts, 0, sizeof(counts) ); + model_countAllTrayTiles( server->vol.model, counts, player ); + + dict = model_getDictionary( server->vol.model ); + nChars = dict_numTileFaces( dict ); + + for ( tile = 0; ; ) { + XP_U16 count = pool_getNTilesLeftFor( pool, tile ) + counts[tile]; + XP_Bool hasCount = count > 0; + + if ( hasCount ) { + XP_UCHAR face[4]; + dict_tilesToString( dict, &tile, 1, face, sizeof(face) ); + + for ( ; ; ) { + stream_putString( stream, face ); + if ( --count == 0 ) { + break; + } + stream_putString( stream, "." ); + } + } + + if ( ++tile >= nChars ) { + break; + } else if ( hasCount ) { + stream_putString( stream, (void*)" " ); + } + } +} /* server_formatRemainingTiles */ + +#define IMPOSSIBLY_LOW_SCORE -1000 +void +server_writeFinalScores( ServerCtxt* server, XWStreamCtxt* stream ) +{ + XP_S16 scores[MAX_NUM_PLAYERS]; + XP_S16 tilePenalties[MAX_NUM_PLAYERS]; + XP_S16 highestIndex; + XP_S16 highestScore; + XP_U16 place, nPlayers, i; + XP_S16 curScore; + ModelCtxt* model = server->vol.model; + const XP_UCHAR* addString = util_getUserString( server->vol.util, + STRD_REMAINING_TILES_ADD ); + const XP_UCHAR* subString = util_getUserString( server->vol.util, + STRD_UNUSED_TILES_SUB ); + XP_UCHAR timeBuf[16]; + XP_UCHAR* timeStr; + CurGameInfo* gi = server->vol.gi; + + XP_ASSERT( server->nv.gameState == XWSTATE_GAMEOVER ); + + model_figureFinalScores( model, scores, tilePenalties ); + + nPlayers = gi->nPlayers; + + for ( place = 1; ; ++place ) { + XP_UCHAR tmpbuf[48]; + XP_UCHAR buf[128]; + XP_Bool firstDone; + + highestScore = IMPOSSIBLY_LOW_SCORE; + highestIndex = -1; + + for ( i = 0; i < nPlayers; ++i ) { + if ( scores[i] > highestScore ) { + highestIndex = i; + highestScore = scores[i]; + } + } + + if ( highestIndex == -1 ) { + break; /* we're done */ + } else if ( place > 1 ) { + stream_putString( stream, XP_CR ); + } + scores[highestIndex] = IMPOSSIBLY_LOW_SCORE; + + curScore = model_getPlayerScore( model, highestIndex ); + + timeStr = (XP_UCHAR*)""; + if ( gi->timerEnabled ) { + XP_U16 penalty = player_timePenalty( gi, highestIndex ); + if ( penalty > 0 ) { + XP_SNPRINTF( timeBuf, sizeof(timeBuf), + util_getUserString( + server->vol.util, + STRD_TIME_PENALTY_SUB ), + penalty ); /* positive for formatting */ + timeStr = timeBuf; + } + } + + firstDone = model_getNumTilesTotal( model, highestIndex) == 0; + XP_SNPRINTF( tmpbuf, sizeof(tmpbuf), + (firstDone? addString:subString), + firstDone? + tilePenalties[highestIndex]: + -tilePenalties[highestIndex] ); + + XP_SNPRINTF( buf, sizeof(buf), + (XP_UCHAR*)"[%d] %s: %d" XP_CR " (%d %s%s)", + place, + emptyStringIfNull(gi->players[highestIndex].name), + highestScore, curScore, tmpbuf, timeStr ); + stream_putString( stream, buf ); + } +} /* server_writeFinalScores */ + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/server.h b/xwords4/common/server.h new file mode 100644 index 000000000..d33bc5080 --- /dev/null +++ b/xwords4/common/server.h @@ -0,0 +1,127 @@ + /* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef _SERVER_H_ +#define _SERVER_H_ + +#include "comtypes.h" /* that's *common* types */ + +#include "commmgr.h" +#include "model.h" + +#ifdef CPLUS +extern "C" { +#endif + +enum { + PHONIES_IGNORE, + PHONIES_WARN, + PHONIES_DISALLOW +}; +typedef XP_U8 XWPhoniesChoice; + +enum { + SERVER_STANDALONE, + SERVER_ISSERVER, + SERVER_ISCLIENT +}; +typedef XP_U8 DeviceRole; + +/* typedef struct ServerCtxt ServerCtxt; */ + +/* typedef struct ServerVtable { */ + +/* void (*m_registerPlayer)( ServerCtxt* server, XP_U16 playerNum, */ +/* XP_PlayerSocket socket ); */ + +/* void (*m_getTileValueInfo)( ServerCtxt* server, void* valueBuf ); */ + +/* } ServerVtable; */ + +ServerCtxt* server_make( MPFORMAL ModelCtxt* model, CommsCtxt* comms, + XW_UtilCtxt* util ); + +ServerCtxt* server_makeFromStream( MPFORMAL XWStreamCtxt* stream, + ModelCtxt* model, CommsCtxt* comms, + XW_UtilCtxt* util, XP_U16 nPlayers ); + +void server_writeToStream( ServerCtxt* server, XWStreamCtxt* stream ); + +void server_reset( ServerCtxt* server, CommsCtxt* comms ); +void server_destroy( ServerCtxt* server ); + +void server_prefsChanged( ServerCtxt* server, CommonPrefs* cp ); + +typedef void (*TurnChangeListener)( void* data ); +void server_setTurnChangeListener( ServerCtxt* server, TurnChangeListener tl, + void* data ); + +typedef void (*GameOverListener)( void* data ); +void server_setGameOverListener( ServerCtxt* server, GameOverListener gol, + void* data ); + +/* support random assignment by telling creator of new player what it's + * number will be */ +/* XP_U16 server_assignNum( ServerCtxt* server ); */ + +EngineCtxt* server_getEngineFor( ServerCtxt* server, XP_U16 playerNum ); +void server_resetEngine( ServerCtxt* server, XP_U16 playerNum ); + +XP_U16 server_secondsUsedBy( ServerCtxt* server, XP_U16 playerNum ); + +/* It might make more sense to have the board supply the undo method clients + call... */ +XP_Bool server_handleUndo( ServerCtxt* server ); + +/* signed because negative number means nobody's turn yet */ +XP_S16 server_getCurrentTurn( ServerCtxt* server ); +XP_Bool server_getGameIsOver( ServerCtxt* server ); +/* Signed in case no dictionary available */ +XP_S16 server_countTilesInPool( ServerCtxt* server ); + +XP_Bool server_do( ServerCtxt* server ); + +XP_Bool server_commitMove( ServerCtxt* server ); +XP_Bool server_commitTrade( ServerCtxt* server, TileBit bits ); + +/* call this when user wants to end the game */ +void server_endGame( ServerCtxt* server ); + +/* called when running as either client or server */ +XP_Bool server_receiveMessage( ServerCtxt* server, XWStreamCtxt* incomming ); + +/* client-side messages. Client (platform code)owns the stream used to talk + * to the server, and passes it in. */ +#ifndef XWFEATURE_STANDALONE_ONLY +void server_initClientConnection( ServerCtxt* server, XWStreamCtxt* stream ); +#endif + +void server_formatDictCounts( ServerCtxt* server, XWStreamCtxt* stream, + XP_U16 nCols ); +void server_formatRemainingTiles( ServerCtxt* server, XWStreamCtxt* stream, + XP_S16 player ); + +void server_writeFinalScores( ServerCtxt* server, XWStreamCtxt* stream ); + +#ifdef CPLUS +} +#endif + +#endif diff --git a/xwords4/common/states.h b/xwords4/common/states.h new file mode 100644 index 000000000..7a07a98d3 --- /dev/null +++ b/xwords4/common/states.h @@ -0,0 +1,61 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _STATES_H_ +#define _STATES_H_ + + + + + +typedef enum { + XWSTATE_NONE, + XWSTATE_BEGIN, + __UNUSED1, /*XWSTATE_POOL_INITED,*/ + XWSTATE_NEED_SHOWSCORE, /* client-only */ + XWSTATE_WAITING_ALL_REG, /* includes waiting for dict from server */ + XWSTATE_RECEIVED_ALL_REG, /* includes waiting for dict from server */ + XWSTATE_NEEDSEND_BADWORD_INFO, + XWSTATE_MOVE_CONFIRM_WAIT, /* client's waiting to hear back */ + XWSTATE_MOVE_CONFIRM_MUSTSEND, /* server should tell client asap */ + XWSTATE_NEEDSEND_ENDGAME, + XWSTATE_INTURN, + XWSTATE_GAMEOVER + +} XW_State; + +/* Game starts out in BEGIN. If the server expects other players, it goes + * into XWSTATE_WAITING_ALL_REG. Likewise goes any client waiting to hear + * from the server after sending off its info. A stand-alone game (server) + * goes immediately from BEGIN to WAITING_INFO. + * + * When a device gets tiles for all players (which happens in a single + * message where there's communication involved) it moves to INTURN (either + * ONDEVICE or OFFDEVICE). ONDEVICE changes to WAITING_INFO when the device + * sends its move to the server; OFFDEVICE changes to ONDEVICE if a + * notification that a move's been made is received and it's now a local + * player's turn; otherwise that notification may arrive with no change in + * XW_State (but a change in whose turn it is.) + +After a move is made (current player's device + * sends move + */ + + +#endif diff --git a/xwords4/common/strutils.c b/xwords4/common/strutils.c new file mode 100644 index 000000000..60154a9bd --- /dev/null +++ b/xwords4/common/strutils.c @@ -0,0 +1,283 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "strutils.h" +#include "xwstream.h" +#include "mempool.h" +#include "xptypes.h" + +#ifdef CPLUS +extern "C" { +#endif + +XP_U16 +bitsForMax( XP_U32 n ) +{ + XP_U16 result = 0; + XP_ASSERT( n > 0 ); + + while ( n != 0 ) { + n >>= 1; + ++result; + } + + return result; +} /* bitsForMax */ + +static void +tilesToStream( XWStreamCtxt* stream, const Tile* tiles, XP_U16 nTiles ) +{ + while ( nTiles-- ) { + stream_putBits( stream, TILE_NBITS, *tiles++ ); + } +} /* tilesToStream */ + +void +traySetToStream( XWStreamCtxt* stream, const TrayTileSet* ts ) +{ + XP_U16 nTiles = ts->nTiles; + stream_putBits( stream, NTILES_NBITS, nTiles ); + tilesToStream( stream, ts->tiles, nTiles ); +} /* traySetFromStream */ + +static void +tilesFromStream( XWStreamCtxt* stream, Tile* tiles, XP_U16 nTiles ) +{ + while ( nTiles-- ) { + *tiles++ = (Tile)stream_getBits( stream, TILE_NBITS ); + } +} /* tilesFromStream */ + +void +traySetFromStream( XWStreamCtxt* stream, TrayTileSet* ts ) +{ + XP_U16 nTiles = (XP_U16)stream_getBits( stream, NTILES_NBITS ); + tilesFromStream( stream, ts->tiles, nTiles ); + ts->nTiles = (XP_U8)nTiles; +} /* traySetFromStream */ + +#if 0 +static void +signedToStream( XWStreamCtxt* stream, XP_U16 nBits, XP_S32 num ) +{ + XP_Bool negative = num < 0; + stream_putBits( stream, 1, negative ); + if ( negative ) { + num *= -1; + } + stream_putBits( stream, nBits, num ); +} /* signedToStream */ + +XP_S32 +signedFromStream( XWStreamCtxt* stream, XP_U16 nBits ) +{ + XP_S32 result; + XP_Bool negative = stream_getBits( stream, 1 ); + result = stream_getBits( stream, nBits ); + if ( negative ) { + result *= -1; + } + return result; +} /* signedFromStream */ +#endif + +XP_UCHAR* +p_stringFromStream( MPFORMAL XWStreamCtxt* stream +#ifdef MEM_DEBUG + , const char* file, XP_U32 lineNo +#endif + ) +{ + XP_UCHAR buf[0xFF]; + XP_UCHAR* str = (XP_UCHAR*)NULL; + XP_U16 len = stringFromStreamHere( stream, buf, sizeof(buf) ); + + if ( len > 0 ) { +#ifdef MEM_DEBUG + str = mpool_alloc( mpool, len + 1, file, lineNo ); +#else + str = (XP_UCHAR*)XP_MALLOC( mpool, len + 1 ); /* leaked */ +#endif + XP_MEMCPY( str, buf, len + 1 ); + } + return str; +} /* makeStringFromStream */ + +XP_U16 +stringFromStreamHere( XWStreamCtxt* stream, XP_UCHAR* buf, XP_U16 buflen ) +{ + XP_U16 len = stream_getU8( stream ); + if ( len > 0 ) { + XP_ASSERT( len < buflen ); + if ( len >= buflen ) { + /* better to leave stream in bad state than overwrite stack */ + len = buflen - 1; + } + stream_getBytes( stream, buf, len ); + } + buf[len] = '\0'; + return len; +} + +void +stringToStream( XWStreamCtxt* stream, const XP_UCHAR* str ) +{ + XP_U16 len = str==NULL? 0: XP_STRLEN( (const char*)str ); + XP_ASSERT( len < 0xFF ); + stream_putU8( stream, (XP_U8)len ); + stream_putBytes( stream, str, len ); +} /* putStringToStream */ + +/***************************************************************************** + * + ****************************************************************************/ +XP_UCHAR* +p_copyString( MPFORMAL const XP_UCHAR* instr +#ifdef MEM_DEBUG + , const char* file, XP_U32 lineNo +#endif + ) +{ + XP_UCHAR* result = (XP_UCHAR*)NULL; + if ( !!instr ) { + XP_U16 len = 1 + XP_STRLEN( (const char*)instr ); +#ifdef MEM_DEBUG + result = mpool_alloc( mpool, len, file, lineNo ); +#else + result = XP_MALLOC( ignore, len ); +#endif + + XP_ASSERT( !!result ); + XP_MEMCPY( result, instr, len ); + } + return result; +} /* copyString */ + +void +p_replaceStringIfDifferent( MPFORMAL XP_UCHAR** curLoc, const XP_UCHAR* newStr +#ifdef MEM_DEBUG + , const char* file, XP_U32 lineNo +#endif + ) +{ + XP_UCHAR* curStr = *curLoc; + + if ( !!newStr && !!curStr && + (0 == XP_STRCMP( (const char*)curStr, (const char*)newStr ) ) ) { + /* do nothing; we're golden */ + } else { + if ( !!curStr ) { + XP_FREE( mpool, curStr ); + } +#ifdef MEM_DEBUG + curStr = p_copyString( mpool, newStr, file, lineNo ); +#else + curStr = p_copyString( newStr ); +#endif + } + + *curLoc = curStr; +} /* replaceStringIfDifferent */ + +/* + * A wrapper for printing etc. potentially null strings. + */ +XP_UCHAR* +emptyStringIfNull( XP_UCHAR* str ) +{ + return !!str? str : (XP_UCHAR*)""; +} /* emptyStringIfNull */ + +XP_Bool +randIntArray( XP_U16* rnums, XP_U16 count ) +{ + XP_Bool changed = XP_FALSE; + XP_U16 i; + + for ( i = 0; i < count; ++i ) { + rnums[i] = i; + } + + for ( i = count; i > 0 ; ) { + XP_U16 rIndex = ((XP_U16)XP_RANDOM()) % i; + if ( --i != rIndex ) { + XP_U16 tmp = rnums[rIndex]; + rnums[rIndex] = rnums[i]; + rnums[i] = tmp; + if ( !changed ) { + changed = XP_TRUE; + } + } + } + + return changed; +} /* randIntArray */ + +#ifdef DEBUG +#define NUM_PER_LINE 8 +void +log_hex( const XP_U8* memp, XP_U16 len, const char* tag ) +{ + const char* hex = "0123456789ABCDEF"; + XP_U16 i, j; + XP_U16 offset = 0; + + while ( offset < len ) { + XP_UCHAR buf[128]; + XP_UCHAR vals[NUM_PER_LINE*3]; + XP_UCHAR* valsp = vals; + XP_UCHAR chars[NUM_PER_LINE+1]; + XP_UCHAR* charsp = chars; + XP_U16 oldOffset = offset; + + for ( i = 0; i < NUM_PER_LINE && offset < len; ++i ) { + XP_U8 byte = memp[offset]; + for ( j = 0; j < 2; ++j ) { + *valsp++ = hex[(byte & 0xF0) >> 4]; + byte <<= 4; + } + *valsp++ = ':'; + + byte = memp[offset]; + if ( (byte >= 'A' && byte <= 'Z') + || (byte >= 'a' && byte <= 'z') + || (byte >= '0' && byte <= '9') ) { + /* keep it */ + } else { + byte = '.'; + } + *charsp++ = byte; + ++offset; + } + *(valsp-1) = '\0'; /* -1 to overwrite ':' */ + *charsp = '\0'; + + if ( (NULL == tag) || (XP_STRLEN(tag) + sizeof(vals) >= sizeof(buf)) ) { + tag = ""; + } + XP_SNPRINTF( buf, sizeof(buf), "%s[%d]: %s %s", tag, oldOffset, + vals, chars ); + XP_LOGF( "%s", buf ); + } +} +#endif + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/strutils.h b/xwords4/common/strutils.h new file mode 100644 index 000000000..07f4fac6e --- /dev/null +++ b/xwords4/common/strutils.h @@ -0,0 +1,97 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _STRUTILS_H_ +#define _STRUTILS_H_ + +#include "comtypes.h" +#include "model.h" + +#ifdef CPLUS +extern "C" { +#endif + +#define TILE_NBITS 6 /* 32 tiles plus the blank */ + +XP_U16 bitsForMax( XP_U32 n ); + +void traySetToStream( XWStreamCtxt* stream, const TrayTileSet* ts ); +void traySetFromStream( XWStreamCtxt* stream, TrayTileSet* ts ); + +XP_S32 signedFromStream( XWStreamCtxt* stream, XP_U16 nBits ); +void signedToStream( XWStreamCtxt* stream, XP_U16 nBits, XP_S32 num ); + +XP_UCHAR* p_stringFromStream( MPFORMAL XWStreamCtxt* stream +#ifdef MEM_DEBUG + , const char* file, XP_U32 lineNo +#endif + ); +#ifdef MEM_DEBUG +# define stringFromStream( p, in ) p_stringFromStream( (p), (in), __FILE__, __LINE__ ) +#else +# define stringFromStream( p, in ) p_stringFromStream( in ) +#endif + +XP_U16 stringFromStreamHere( XWStreamCtxt* stream, XP_UCHAR* buf, XP_U16 len ); +void stringToStream( XWStreamCtxt* stream, const XP_UCHAR* str ); + +XP_UCHAR* p_copyString( MPFORMAL const XP_UCHAR* instr +#ifdef MEM_DEBUG + , const char* file, XP_U32 lineNo +#endif + ); +#ifdef MEM_DEBUG +# define copyString( p, in ) p_copyString( (p), (in), __FILE__, __LINE__ ) +#else +# define copyString( p, in ) p_copyString( in ) +#endif + + +void p_replaceStringIfDifferent( MPFORMAL XP_UCHAR** curLoc, + const XP_UCHAR* newStr +#ifdef MEM_DEBUG + , const char* file, XP_U32 lineNo +#endif + ); +#ifdef MEM_DEBUG +# define replaceStringIfDifferent(p, sp, n) \ + p_replaceStringIfDifferent( (p), (sp), (n), __FILE__, __LINE__ ) +#else +# define replaceStringIfDifferent(p, sp, n) p_replaceStringIfDifferent((sp),(n)) +#endif + + +XP_UCHAR* emptyStringIfNull( XP_UCHAR* str ); + +/* Produce an array of ints 0..count-1, juggled */ +XP_Bool randIntArray( XP_U16* rnums, XP_U16 count ); + + +#ifdef DEBUG +void log_hex( const XP_U8*memp, XP_U16 len, const char* tag ); +# define LOG_HEX(m,l,t) log_hex((const XP_U8*)(m),(l),(t)) +#else +# define LOG_HEX(m,l,t) +#endif + +#ifdef CPLUS +} +#endif + +#endif /* _STRUTILS_H_ */ diff --git a/xwords4/common/tray.c b/xwords4/common/tray.c new file mode 100644 index 000000000..ff7711770 --- /dev/null +++ b/xwords4/common/tray.c @@ -0,0 +1,687 @@ +/* -*-mode: C; fill-column: 78; compile-command: "cd ../linux && make MEMDEBUG=TRUE"; -*- */ +/* + * Copyright 1997 - 2008 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "boardp.h" +#include "dragdrpp.h" +#include "engine.h" +#include "draw.h" +#include "strutils.h" + +#ifdef CPLUS +extern "C" { +#endif + +/****************************** prototypes ******************************/ +static void drawPendingScore( BoardCtxt* board, XP_Bool hasCursor ); +static XP_U16 countTilesToShow( BoardCtxt* board ); +static void figureDividerRect( BoardCtxt* board, XP_Rect* rect ); + +static XP_S16 +trayLocToIndex( BoardCtxt* board, XP_U16 loc ) +{ + if ( loc >= model_getNumTilesInTray( board->model, + board->selPlayer ) ) { + loc *= -1; + /* (0 * -1) is still 0, so reduce by 1. Will need to adjust + below. NOTE: this is something of a hack.*/ + --loc; + } + return loc; +} /* trayLocToIndex */ + +XP_S16 +pointToTileIndex( BoardCtxt* board, XP_U16 x, XP_U16 y, XP_Bool* onDividerP ) +{ + XP_S16 result = -1; /* not on a tile */ + XP_Rect divider; + XP_Rect biggerRect; + XP_Bool onDivider; + + figureDividerRect( board, ÷r ); + + /* The divider rect is narrower and kinda hard to tap on. Let's expand + it just for this test */ + biggerRect = divider; + if ( board->srcIsPen ) { + biggerRect.left -= 2; /* should be in proportion to tile dims */ + biggerRect.width += 4; + } + onDivider = rectContainsPt( &biggerRect, x, y ); + + if ( !onDivider ) { + if ( x > divider.left ) { + XP_ASSERT( divider.width == board->dividerWidth ); + x -= divider.width; + } + + XP_ASSERT( x >= board->trayBounds.left ); + x -= board->trayBounds.left; + result = x / board->trayScaleH; + + result = trayLocToIndex( board, result ); + } + + if ( onDividerP != NULL ) { + *onDividerP = onDivider; + } + + return result; +} /* pointToTileIndex */ + +void +figureTrayTileRect( BoardCtxt* board, XP_U16 index, XP_Rect* rect ) +{ + rect->left = board->trayBounds.left + (index * board->trayScaleH); + rect->top = board->trayBounds.top/* + 1 */; + + rect->width = board->trayScaleH; + rect->height = board->trayScaleV; + + if ( board->selInfo->dividerLoc <= index ) { + rect->left += board->dividerWidth; + } +} /* figureTileRect */ + +/* When drawing tray mid-drag: + * + * Rule is not to touch the model. + * + * Cases: Tile's been dragged into tray (but not yet dropped.); tile's been + * dragged out of tray (but not yet dropped); and tile's been dragged within + * tray. More's the point, there's an added tile and a removed one. We draw + * the added tile extra, and skip the removed one. + * + * We're walking two arrays at once, backwards. The first is the tile rects + * themselves. If the dirty bit is set, something must get drawn. The second + * is the model's view of tiles augmented by drag-and-drop. D-n-d may have + * removed a tile from the tray (for drawing purposes only), have added one, + * or both (drag-within-tray case). Since a drag lasts only until pen-up, + * there's never more than one tile involved. Adjustment is never by more + * than one. + * + * So while one counter (i) walks the array of rects, we can't use it + * unmodified to fetch from the model. Instead we increment or decrement it + * based on the drag state. + */ + +void +drawTray( BoardCtxt* board ) +{ + XP_Rect tileRect; + + if ( (board->trayInvalBits != 0) || board->dividerInvalid ) { + const XP_S16 turn = board->selPlayer; + PerTurnInfo* pti = board->selInfo; + + if ( draw_trayBegin( board->draw, &board->trayBounds, turn, + dfsFor( board, OBJ_TRAY ) ) ) { + DictionaryCtxt* dictionary = model_getDictionary( board->model ); + XP_S16 cursorBits = 0; + XP_Bool cursorOnDivider = XP_FALSE; +#ifdef KEYBOARD_NAV + XP_S16 cursorTile = pti->trayCursorLoc; + if ( (board->focussed == OBJ_TRAY) && !board->hideFocus ) { + cursorOnDivider = pti->dividerLoc == cursorTile; + if ( board->focusHasDived ) { + if ( !cursorOnDivider ) { + adjustForDivider( board, &cursorTile ); + cursorBits = 1 << cursorTile; + } + } else { + cursorBits = ALLTILES; + cursorOnDivider = XP_TRUE; + } + } +#endif + + if ( dictionary != NULL ) { + XP_Bool showFaces = board->trayVisState == TRAY_REVEALED; + Tile blank = dict_getBlankTile( dictionary ); + + if ( turn >= 0 ) { + XP_S16 ii; /* which tile slot are we drawing in */ + XP_U16 ddAddedIndx, ddRmvdIndx; + XP_U16 numInTray = countTilesToShow( board ); + XP_Bool isBlank; + XP_Bool isADrag = dragDropInProgress( board ); + CellFlags baseFlags = board->hideValsInTray && !board->showCellValues + ? CELL_VALHIDDEN : CELL_NONE; + + dragDropGetTrayChanges( board, &ddRmvdIndx, &ddAddedIndx ); + + /* draw in reverse order so drawing happens after + erasing */ + for ( ii = MAX_TRAY_TILES - 1; ii >= 0; --ii ) { + CellFlags flags = baseFlags; + XP_U16 mask = 1 << ii; + + if ( (board->trayInvalBits & mask) == 0 ) { + continue; + } +#ifdef KEYBOARD_NAV + if ( (cursorBits & mask) != 0 ) { + flags |= CELL_ISCURSOR; + } +#endif + figureTrayTileRect( board, ii, &tileRect ); + + if ( ii >= numInTray ) { + draw_drawTile( board->draw, &tileRect, NULL, + NULL, -1, flags | CELL_ISEMPTY ); + } else if ( showFaces ) { + XP_UCHAR buf[4]; + XP_Bitmap bitmap = NULL; + XP_UCHAR* textP = (XP_UCHAR*)NULL; + XP_U8 traySelBits = pti->traySelBits; + XP_S16 value; + Tile tile; + + if ( ddAddedIndx == ii ) { + dragDropTileInfo( board, &tile, &isBlank ); + } else { + XP_U16 modIndex = ii; + if ( ddAddedIndx < ii ) { + --modIndex; + } + /* while we're right of the removal area, + draw the one from the right to cover. */ + if ( ddRmvdIndx <= modIndex /*slotIndx*/ ) { + ++modIndex; + } + tile = model_getPlayerTile( board->model, + turn, modIndex ); + isBlank = tile == blank; + } + + textP = getTileDrawInfo( board, tile, isBlank, + &bitmap, &value, + buf, sizeof(buf) ); + if ( isADrag ) { + if ( ddAddedIndx == ii ) { + flags |= CELL_HIGHLIGHT; + } + } else if ( (traySelBits & (1<draw, &tileRect, textP, + bitmap, value, flags ); + } else { + draw_drawTileBack( board->draw, &tileRect, flags ); + } + } + } + + if ( (board->dividerWidth > 0) && board->dividerInvalid ) { + CellFlags flags = cursorOnDivider? CELL_ISCURSOR : CELL_NONE; + XP_Rect divider; + figureDividerRect( board, ÷r ); + if ( pti->dividerSelected + || dragDropIsDividerDrag(board) ) { + flags |= CELL_HIGHLIGHT; + } + draw_drawTrayDivider( board->draw, ÷r, flags ); + board->dividerInvalid = XP_FALSE; + } + + drawPendingScore( board, + (cursorBits & (1<<(MAX_TRAY_TILES-1))) != 0); + } + + draw_objFinished( board->draw, OBJ_TRAY, &board->trayBounds, + dfsFor( board, OBJ_TRAY ) ); + + board->trayInvalBits = 0; + } + } + +} /* drawTray */ + +XP_UCHAR* +getTileDrawInfo( const BoardCtxt* board, Tile tile, XP_Bool isBlank, + XP_Bitmap* bitmap, XP_S16* value, XP_UCHAR* buf, XP_U16 len ) +{ + XP_UCHAR* face = NULL; + DictionaryCtxt* dict = model_getDictionary( board->model ); + if ( isBlank ) { + tile = dict_getBlankTile( dict ); + } else { + dict_tilesToString( dict, &tile, 1, buf, len ); + face = buf; + } + + *value = dict_getTileValue( dict, tile ); + if ( dict_faceIsBitmap( dict, tile ) ) { + *bitmap = dict_getFaceBitmap( dict, tile, XP_TRUE ); + } + + return face; +} + +static XP_U16 +countTilesToShow( BoardCtxt* board ) +{ + XP_U16 numToShow; + XP_S16 selPlayer = board->selPlayer; + XP_U16 ddAddedIndx, ddRemovedIndx; + + XP_ASSERT( selPlayer >= 0 ); + if ( board->trayVisState == TRAY_REVEALED ) { + numToShow = model_getNumTilesInTray( board->model, selPlayer ); + } else { + numToShow = model_getNumTilesTotal( board->model, selPlayer ); + } + + dragDropGetTrayChanges( board, &ddRemovedIndx, &ddAddedIndx ); + if ( ddAddedIndx < MAX_TRAY_TILES ) { + ++numToShow; + } + if ( ddRemovedIndx < MAX_TRAY_TILES ) { + --numToShow; + } + + XP_ASSERT( numToShow <= MAX_TRAY_TILES ); + return numToShow; +} /* countTilesToShow */ + +static void +drawPendingScore( BoardCtxt* board, XP_Bool hasCursor ) +{ + /* Draw the pending score down in the last tray's rect */ + if ( countTilesToShow( board ) < MAX_TRAY_TILES ) { + XP_U16 selPlayer = board->selPlayer; + XP_S16 turnScore = 0; + XP_Rect lastTileR; + + (void)getCurrentMoveScoreIfLegal( board->model, selPlayer, + (XWStreamCtxt*)NULL, &turnScore ); + figureTrayTileRect( board, MAX_TRAY_TILES-1, &lastTileR ); + draw_score_pendingScore( board->draw, &lastTileR, turnScore, + selPlayer, + hasCursor?CELL_ISCURSOR:CELL_NONE ); + } +} /* drawPendingScore */ + +static void +figureDividerRect( BoardCtxt* board, XP_Rect* rect ) +{ + figureTrayTileRect( board, board->selInfo->dividerLoc, rect ); + rect->left -= board->dividerWidth; + rect->width = board->dividerWidth; +} /* figureDividerRect */ + +void +invalTilesUnderRect( BoardCtxt* board, const XP_Rect* rect ) +{ + /* This is an expensive way to do this -- calculating all the rects rather + than starting with the bounds of the rect passed in -- but this + function is called so infrequently and there are only 7 tiles, so leave + it for now. If it needs to be faster, invalCellsUnderRect is the model + to use. */ + + XP_U16 i; + XP_Rect locRect; + + for ( i = 0; i < MAX_TRAY_TILES; ++i ) { + figureTrayTileRect( board, i, &locRect ); + if ( rectsIntersect( rect, &locRect ) ) { + board_invalTrayTiles( board, (TileBit)(1 << i) ); + } + } + + figureDividerRect( board, &locRect ); + if ( rectsIntersect( rect, &locRect ) ) { + board->dividerInvalid = XP_TRUE; + } +} /* invalTilesUnderRect */ + +XP_Bool +handleTrayDuringTrade( BoardCtxt* board, XP_S16 index ) +{ + TileBit bits; + + XP_ASSERT( index >= 0 ); + + bits = 1 << index; + board->selInfo->traySelBits ^= bits; + board_invalTrayTiles( board, bits ); + return XP_TRUE; +} /* handleTrayDuringTrade */ + +static XP_Bool +handleActionInTray( BoardCtxt* board, XP_S16 index, XP_Bool onDivider ) +{ + XP_Bool result = XP_FALSE; + const XP_U16 selPlayer = board->selPlayer; + PerTurnInfo* pti = board->selInfo; + + if ( onDivider ) { + /* toggle divider sel state */ + pti->dividerSelected = !pti->dividerSelected; + board->dividerInvalid = XP_TRUE; + pti->traySelBits = NO_TILES; + result = XP_TRUE; + } else if ( pti->tradeInProgress ) { + if ( index >= 0 ) { + result = handleTrayDuringTrade( board, index ); + } + } else if ( index >= 0 ) { + result = moveTileToArrowLoc( board, (XP_U8)index ); + if ( !result ) { + TileBit newBits = 1 << index; + XP_U8 selBits = pti->traySelBits; + /* Tap on selected tile unselects. If we don't do this, + then there's no way to unselect and so no way to turn + off the placement arrow */ + if ( newBits == selBits ) { + board_invalTrayTiles( board, selBits ); + pti->traySelBits = NO_TILES; + } else if ( selBits != 0 ) { + XP_U16 selIndex = indexForBits( selBits ); + model_moveTileOnTray( board->model, selPlayer, + selIndex, index ); + pti->traySelBits = NO_TILES; + } else { + board_invalTrayTiles( board, newBits ); + pti->traySelBits = newBits; + } + board->dividerInvalid = + board->dividerInvalid || pti->dividerSelected; + pti->dividerSelected = XP_FALSE; + result = XP_TRUE; + } + } else if ( index == -(MAX_TRAY_TILES) ) { /* pending score tile */ + result = board_commitTurn( board ); + } else if ( index < 0 ) { /* other empty area */ + /* it better be true */ + (void)board_replaceTiles( board ); + result = XP_TRUE; + } + return result; +} /* handleActionInTray */ + +XP_Bool +handlePenUpTray( BoardCtxt* board, XP_U16 x, XP_U16 y ) +{ + XP_Bool onDivider; + XP_S16 index = pointToTileIndex( board, x, y, &onDivider ); + return handleActionInTray( board, index, onDivider ); +} /* handlePenUpTray */ + +XP_U16 +indexForBits( XP_U8 bits ) +{ + XP_U16 result = 0; + XP_U16 mask = 1; + + XP_ASSERT( bits != 0 ); /* otherwise loops forever */ + + while ( (mask & bits) == 0 ) { + ++result; + mask <<= 1; + } + return result; +} /* indexForBits */ + +XP_Bool +dividerMoved( BoardCtxt* board, XP_U8 newLoc ) +{ + XP_U8 oldLoc = board->selInfo->dividerLoc; + XP_Bool moved = oldLoc != newLoc; + if ( moved ) { + board->selInfo->dividerLoc = newLoc; + + /* This divider's index corresponds to the tile it's to the left of, and + there's no need to invalidate any tiles to the left of the uppermore + divider position. */ + if ( oldLoc > newLoc ) { + --oldLoc; + } else { + --newLoc; + } + invalTrayTilesBetween( board, newLoc, oldLoc ); + + board->dividerInvalid = XP_TRUE; + /* changed number of available tiles */ + board_resetEngine( board ); + } + return moved; +} /* dividerMoved */ + +void +board_invalTrayTiles( BoardCtxt* board, TileBit what ) +{ + board->trayInvalBits |= what; +} /* invalTrayTiles */ + +void +invalTrayTilesAbove( BoardCtxt* board, XP_U16 tileIndex ) +{ + TileBit bits = 0; + while ( tileIndex < MAX_TRAY_TILES ) { + bits |= 1 << tileIndex++; + } + board_invalTrayTiles( board, bits ); +} + +void +invalTrayTilesBetween( BoardCtxt* board, XP_U16 tileIndex1, + XP_U16 tileIndex2 ) +{ + TileBit bits = 0; + + if ( tileIndex1 > tileIndex2 ) { + XP_U16 tmp = tileIndex1; + tileIndex1 = tileIndex2; + tileIndex2 = tmp; + } + + while ( tileIndex1 <= tileIndex2 ) { + bits |= (1 << tileIndex1); + ++tileIndex1; + } + board_invalTrayTiles( board, bits ); +} /* invalTrayTilesBetween */ + +XP_Bool +board_juggleTray( BoardCtxt* board ) +{ + XP_Bool result = XP_FALSE; + const XP_S16 turn = board->selPlayer; + + if ( checkRevealTray( board ) ) { + XP_S16 nTiles; + XP_U16 dividerLoc = board->selInfo->dividerLoc; + ModelCtxt* model = board->model; + + nTiles = model_getNumTilesInTray( model, turn ) - dividerLoc; + if ( nTiles > 1 ) { + XP_S16 i; + Tile tmpT[MAX_TRAY_TILES]; + XP_U16 newT[MAX_TRAY_TILES]; + + /* loop until there'll be change */ + while ( !randIntArray( newT, nTiles ) ) { + } + + /* save copies of the tiles in juggled order */ + for ( i = 0; i < nTiles; ++i ) { + tmpT[i] = model_getPlayerTile( model, turn, + (Tile)(dividerLoc + newT[i]) ); + } + + /* delete tiles off right end; put juggled ones back on the other */ + for ( i = nTiles - 1; i >= 0; --i ) { + (void)model_removePlayerTile( model, turn, -1 ); + model_addPlayerTile( model, turn, dividerLoc, tmpT[i] ); + } + board->selInfo->traySelBits = 0; + result = XP_TRUE; + } + } + return result; +} /* board_juggleTray */ + +#ifdef KEYBOARD_NAV +void +adjustForDivider( const BoardCtxt* board, XP_S16* index ) +{ + XP_U16 dividerLoc = board->selInfo->dividerLoc; + if ( dividerLoc <= *index ) { + --*index; + } +} + +XP_Bool +tray_moveCursor( BoardCtxt* board, XP_Key cursorKey, XP_Bool preflightOnly, + XP_Bool* pUp ) +{ + XP_Bool draw = XP_FALSE; + XP_Bool up = XP_FALSE; + + if ( cursorKey == XP_CURSOR_KEY_UP || cursorKey == XP_CURSOR_KEY_DOWN ) { + up = XP_TRUE; + } else if ( (cursorKey == XP_CURSOR_KEY_RIGHT) + || (cursorKey == XP_CURSOR_KEY_LEFT) ) { + XP_Bool resetEngine = XP_FALSE; + XP_S16 delta = cursorKey == XP_CURSOR_KEY_RIGHT ? 1 : -1; + const XP_U16 selPlayer = board->selPlayer; + PerTurnInfo* pti = board->selInfo; + XP_S16 trayCursorLoc; + XP_S16 newLoc; + for ( ; ; ) { + trayCursorLoc = pti->trayCursorLoc; + newLoc = trayCursorLoc + delta; + if ( newLoc < 0 || newLoc > MAX_TRAY_TILES ) { + up = XP_TRUE; + } else if ( !preflightOnly ) { + XP_S16 tileLoc = trayCursorLoc; + XP_U16 nTiles = board->trayVisState == TRAY_REVEALED + ? model_getNumTilesInTray( board->model, selPlayer ) + : MAX_TRAY_TILES; + XP_Bool cursorOnDivider = trayCursorLoc == pti->dividerLoc; + XP_Bool cursorObjSelected; + XP_S16 newTileLoc; + + adjustForDivider( board, &tileLoc ); + cursorObjSelected = cursorOnDivider? + pti->dividerSelected : pti->traySelBits == (1 << tileLoc); + + if ( !cursorObjSelected ) { + /* nothing to do */ + } else if ( cursorOnDivider ) { + /* just drag the divider */ + pti->dividerLoc = newLoc; + resetEngine = XP_TRUE; + } else if ( pti->tradeInProgress ) { + /* nothing to do */ + } else { + /* drag the tile, skipping over the divider if needed */ + if ( (newLoc == pti->dividerLoc) && (newLoc > 0) ) { + newLoc += delta; + resetEngine = XP_TRUE; + } + newTileLoc = newLoc; + adjustForDivider( board, &newTileLoc ); + + if ( newTileLoc >= 0 ) { + XP_ASSERT( tileLoc < nTiles ); + if ( newTileLoc < nTiles ) { + model_moveTileOnTray( board->model, selPlayer, + tileLoc, newTileLoc ); + pti->traySelBits = (1 << newTileLoc); + } else { + pti->traySelBits = 0; /* clear selection */ + } + } + } + pti->trayCursorLoc = newLoc; + + /* Check if we're settling on an empty tile location other + than the rightmost one. If so, loop back and move + further. */ + newTileLoc = newLoc; + adjustForDivider( board, &newTileLoc ); + + if ( (newTileLoc > nTiles) + && (newLoc != pti->dividerLoc) + && (newTileLoc < MAX_TRAY_TILES-1) ) { + continue; + } + } + break; /* always exit loop if we get here */ + } + + /* PENDING: don't just inval everything */ + board->dividerInvalid = XP_TRUE; + board_invalTrayTiles( board, ALLTILES ); + if ( resetEngine ) { + board_resetEngine( board ); + } + } + draw = XP_TRUE; + + *pUp = up; + return draw; +} /* tray_moveCursor */ + +void +getFocussedTileCenter( BoardCtxt* board, XP_U16* xp, XP_U16* yp ) +{ + XP_Rect rect; + PerTurnInfo* pti = board->selInfo; + XP_S16 cursorTile = pti->trayCursorLoc; + XP_Bool cursorOnDivider = pti->dividerLoc == cursorTile; + + if ( cursorOnDivider ) { + figureDividerRect( board, &rect ); + } else { + XP_S16 indx = pti->trayCursorLoc; + adjustForDivider( board, &indx ); + XP_ASSERT( indx >= 0 ); + figureTrayTileRect( board, indx, &rect ); + } + getRectCenter( &rect, xp, yp ); +} + +#endif /* KEYBOARD_NAV */ + +#if defined FOR_GREMLINS +XP_Bool +board_moveDivider( BoardCtxt* board, XP_Bool right ) +{ + XP_Bool result = board->trayVisState == TRAY_REVEALED; + if ( result ) { + XP_U8 loc = board->selInfo->dividerLoc; + loc += MAX_TRAY_TILES + 1; + loc += right? 1:-1; + loc %= MAX_TRAY_TILES + 1; + + (void)dividerMoved( board, loc ); + } + return result; +} /* board_moveDivider */ +#endif + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/util.h b/xwords4/common/util.h new file mode 100644 index 000000000..74ecc91cf --- /dev/null +++ b/xwords4/common/util.h @@ -0,0 +1,272 @@ + /* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2007 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _UTIL_H_ +#define _UTIL_H_ + +#include "comtypes.h" + +#include "dawg.h" +#include "model.h" +#include "board.h" +#include "mempool.h" +#include "vtabmgr.h" +#include "comms.h" + +#include "xwrelay.h" + +#define LETTER_NONE '\0' + +typedef enum { + ERR_NONE, /* 0 is special case */ + ERR_TILES_NOT_IN_LINE, /* scoring a move where tiles aren't in line */ + ERR_NO_EMPTIES_IN_TURN, + ERR_TWO_TILES_FIRST_MOVE, + ERR_TILES_MUST_CONTACT, +/* ERR_NO_HINT_MID_TURN, */ + ERR_TOO_FEW_TILES_LEFT_TO_TRADE, + ERR_NOT_YOUR_TURN, + ERR_NO_PEEK_ROBOT_TILES, +#ifndef XWFEATURE_STANDALONE_ONLY + ERR_SERVER_DICT_WINS, + ERR_NO_PEEK_REMOTE_TILES, + ERR_REG_UNEXPECTED_USER, /* server asked to register too many remote + users */ + ERR_REG_SERVER_SANS_REMOTE, + STR_NEED_BT_HOST_ADDR, +#endif + ERR_CANT_TRADE_MID_MOVE, +/* ERR_CANT_ENGINE_MID_MOVE, */ +/* ERR_NOT_YOUR_TURN_TO_TRADE, */ +/* ERR_NOT_YOUR_TURN_TO_MOVE, */ + ERR_CANT_UNDO_TILEASSIGN, + ERR_CANT_HINT_WHILE_DISABLED, + + ERR_RELAY_BASE, + ERR_RELAY_END = ERR_RELAY_BASE + XWRELAY_ERROR_LASTERR +} UtilErrID; + +typedef enum { + QUERY_COMMIT_TURN, /* 0 means cancel; 1 means commit */ + QUERY_COMMIT_TRADE, + QUERY_ROBOT_MOVE, + QUERY_ROBOT_TRADE, + + QUERY_LAST_COMMON +} UtilQueryID; + +typedef enum { + PICK_FOR_BLANK + , PICK_FOR_CHEAT +} PICK_WHY; + +#define PICKER_PICKALL -1 +#define PICKER_BACKUP -2 + +typedef struct PickInfo { + XP_UCHAR4* curTiles; + XP_U16 nCurTiles; + XP_U16 nTotal; /* count to fetch for turn, <= MAX_TRAY_TILES */ + XP_U16 thisPick; /* <= nTotal */ + PICK_WHY why; +} PickInfo; + +typedef struct BadWordInfo { + XP_U16 nWords; + XP_UCHAR* words[MAX_TRAY_TILES+1]; /* can form in both directions */ +} BadWordInfo; + +/* XWTimerProc returns true if redraw was necessitated by what the proc did */ +typedef XP_Bool (*XWTimerProc)( void* closure, XWTimerReason why ); + +/* Platform-specific utility functions that need to be + */ +typedef struct UtilVtable { + + VTableMgr* (*m_util_getVTManager)(XW_UtilCtxt* uc); + +#ifndef XWFEATURE_STANDALONE_ONLY + XWStreamCtxt* (*m_util_makeStreamFromAddr )(XW_UtilCtxt* uc, + XP_PlayerAddr channelNo ); +#endif + + XWBonusType (*m_util_getSquareBonus)( XW_UtilCtxt* uc, + const ModelCtxt* model, + XP_U16 col, XP_U16 row ); + void (*m_util_userError)( XW_UtilCtxt* uc, UtilErrID id ); + + XP_Bool (*m_util_userQuery)( XW_UtilCtxt* uc, UtilQueryID id, + XWStreamCtxt* stream ); + + /* return of < 0 means computer should pick */ + XP_S16 (*m_util_userPickTile)( XW_UtilCtxt* uc, const PickInfo* pi, + XP_U16 playerNum, + const XP_UCHAR4* texts, XP_U16 nTiles ); + + XP_Bool (*m_util_askPassword)( XW_UtilCtxt* uc, const XP_UCHAR* name, + XP_UCHAR* buf, XP_U16* len ); + + void (*m_util_trayHiddenChange)(XW_UtilCtxt* uc, + XW_TrayVisState newState, + XP_U16 nVisibleRows ); + void (*m_util_yOffsetChange)(XW_UtilCtxt* uc, XP_U16 oldOffset, + XP_U16 newOffset ); +#ifdef XWFEATURE_TURNCHANGENOTIFY + void (*m_util_turnChanged)(XW_UtilCtxt* uc); +#endif + void (*m_util_notifyGameOver)( XW_UtilCtxt* uc ); + + XP_Bool (*m_util_hiliteCell)( XW_UtilCtxt* uc, XP_U16 col, XP_U16 row ); + + XP_Bool (*m_util_engineProgressCallback)( XW_UtilCtxt* uc ); + + void (*m_util_setTimer)( XW_UtilCtxt* uc, XWTimerReason why, XP_U16 when, + XWTimerProc proc, void* closure ); + + void (*m_util_requestTime)( XW_UtilCtxt* uc ); + + XP_Bool (*m_util_altKeyDown)( XW_UtilCtxt* uc ); + + XP_U32 (*m_util_getCurSeconds)( XW_UtilCtxt* uc ); + + DictionaryCtxt* (*m_util_makeEmptyDict)( XW_UtilCtxt* uc ); + + const XP_UCHAR* (*m_util_getUserString)( XW_UtilCtxt* uc, + XP_U16 stringCode ); + + XP_Bool (*m_util_warnIllegalWord)( XW_UtilCtxt* uc, BadWordInfo* bwi, + XP_U16 turn, XP_Bool turnLost ); + + void (*m_util_remSelected)(XW_UtilCtxt* uc); + +#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH + void (*m_util_addrChange)( XW_UtilCtxt* uc, const CommsAddrRec* oldAddr, + const CommsAddrRec* newAddr ); +#endif + +#ifdef XWFEATURE_SEARCHLIMIT + XP_Bool (*m_util_getTraySearchLimits)(XW_UtilCtxt* uc, + XP_U16* min, XP_U16* max ); +#endif + +#ifdef SHOW_PROGRESS + void (*m_util_engineStarting)( XW_UtilCtxt* uc, XP_U16 nBlanks ); + void (*m_util_engineStopping)( XW_UtilCtxt* uc ); +#endif + +} UtilVtable; + + +struct XW_UtilCtxt { + UtilVtable* vtable; + + struct CurGameInfo* gameInfo; + + void* closure; + MPSLOT +}; + +#define util_getVTManager(uc) \ + (uc)->vtable->m_util_getVTManager((uc)) + +#define util_makeStreamFromAddr(uc,a) \ + (uc)->vtable->m_util_makeStreamFromAddr((uc),(a)) + +#define util_getSquareBonus(uc,m,c,r) \ + (uc)->vtable->m_util_getSquareBonus((uc),(m),(c),(r)) + +#define util_userError(uc,err) \ + (uc)->vtable->m_util_userError((uc),(err)) + +#define util_userQuery(uc,qcode,str) \ + (uc)->vtable->m_util_userQuery((uc),(qcode),(str)) + +#define util_userPickTile( uc, w, n, tx, nt ) \ + (uc)->vtable->m_util_userPickTile( (uc), (w), (n), (tx), (nt) ) +#define util_askPassword( uc, n, b, lp ) \ + (uc)->vtable->m_util_askPassword( (uc), (n), (b), (lp) ) + +#define util_trayHiddenChange( uc, b, n ) \ + (uc)->vtable->m_util_trayHiddenChange((uc), (b), (n)) + +#define util_yOffsetChange( uc, o, n ) \ + (uc)->vtable->m_util_yOffsetChange((uc), (o), (n) ) + +#ifdef XWFEATURE_TURNCHANGENOTIFY +# define util_turnChanged( uc ) \ + (uc)->vtable->m_util_turnChanged((uc) ) +#else +# define util_turnChanged( uc ) +#endif + +#define util_notifyGameOver( uc ) \ + (uc)->vtable->m_util_notifyGameOver((uc)) + +#define util_hiliteCell( uc, c, r ) \ + (uc)->vtable->m_util_hiliteCell((uc), (c), (r)) + +#define util_engineProgressCallback( uc ) \ + (uc)->vtable->m_util_engineProgressCallback((uc)) + +#define util_setTimer( uc, why, when, proc, clos ) \ + (uc)->vtable->m_util_setTimer((uc),(why),(when),(proc),(clos)) + +#define util_requestTime( uc ) \ + (uc)->vtable->m_util_requestTime((uc)) + +#define util_altKeyDown( uc ) \ + (uc)->vtable->m_util_altKeyDown((uc)) + +#define util_getCurSeconds(uc) \ + (uc)->vtable->m_util_getCurSeconds((uc)) + +#define util_makeEmptyDict( uc ) \ + (uc)->vtable->m_util_makeEmptyDict((uc)) + +#define util_getUserString( uc, c ) \ + (uc)->vtable->m_util_getUserString((uc),(c)) + +#define util_warnIllegalWord( uc, w, p, b ) \ + (uc)->vtable->m_util_warnIllegalWord((uc),(w),(p),(b)) + +#define util_remSelected( uc ) \ + (uc)->vtable->m_util_remSelected((uc)) + +#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH +#define util_addrChange( uc, addro, addrn ) \ + (uc)->vtable->m_util_addrChange((uc), (addro), (addrn)) +#endif + +#ifdef XWFEATURE_SEARCHLIMIT +#define util_getTraySearchLimits(uc,min,max) \ + (uc)->vtable->m_util_getTraySearchLimits((uc), (min), (max)) +#endif + + +# ifdef SHOW_PROGRESS +# define util_engineStarting( uc, nb ) \ + (uc)->vtable->m_util_engineStarting((uc),(nb)) +# define util_engineStopping( uc ) \ + (uc)->vtable->m_util_engineStopping((uc)) +# else +# define util_engineStarting( uc, nb ) +# define util_engineStopping( uc ) +# endif + +#endif diff --git a/xwords4/common/virtuals.h b/xwords4/common/virtuals.h new file mode 100644 index 000000000..b01853438 --- /dev/null +++ b/xwords4/common/virtuals.h @@ -0,0 +1,33 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _VIRTUALS_H_ +#define _VIRTUALS_H_ + +/* List of classes requiring vtables -- for allocating and keeping track of + vtables in some central location. */ +enum { + VIRTUAL_UTIL, + VIRTUAL_DRAW, + VIRTUAL_STREAM, + VIRTUAL_NUM_VIRTUALS /* must be last */ +} XW_VIRTUALS; + + +#endif diff --git a/xwords4/common/vtabmgr.c b/xwords4/common/vtabmgr.c new file mode 100644 index 000000000..30f7ef711 --- /dev/null +++ b/xwords4/common/vtabmgr.c @@ -0,0 +1,75 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "vtabmgr.h" + +#define VTABLE_NUM_SLOTS VTABLE_LAST_ENTRY + +#ifdef CPLUS +extern "C" { +#endif + +struct VTableMgr { + void* slots[VTABLE_NUM_SLOTS]; +}; + +VTableMgr* +make_vtablemgr( MPFORMAL_NOCOMMA ) +{ + VTableMgr* result = (VTableMgr*)XP_MALLOC( mpool, sizeof(*result) ); + XP_MEMSET( result, 0, sizeof(*result) ); + + return result; +} /* make_vtablemgr */ + +void +vtmgr_destroy( MPFORMAL VTableMgr* vtmgr ) +{ + XP_U16 i; + + XP_ASSERT( !!vtmgr ); + + for ( i = 0; i < VTABLE_NUM_SLOTS; ++i ) { + void* vtable = vtmgr->slots[i]; + if ( !!vtable ) { + XP_FREE( mpool, vtable ); + } + } + + XP_FREE( mpool, vtmgr ); +} /* vtmgr_destroy */ + +void +vtmgr_setVTable( VTableMgr* vtmgr, VtableType typ, void* vtable ) +{ + XP_ASSERT( typ < VTABLE_NUM_SLOTS ); + XP_ASSERT( !vtmgr->slots[typ] ); + vtmgr->slots[typ] = vtable; +} /* VTMSetVtable */ + +void* +vtmgr_getVTable( VTableMgr* vtmgr, VtableType typ ) +{ + XP_ASSERT( typ < VTABLE_NUM_SLOTS ); + return vtmgr->slots[typ]; +} /* VTMGetVtable */ + +#ifdef CPLUS +} +#endif diff --git a/xwords4/common/vtabmgr.h b/xwords4/common/vtabmgr.h new file mode 100644 index 000000000..4714965fe --- /dev/null +++ b/xwords4/common/vtabmgr.h @@ -0,0 +1,50 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _VTABMGR_H_ +#define _VTABMGR_H_ + +#include "comtypes.h" +#include "mempool.h" + +#ifdef CPLUS +extern "C" { +#endif + +typedef enum { + VTABLE_MEM_STREAM = 0, + + VTABLE_LAST_ENTRY +} VtableType; + +typedef struct VTableMgr VTableMgr; + +VTableMgr* make_vtablemgr( MPFORMAL_NOCOMMA ); +void vtmgr_destroy( MPFORMAL VTableMgr* vtmgr ); + +void vtmgr_setVTable( VTableMgr* vtmgr, VtableType typ, void* vtable ); +void* vtmgr_getVTable( VTableMgr* vtmgr, VtableType typ ); + +#ifdef CPLUS +} +#endif + +#endif + + diff --git a/xwords4/common/xwproto.h b/xwords4/common/xwproto.h new file mode 100644 index 000000000..1c6c975c1 --- /dev/null +++ b/xwords4/common/xwproto.h @@ -0,0 +1,53 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _XWPROTO_H_ +#define _XWPROTO_H_ + + + + +typedef enum { + XWPROTO_ERROR = 0, /* illegal value */ + XWPROTO_CHAT, /* reserved... */ + XWPROTO_DEVICE_REGISTRATION, /* client's first message to server */ + XWPROTO_CLIENT_SETUP, /* server's first message to client */ + XWPROTO_MOVEMADE_INFO_CLIENT, /* client reports a move it made */ + XWPROTO_MOVEMADE_INFO_SERVER, /* server tells all clients about a move + made by it or another client */ + XWPROTO_UNDO_INFO_CLIENT, /* client reports undo[s] on the device */ + XWPROTO_UNDO_INFO_SERVER, /* server reports undos[s] happening + elsewhere*/ + //XWPROTO_CLIENT_MOVE_INFO, /* client says "I made this move" */ + //XWPROTO_SERVER_MOVE_INFO, /* server says "Player X made this move" */ +/* XWPROTO_CLIENT_TRADE_INFO, */ +/* XWPROTO_TRADEMADE_INFO, */ + XWPROTO_BADWORD_INFO, + XWPROTO_MOVE_CONFIRM, /* server tells move sender that move was + legal */ + //XWPROTO_MOVEMADE_INFO, /* info about tiles placed and received */ + XWPROTO_CLIENT_REQ_END_GAME, /* non-server wants to end the game */ + XWPROTO_END_GAME /* server says to end game */ + + +} XW_Proto; + +#define XWPROTO_NBITS 4 + +#endif diff --git a/xwords4/common/xwstate.h b/xwords4/common/xwstate.h new file mode 100644 index 000000000..3bb3d0831 --- /dev/null +++ b/xwords4/common/xwstate.h @@ -0,0 +1,29 @@ +/* + * Copyright 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef XWSTATE_H_ +#define XWSTATE_H_ + +typedef enum { + XW_UNDEFINED, + + XW_SERVER_WAITING_CLIENT_SIGNON, + XW_SERVER_READY_TO_PLAY, +} XWGameState; + + +#endif diff --git a/xwords4/common/xwstream.h b/xwords4/common/xwstream.h new file mode 100644 index 000000000..bb9a603cc --- /dev/null +++ b/xwords4/common/xwstream.h @@ -0,0 +1,164 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _XWSTREAM_H_ +#define _XWSTREAM_H_ + +#include "comtypes.h" +#include "memstream.h" +/* #include "xptypes.h" */ + +#define START_OF_STREAM 0 +#define END_OF_STREAM -1 + +typedef XP_U32 XWStreamPos; /* low 3 bits are bit offset; rest byte offset */ +enum { POS_READ, POS_WRITE }; +typedef XP_U8 PosWhich; + +#ifdef DEBUG +# define DBG_LINE_FILE_FORMAL , XP_U16 lin, const char* fil +# define DBG_LINE_FILE_PARM , __LINE__, __FILE__ +#else +# define DBG_LINE_FILE_FORMAL +# define DBG_LINE_FILE_PARM +#endif + +typedef struct StreamCtxVTable { + void (*m_stream_destroy)( XWStreamCtxt* dctx ); + + XP_U8 (*m_stream_getU8)( XWStreamCtxt* dctx ); + void (*m_stream_getBytes)( XWStreamCtxt* dctx, void* where, + XP_U16 count ); + XP_U16 (*m_stream_getU16)( XWStreamCtxt* dctx ); + XP_U32 (*m_stream_getU32)( XWStreamCtxt* dctx ); + XP_U32 (*m_stream_getBits)( XWStreamCtxt* dctx, XP_U16 nBits ); + + void (*m_stream_putU8)( XWStreamCtxt* dctx, XP_U8 byt ); + void (*m_stream_putBytes)( XWStreamCtxt* dctx, const void* whence, + XP_U16 count ); + void (*m_stream_putString)( XWStreamCtxt* dctx, const char* whence ); + void (*m_stream_putU16)( XWStreamCtxt* dctx, XP_U16 data ); + void (*m_stream_putU32)( XWStreamCtxt* dctx, XP_U32 data ); + void (*m_stream_putBits)( XWStreamCtxt* dctx, XP_U16 nBits, XP_U32 bits + DBG_LINE_FILE_FORMAL ); + + void (*m_stream_copyFromStream)( XWStreamCtxt* dctx, XWStreamCtxt* src, + XP_U16 nBytes ); + + XWStreamPos (*m_stream_getPos)( XWStreamCtxt* dctx, PosWhich which ); + XWStreamPos (*m_stream_setPos)( XWStreamCtxt* dctx, XWStreamPos newpos, + PosWhich which ); + + void (*m_stream_open)( XWStreamCtxt* dctx ); + void (*m_stream_close)( XWStreamCtxt* dctx ); + + XP_U16 (*m_stream_getSize)( XWStreamCtxt* dctx ); + +/* void (*m_stream_makeReturnAddr)( XWStreamCtxt* dctx, XP_PlayerAddr* addr, */ +/* XP_U16* addrLen ); */ + + XP_PlayerAddr (*m_stream_getAddress)( XWStreamCtxt* dctx ); + void (*m_stream_setAddress)( XWStreamCtxt* dctx, XP_PlayerAddr channelNo ); + + void (*m_stream_setVersion)( XWStreamCtxt* dctx, XP_U16 vers ); + XP_U16 (*m_stream_getVersion)( XWStreamCtxt* dctx ); + + void (*m_stream_setOnCloseProc)( XWStreamCtxt* dctx, + MemStreamCloseCallback proc ); +} StreamCtxVTable; + + +struct XWStreamCtxt { + StreamCtxVTable* vtable; +}; + + +#define stream_destroy(sc) \ + (sc)->vtable->m_stream_destroy(sc) + +#define stream_getU8(sc) \ + (sc)->vtable->m_stream_getU8(sc) + +#define stream_getBytes(sc, wh, c ) \ + (sc)->vtable->m_stream_getBytes((sc), (wh), (c)) + +#define stream_getU16(sc) \ + (sc)->vtable->m_stream_getU16(sc) + +#define stream_getU32(sc) \ + (sc)->vtable->m_stream_getU32(sc) + +#define stream_getBits(sc, n) \ + (sc)->vtable->m_stream_getBits((sc), (n)) + +#define stream_putU8(sc, b) \ + (sc)->vtable->m_stream_putU8((sc), (b)) + +#define stream_putBytes( sc, w, c ) \ + (sc)->vtable->m_stream_putBytes((sc), (w), (c)) + +#define stream_putString( sc, w ) \ + (sc)->vtable->m_stream_putString((sc), (w)) + +#define stream_putU16(sc, d) \ + (sc)->vtable->m_stream_putU16((sc), (d)) + +#define stream_putU32(sc, d) \ + (sc)->vtable->m_stream_putU32((sc), (d)) + +#define stream_putBits(sc, n, b) \ + (sc)->vtable->m_stream_putBits((sc), (n), (b) DBG_LINE_FILE_PARM ) + +#define stream_copyFromStream( sc, src, nb ) \ + (sc)->vtable->m_stream_copyFromStream((sc), (src), (nb)) + +#define stream_getPos(sc, w) \ + (sc)->vtable->m_stream_getPos((sc), (w)) + +#define stream_setPos(sc, p, w) \ + (sc)->vtable->m_stream_setPos((sc), (p), (w)) + +#define stream_open(sc) \ + (sc)->vtable->m_stream_open((sc)) + +#define stream_close(sc) \ + (sc)->vtable->m_stream_close((sc)) + +#define stream_getSize(sc) \ + (sc)->vtable->m_stream_getSize((sc)) + +#define stream_makeReturnAddr(sc,addr,len) \ + (sc)->vtable->m_stream_makeReturnAddr((sc),(addr),(len)) + +#define stream_getAddress(sc) \ + (sc)->vtable->m_stream_getAddress((sc)) + +#define stream_setAddress(sc,ch) \ + (sc)->vtable->m_stream_setAddress((sc),(ch)) + +#define stream_setVersion(sc,ch) \ + (sc)->vtable->m_stream_setVersion((sc),(ch)) + +#define stream_getVersion(sc) \ + (sc)->vtable->m_stream_getVersion((sc)) + +#define stream_setOnCloseProc(sc, p) \ + (sc)->vtable->m_stream_setOnCloseProc((sc), (p)) + +#endif /* _XWSTREAM_H_ */ diff --git a/xwords4/dawg/Catalan/info.txt b/xwords4/dawg/Catalan/info.txt new file mode 100644 index 000000000..c8f679016 --- /dev/null +++ b/xwords4/dawg/Catalan/info.txt @@ -0,0 +1,96 @@ +# Copyright 2002,2006 by Eric House (fixin@peak.org). All rights +# reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +LANGCODE:ca_ES + +NEEDSSORT:true + +LANGINFO:

Catalan includes several special tiles, "L.L", "NY" and +LANGINFO: "QU" in addition to Ç. There are no "Y" or "Q" tiles, +LANGINFO: and all words containing either of these letters not in +LANGINFO: combination with a "N" or "U" will be excluded from the +LANGINFO: dictionary.

+ +LANGINFO:

"L" is legal by itself, as are words in which two "L"s +LANGINFO: appear side-by-side. If you want your dictionary to include +LANGINFO: the "L.L" tile you'll need to make sure that the exact +LANGINFO: string "L.L" (or "l.l") appears in the wordlist you +LANGINFO: upload.

+ + +LANGFILTER_PRECLIP: tr 'ça-z' 'ÇA-Z' | +LANGFILTER_PRECLIP: grep -v 'Q[^U]' | +LANGFILTER_PRECLIP: grep -v '[^N]Y' | +LANGFILTER_PRECLIP: grep -v '^Y' | +LANGFILTER_PRECLIP: grep '^[ÇA-JL-VXYZ\.]*$' | +LANGFILTER_PRECLIP: sed -e 's/L\.L/1/g' -e 's/NY/2/g' -e 's/QU/3/g' | + +LANGFILTER_POSTCLIP: | tr -d '\r' +LANGFILTER_POSTCLIP: | sort -u +LANGFILTER_POSTCLIP: | tr -s '\n' '\000' + +#LANGFILTER_PRECLIP: sed 's/NY/2/g' | +#LANGFILTER_PRECLIP: sed 's/QU/3/g' | + + +LANGFILTER_POSTCLIP: | tr '123' '\001\002\003' + +# High bit means "official". Next 7 bits are an enum where +# Catalan==c. Low byte is padding +XLOC_HEADER:0x8C00 + + + +2 0 {"_"} +12 1 'A' +2 3 'B' +3 2 'C' +1 10 'Ç' +3 2 'D' +13 1 'E' +1 4 'F' +2 3 'G' +1 8 'H' +8 1 'I' +1 8 'J' +4 1 'L' +1 10 {"L.L"} +3 2 'M' +6 1 'N' +1 10 {"NY"} +5 1 'O' +2 3 'P' +1 8 {"QU"} +8 1 'R' +8 1 'S' +5 1 'T' +4 1 'U' +1 4 'V' +1 10 'X' +1 8 'Z' + +# +# NOTES: +#------ +# (1) - Just for avoiding character set mistakes: in the "INT." section of the Palm +# screen keyboard, this letter is on the first line, at the very right of "ae". +# (2) - This is another curious catalan double-letter: two "L" separated by a dot. +# (3) - In catalan, the "Y" is only used for the double-letter "NY". +# (4) - In catalan, the tile is not [Q], i [QU]; because it is not possible to +# use a "Q" alone. +# (5) - Blank tile. + diff --git a/xwords4/dawg/Czech-CP1250/Makefile b/xwords4/dawg/Czech-CP1250/Makefile new file mode 100644 index 000000000..a93be3001 --- /dev/null +++ b/xwords4/dawg/Czech-CP1250/Makefile @@ -0,0 +1,43 @@ +# -*-mode: Makefile; coding: windows-1250; -*- +# Copyright 2002-2008 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +XWLANG=Czech-CP1250 +LANGCODE=cs_CS + +TARGET_TYPE ?= PALM + +include ../Makefile.2to8 + +include ../Makefile.langcommon + +SOURCEDICT ?= $(XWDICTPATH)/$(XWLANG)/czech2_5.dict.gz + +$(XWLANG)Main.dict.gz: $(SOURCEDICT) Makefile + export LC_ALL=$(LANGCODE); \ + zcat $< | \ + tr [aábcèdïeéìfghiíjklmnòoóprøsštuúùvxyýzž] [AÁBCÈDÏEÉÌFGHIÍJKLMNÒOÓPRØSŠTUÚÙVXYÝZŽ] | \ + grep '^[AÁBCÈDÏEÉÌFGHIÍJKLMNÒOÓPRØSŠTUÚÙVXYÝZŽ]\+$$' | \ + gzip -c > $@ + +# Everything but creating of the Main.dict file is inherited from the +# "parent" Makefile.langcommon in the parent directory. + +clean: clean_common + rm -f $(XWLANG)Main.dict.gz *.bin $(XWLANG)*.pdb $(XWLANG)*.seb + +help: + @echo 'make [SOURCEDICT=$(XWDICTPATH)/$(XWLANG)/czech2_5.dict.gz]' diff --git a/xwords4/dawg/Czech-CP1250/info.txt b/xwords4/dawg/Czech-CP1250/info.txt new file mode 100644 index 000000000..28f171332 --- /dev/null +++ b/xwords4/dawg/Czech-CP1250/info.txt @@ -0,0 +1,84 @@ +# -*- coding: windows-1250; mode: conf; -*- +# Copyright 2002-2008 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +LANGCODE:cs_CZ +CHARSET:windows-1250 + +# deal with DOS files +LANGFILTER: tr -d '\r' +# tr seems to work on systems that don't know the Czech locale, but +# grep does not. So don't use grep, e.g. to eliminate words +# containing letters not in our alphabet. Instead, pass the -r flag +# via D2DARGS so they're dropped. +LANGFILTER: | tr [aábcèdïeéìfghiíjklmnòoóprøsštuúùvxyýzž] [AÁBCÈDÏEÉÌFGHIÍJKLMNÒOÓPRØSŠTUÚÙVXYÝZŽ] +LANGFILTER: | sort -u + +# presence of high-ascii means we must not pass -nosort +D2DARGS: -term 10 -r + +LANGINFO:

This BYOD language works on Czech wordlists encoded in +LANGINFO: windows-1250 and produces dictionaries that should work on +LANGINFO: windows-1250-localized systems. If your Czech wordlist is +LANGINFO: iso-8859-2-encoded, go back and choose Czech-ISO8859-2.

+ +# High bit means "official". Next 7 bits are an enum where +# Czech-CP1250==0x10. Low byte is padding. +XLOC_HEADER:0x9000 + +#COUNT VAL FACE + +2 0 {"_"} +5 1 'A' +2 2 'Á' +2 3 'B' +3 2 'C' +1 4 'È' +3 1 'D' +1 8 'Ï' +5 1 'E' +2 3 'É' +2 3 'Ì' +1 5 'F' +1 5 'G' +3 2 'H' +4 1 'I' +3 2 'Í' +2 2 'J' +3 1 'K' +3 1 'L' +3 2 'M' +5 1 'N' +1 6 'Ò' +6 1 'O' +1 7 'Ó' +3 1 'P' +3 1 'R' +2 4 'Ø' +4 1 'S' +2 4 'Š' +4 1 'T' +1 7 '' +3 2 'U' +1 5 'Ú' +1 4 'Ù' +4 1 'V' +1 10 'X' +2 2 'Y' +2 4 'Ý' +2 2 'Z' +1 4 'Ž' + diff --git a/xwords4/dawg/Czech-ISO8859-2/Makefile b/xwords4/dawg/Czech-ISO8859-2/Makefile new file mode 100644 index 000000000..f7c4e68aa --- /dev/null +++ b/xwords4/dawg/Czech-ISO8859-2/Makefile @@ -0,0 +1,43 @@ +# -*-mode: Makefile; coding: iso-8859-2; -*- +# Copyright 2002-2008 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +XWLANG=Czech-ISO8859-2 +LANGCODE=cs_CS + +TARGET_TYPE ?= PALM + +include ../Makefile.2to8 + +include ../Makefile.langcommon + +SOURCEDICT ?= $(XWDICTPATH)/$(XWLANG)/czech2_10_iso.dict.gz + +$(XWLANG)Main.dict.gz: $(SOURCEDICT) Makefile + export LC_ALL=$(LANGCODE); \ + zcat $< | \ + tr [aábcèdïeéìfghiíjklmnòoóprøs¹t»uúùvxyýz¾] [AÁBCÈDÏEÉÌFGHIÍJKLMNÒOÓPRØS©T«UÚÙVXYÝZ®] | \ + grep '^[AÁBCÈDÏEÉÌFGHIÍJKLMNÒOÓPRØS©T«UÚÙVXYÝZ®]\+$$' | \ + gzip -c > $@ + +# Everything but creating of the Main.dict file is inherited from the +# "parent" Makefile.langcommon in the parent directory. + +clean: clean_common + rm -f $(XWLANG)Main.dict.gz *.bin $(XWLANG)*.pdb $(XWLANG)*.seb + +help: + @echo 'make [SOURCEDICT=$(XWDICTPATH)/$(XWLANG)/czech2_5.dict.gz]' diff --git a/xwords4/dawg/Czech-ISO8859-2/info.txt b/xwords4/dawg/Czech-ISO8859-2/info.txt new file mode 100644 index 000000000..882db9c62 --- /dev/null +++ b/xwords4/dawg/Czech-ISO8859-2/info.txt @@ -0,0 +1,84 @@ +# -*- coding: iso-8859-2; mode: conf; -*- +# Copyright 2002-2008 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +LANGCODE:cs_CZ +CHARSET:iso-8859-2 + +# deal with DOS files +LANGFILTER: tr -d '\r' +# tr seems to work on systems that don't know the Czech locale, but +# grep does not. So don't use grep, e.g. to eliminate words +# containing letters not in our alphabet. Instead, pass the -r flag +# via D2DARGS so they're dropped. +LANGFILTER: | tr [aábcèdïeéìfghiíjklmnòoóprøs¹t»uúùvxyýz¾] [AÁBCÈDÏEÉÌFGHIÍJKLMNÒOÓPRØS©T«UÚÙVXYÝZ®] +LANGFILTER: | sort -u + +# presence of high-ascii means we must not pass -nosort +D2DARGS: -term 10 -r + +LANGINFO:

This BYOD language works on Czech wordlists encoded in +LANGINFO: iso-8859-2 and produces dictionaries that should work on +LANGINFO: iso-8859-2-localized systems. If your Czech wordlist is +LANGINFO: windows-1250-encoded, go back and choose Czech-CP1250.

+ +# High bit means "official". Next 7 bits are an enum where +# Czech-ISO8859-2==0x11. Low byte is padding. +XLOC_HEADER:0x9100 + +#COUNT VAL FACE + +2 0 {"_"} +5 1 'A' +2 2 'Á' +2 3 'B' +3 2 'C' +1 4 'È' +3 1 'D' +1 8 'Ï' +5 1 'E' +2 3 'É' +2 3 'Ì' +1 5 'F' +1 5 'G' +3 2 'H' +4 1 'I' +3 2 'Í' +2 2 'J' +3 1 'K' +3 1 'L' +3 2 'M' +5 1 'N' +1 6 'Ò' +6 1 'O' +1 7 'Ó' +3 1 'P' +3 1 'R' +2 4 'Ø' +4 1 'S' +2 4 '©' +4 1 'T' +1 7 '«' +3 2 'U' +1 5 'Ú' +1 4 'Ù' +4 1 'V' +1 10 'X' +2 2 'Y' +2 4 'Ý' +2 2 'Z' +1 4 '®' + diff --git a/xwords4/dawg/Danish/.cvsignore b/xwords4/dawg/Danish/.cvsignore new file mode 100644 index 000000000..2fb5d7db5 --- /dev/null +++ b/xwords4/dawg/Danish/.cvsignore @@ -0,0 +1,5 @@ +*.bin +*.xwd +*.pdb +*.inf +*.dict.gz diff --git a/xwords4/dawg/Danish/Makefile b/xwords4/dawg/Danish/Makefile new file mode 100644 index 000000000..0e289f130 --- /dev/null +++ b/xwords4/dawg/Danish/Makefile @@ -0,0 +1,42 @@ +# -*- mode: makefile -*- +# Copyright 2002-2005 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +XWLANG=Danish +LANGCODE=da_DK + +TARGET_TYPE ?= PALM + +include ../Makefile.2to8 + +include ../Makefile.langcommon + +SOURCEDICT ?= $(XWDICTPATH)/$(XWLANG)/LarsDanish.dict.gz + +$(XWLANG)Main.dict.gz: $(SOURCEDICT) Makefile + zcat $< | tr -d '\r' | tr [a-zåæø] [A-ZÅÆØ] | \ + grep '[AEIOUÅÆØ]' | \ + grep '^[A-PR-VX-ZÅÆØ]\+$$' | sort -u | \ + gzip -c > $@ + +# Everything but creating of the Main.dict file is inherited from the +# "parent" Makefile.langcommon in the parent directory. + +clean: clean_common + rm -f $(XWLANG)Main.dict.gz *.bin $(XWLANG)*.pdb $(XWLANG)*.seb + +help: + @echo 'make [SOURCEDICT=LarsDanish.dict.gz] [TARGET_TYPE=WINCE|PALM]' diff --git a/xwords4/dawg/Danish/info.txt b/xwords4/dawg/Danish/info.txt new file mode 100644 index 000000000..7e8353249 --- /dev/null +++ b/xwords4/dawg/Danish/info.txt @@ -0,0 +1,79 @@ +# Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +LANGCODE:da_DK + + +# deal with DOS files +LANGFILTER: tr -d '\r' +# uppercase all +LANGFILTER: | tr [a-zåæø] [A-ZÅÆØ] +# no words not containing a vowel +LANGFILTER: | grep '[AEIOUYÅÆØ]' +# none with illegal chars +LANGFILTER: | grep '^[A-PR-VX-ZÅÆØ]\+$' +# remove duplicates +LANGFILTER: | sort -u + +# Until I can figure out how to force sort to use a locale's collation +# rules we can't trust sort in the filtering rules above and so must +# leave the sorting work to dict2dawg.pl. + +D2DARGS: -r -term 10 + +LANGINFO:

Danish uses all English letters except Q and W. There +LANGINFO: are three non-English letters: 'Å', 'Æ' and 'Ø'.

+ +# High bit means "official". Next 7 bits are an enum where +# Danish==9. Low byte is padding +XLOC_HEADER:0x8900 + + + + +2 0 {"_"} +7 1 'A' +4 3 'B' +2 8 'C' +5 2 'D' +9 1 'E' +3 3 'F' +3 3 'G' +2 4 'H' +4 3 'I' +2 4 'J' +4 3 'K' +5 2 'L' +3 3 'M' +6 1 'N' +5 2 'O' +2 4 'P' +6 1 'R' +5 2 'S' +5 2 'T' +3 3 'U' +3 3 'V' +1 8 'X' +2 4 'Y' +1 8 'Z' +# These are the hard ones. O-with-slaththru, A-with-circle-over, and +# AE ASCII for AE is 198, for O-with-slaththru is 216, and for +# A-WITH-CIRCLE-OVER is 197. +2 4 'Å' +2 4 'Æ' +2 4 'Ø' + +# should ignore all after the above diff --git a/xwords4/dawg/Dutch/.cvsignore b/xwords4/dawg/Dutch/.cvsignore new file mode 100644 index 000000000..086c16f6f --- /dev/null +++ b/xwords4/dawg/Dutch/.cvsignore @@ -0,0 +1,3 @@ +*.bin +*.xwd +*.pdb diff --git a/xwords4/dawg/Dutch/Makefile b/xwords4/dawg/Dutch/Makefile new file mode 100644 index 000000000..0e87c082e --- /dev/null +++ b/xwords4/dawg/Dutch/Makefile @@ -0,0 +1,41 @@ +# -*- mode: makefile -*- +# Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +XWLANG=Dutch +LANGCODE=nl_NL + +TARGET_TYPE ?= PALM + +include ../Makefile.2to8 + +include ../Makefile.langcommon + +SOURCEDICT ?= $(XWDICTPATH)/$(XWLANG)/Dutch__unofficial_alphabetical.dict.gz + +$(XWLANG)Main.dict.gz: $(SOURCEDICT) Makefile + zcat $< | tr -d '\r' | tr [a-zäöü] [A-ZÄÖÜ] | \ + grep '^[A-Z]\+$$' | sort -u | \ + gzip -c > $@ + +# Everything but creating of the Main.dict file is inherited from the +# "parent" Makefile.langcommon in the parent directory. + +clean: clean_common + rm -f $(XWLANG)Main.dict.gz *.bin $(XWLANG)*.pdb $(XWLANG)*.seb + +help: + @echo 'make [SOURCEDICT=Dutch__unofficial_alphabetical.dict.gz]' diff --git a/xwords4/dawg/Dutch/info.txt b/xwords4/dawg/Dutch/info.txt new file mode 100644 index 000000000..58413d59d --- /dev/null +++ b/xwords4/dawg/Dutch/info.txt @@ -0,0 +1,72 @@ +# -*- mode:conf; -*- +# Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +LANGCODE:nl_NL + + +# deal with DOS files +LANGFILTER: tr -d '\r' +# uppercase all +LANGFILTER: | tr [a-z] [A-Z] +# none with illegal chars +LANGFILTER: | grep '^[A-Z]\+$' +LANGFILTER: | sort -u + +# Until I can figure out how to force sort to use a locale's collation +# rules we can't trust sort in the filtering rules above and so must +# leave the sorting work to dict2dawg.pl. +D2DARGS: -r -term 10 + +LANGINFO:

Dutch has the same 26 letters as English, though of +LANGINFO: course the counts and values are different. Filtering rules +LANGINFO: eliminate all words that contain letters not found on tiles.

+ +# High bit means "official". Next 7 bits are an enum where +# Dutch==B. Low byte is padding +XLOC_HEADER:0x8B00 + + + +2 0 {"_"} +6 1 'A' +2 3 'B' +2 5 'C' +5 2 'D' +18 1 'E' +2 4 'F' +3 3 'G' +2 4 'H' +4 1 'I' +2 4 'J' +3 3 'K' +3 3 'L' +3 3 'M' +10 1 'N' +6 1 'O' +2 3 'P' +1 10 'Q' +5 2 'R' +5 2 'S' +5 2 'T' +3 4 'U' +2 4 'V' +2 5 'W' +1 8 'X' +1 8 'Y' +2 4 'Z' + +# should ignore all after the above diff --git a/xwords4/dawg/English/.cvsignore b/xwords4/dawg/English/.cvsignore new file mode 100644 index 000000000..a5c303ddc --- /dev/null +++ b/xwords4/dawg/English/.cvsignore @@ -0,0 +1,10 @@ +*.bin +*.pdb +*.xwd +*.saved +*.pdr +*.ehouse +*.seb +decompose +temp +*.stamp diff --git a/xwords4/dawg/English/BasEnglish.dict.gz b/xwords4/dawg/English/BasEnglish.dict.gz new file mode 100644 index 0000000000000000000000000000000000000000..cf3f3806b429dc96c1e8bca46befa11411d38ff6 GIT binary patch literal 2537 zcmV({UWNBk`05zCdmg_1EMgKi3$Hq1lx=Rzv zZ~g;$`^c%a!V#twLLFIO!7b}6kG`zimPZoxJ5Y0L5We@_cb^lLpV9RFakcyAqSNT3 zySl9B>eGW7aaQ;3_4I{6iCG;nr&DPA#PPRAV#9QblXTV_^l@cUYEx_52wF>7>)JDTh2(XpOC1_Rx;4h?gx;zW;JMXvSZ zbEOizJ47^=le}C-7%$hSbFAy@F3_^Rt2lwA{|d%kSa+I2&bx2|gYUd*UPcVByt7>P z!E*_(oYa}$0`>JC)bLATRMmZgI)CM`mk0{+s!l=x#YgqpxBC6mFn?YwRP$Th*4!_# zR`b-?Xbc~lNoM%48w(@6fSLqN9mvmVlcA62Xb^6P8`#9lwnkT1NcwQKn~nyky;i@T zR_TC>WWAxOI>~l@F}#JkJP}Q}+**XL*+=vnDn$i`wo4ZRYU+@dZN6@ZiLxfc3TA`= zA`o!EQPv8Q_?Ck{L(sS!vM{~`&Y(YrMHN-h@ECQY))}9UBGorqefu^A*WI`B3r+jq zGAX(3ui7@Bu@!2)X7R&SZ@CmFCW_XS@N!kF$r$TDuf=>oRrkk6&Sk68uB|$@fq3y= zhYLb(b<^O12uH2bTIeJ4Q9q1ih%v;K?rYQ)?UH+iu=T#OXSc6=*`->$WV)Mr?e4`S zxA~?OcIOHp*8&M#Q&*+9v}@blHGd`}Rqd_U@yKF#@Dm^(7jiKlvHRn2^{Jf#GwSo@IrBRy-29%0jw5r}&ALFjx4lZ` z_v&#Y;769R*UVY9wKkDNDB%_&7dW6%Hs|(%7_^h)K=^8Ro{J0g_-#|riFYtv}l0g^RHygX(Rq6%(+KKz+gic3wzv!eLkD9CguMtyQ-0hZQ-3&T_ zD+8LoqvrqzM>*u7k5%2~A`YGJaDN|85eZ^m>Tm=CeG+g){c}TycNo@T4(ITuPKT}? zXlg+%5C4mwJp%3;KxaX^!ZRo6MnGM_A--g&hdd_`Y`(nWp;5W&JTep@3a0$dktc_% zwzPB-_%|fQLn5KS<|we1f$0zfm@xW)(UTPr`{@$yEI|8m`bx!PGiQ*eJwsMK^MmnI z44z`}lu{;B`>&3H>6n z-e7%Y^;Gp#+>zS4iUZ*7(hrjbLcea&YV)P0LP011C%zCo(0WF@uFPcuic*&x)0K-F z2n9e>wH4oy%#}wKSQ~>!geT91aEf*D$KnyHj|KOsJb~Jfx*-;ya9OL>Wku{N3nM>~ zohVJIPrg8PRI_29@I>#YF!h68|0dM6a+o%|t0zknIcl5Gdz9IJi7MUbF_93(iGAW;K{pe6}Ae2pf! zgW*mMq82!V+}#p9VVk1|uMwvE!4pS=C(wY&86>b!{-GQYZSL(1;qgZn;zG7sMx;Tx zp~X5=Qe9(Q6(=3^EeL|7vdTImF*eHK3t%!TUSpq8s!Os(Xa#X5TzN#1qf>?Jld(i; zQY8Fr)=1QZk?5i{i5RLzzwg?S)TZmWRM<*lkH(Wvql<8`Xnp~0=61mMLoke(xoXxl z$;Kypfp}{rCd$;P*htPChj<=M=>P;bn-pb|{6XS$h$KC(88T$#Nq&w1jk_0*)ZPHh z2v`MI@DX;(RB21H8%voVfd{P0iy?54vbCtYtHP8KNf{x1$*;z0&;I$~XN68s)}-~xlp<8W=c_r3(}P0PYN9#cT$4Q z!l)kX!fy2&FyCOrW(Et4MXFq~<*6kbr`gi3@3iL@Go|vs#^e`y6r&hH}-1g#SyF`T@fYB+xOu$T=ztRLZw-2m(MEWMNS04 z9~QZyG7=m=@*B0-SmkSl>rwdEQ-^@{BtIBF+6X#z>c*?I+2YG{?`VdKD))k8eFCw) z1_Ux1QR{2gvpLUW3#vr3Dsj`-Ig|`nl9cZPc%`TEl@0Bffd$j139ZOsi;~ltaO=}y zQMg`rzv5!zd>$Ycf{-QBu36k=6{J!#mHMv^8QE6brEV?ex1ruiEg8o+J}oQX z)iOVVM9at%&1}YLCTcZI4q@oYB)w{iLd}@Bt82<_n)&5cWbmNQgVrdRNNY}g%DcB> z%F$4NOrFgBG{jiIYS4xK`CCFYyOQqKD8xm9R@D@ljNvVUiv@LFUUkFN`!k(15HS0Dh^Cq=5z&mAZMpg#LtqmoIZ_E6=DW$v z1m5)|?OL8U0BdA^G-R@B#UsR@EgmEPbErcqsB7q10_=K{A$zo@o5RyRG!s~tv+MRR z2e&XxL{-TscFuk9rGGE_RAK>-IJ2-W51V*j+RCEL^|R`q7_6!p-ay07mygF&d_2i8 z+V;uvVRQAC-b~4VO8&Sxl?Y%`({5v0#x#{Nfgd&eQtoAbGeYi{>CR*{WrR~VHIduA zq{dUXH0^rkCG(u}5>xs4^Px?yOkqv=hbci%WmE!$Ih9yVW7YCzh!&G}O^4R~B-m446pzAuy8%ujlg)R}gn3HV_8D@g#QE`vA8B^f%InclfKY1(3CRX|v1 zYf%;pc3YFSNRq}FS5d1Ve@!t|S1|I^?#fSPX`A*b!8W3}i;4>@e=hE}vf`d^$TP_^ zb93i>=e*~A-*YZ_0RRu}=s+iYT!t=mBZtee5_$BXfFep*g#cxQh|r5ZtVTc9Yy-m~ zR;)C~iM3+Z4pB83Emy1L4J(>cMVjm{Yt~dPzjJ84oM(ToB)rH}Ry3yRfZ@4Sd{K;V zsOCY3zcGjJso~>s6;G%|NtI2hJ+I_rROdrx~W zRu=7@`2am7;2E6dV^t4=2qlGb4B6pIK8LZo1DO)m^q`-x*2lUK>6KXTVFBZV3Ln;J ztHMPwv^e-^9G4{ECsC|LDCK+&pBTp{`;hLyh5|N{O)298gJ^cJc_p%p)yn3Eo!At| z>LhNgLrLP7u{FX!dPoP@UPMa~133)#VuuGD!Y&`TIX0Bk*z9B2$4DQxy&q=~U#`N= zYJ4S*OcFybc84~;#FRq?x;tEad&VA`?nGk>IEn8O?n)sYp|OJhiURiLFp&ptx8^K{|;CQh2Z)_@>3M)L{!r*I+7XTX}~h+=WcHjekJnU>91uFe1YX zQW-xbJZ|tW4xUWmP-GF9=)_Su>j_}Q_WB$jCkl8jhZbXL@O+3P3eygb#c|xlOdU?n z;3mT8Aoe)8JC6M+{M^N<2Ad3ihL;2U!AFyaS9)>4!)v(;wuAVuN@pJM~b7+nU zjp>FQyc})v=u?CY)76S#=(91pK296!=yM^mA+~zh=+P|(%VTtFNLyU`QXO60i%h!} zNK1+a8Z42dN1|vE%S12TMmWdN?G8c6JWHP-+H7c>Lxa0%*tOP?jZ1Fy%`w})Ob30( zr#+VePtx~tbZ1C+mFdoa?hfgm4*EXPy*|4;GDC_SU-**G0d(DWFdn6Y?15n__93@1HI=4dvg8KPepI+dVb251zv z4BCnseJoM*8-s%}X$t+ji&7oEwA#XzPS}G)gDok6i(Vyq-Jv%uN;qV7LGQ%v4UGYv z7TJn>h~9_kOdlHK^rsa4c?@13RtX4$%&X8?U?avgECx;;GCK@cIUGxo7h!t@FV4x3 zjuE|J=(i5N6{q)7?6{nYFx<&vV(kIX_jy6mp6I!jb@DogxKZRlaw6o7w6#eXIBTyo zi_pYgLP7&C?c?)^7a4-XV)*G!ZWH&L<_z)Q7(UzKrZ}Iwn?LOG1;SF91p}DqLbgN_ zbAaU|dp%;#d`W_{N&a{(w@U)!Hi2w6=R@q3XoC3w_r$qije<)=h+FR+DsrEd8?P>l zpm?pwd63r$_#?J`?IB;8c)Y1;xQ^&W4Y&`yQUK+%k<>C$t z^%oNSr4cN0d7uo>rz3G5?nBn+(E@)}CW6CbktHE|c*OA6QvCHuv{o_gS?f-X6|YWT zhP2^39b}69EyH^pzN>)OEa&;&lNQIXqwFATu$c=DNz2 zqn|Se^sE2g@|RBP2rSIHCz`e{=K7L~DqR5YS`1LCq;(=QrV z>*OmtEBr4BC+V4FWr_*-gW?0~Gi7zP_daLTm87mQ>a!y7qzyIMt~Tbd#8I0>O2^cV zq58ZhY1@tNh*CE=){exf+FbRyI`xHdb(5G++4_?GdL3QYt2TPr5Yd)E?ef(Q#tEf% zYVxYoZMA|SZwb{lqqaNhD@D39kF7=Z^^zKA(F-A={>d7n#kNSQN4>Iz3)7gsl@ z)K^{g4GEi5_vW#rY{{81GQZS)r0zFrZ%#cRq8JjxQTrVnkb0$AvlY{r74KCK57_F~ zX(@JYfF)>~=%HB92g{Ya7Iw{F)w_B^^^CMD+j5=1Wjyq~LrCzk+ zMY_s%KO^>)uu7MixTJ`tK)qR1Z%Yi9raGYCWXziz=*()Rw9(? zxiAZNj9(B?J+DX0G+P;<& zJelKScjAOKytJs#C*5i=>1feHdX2u=$9mE$j94^n@pZPMbX)#tWr&czRN7j_k5^z> zA*-gVWpWATGEHVXY+IX)I#0S_bTNeq*}6R)$qg|f4>Du&FhbL0_bA(!lJnh z)NxFOm{^G^S@2}C&+1Pr{h5T_lQF8V3Pi=SCs;;Jh5CBZ8w~aqbsy=qMt{=L*QE5O zfEJdpHK)HA=q)Ada%oNTV=}D-5Pfs4z9oRHG}Aen_VtcX?<}i7kouF-RYqs3b)%!3 zYxJ_XzA&LbmeifQ^{QIkJ6GRWuWxP810T@4WaW^_e#EX5)|YDZ9dWu=v~UU&-TJN$ zWIOcT1$~dN@9oszCw-szp(s?`Rc7J{hGe2uS}CB(^IGN^{m)(c$MUcsoP1g)wX8|{ zVWX#tXzj$Ygnq1`_mTd=px*E3hf?|xS3e%v9bULCSlOLqgkB0{i0Eg1H8;>lq)*Kgw+;GteRw&qU(f3|eEnvi-)gsplga6~6Z(%8Zlue4S{Nm0%<^zq zpKY+&+vp|a4ZO>gNx)bK?v`=SJtQR-uipf;wg3O3! z``}>PXa7nckD6r(vpi+aciBXehS15(A>$geu-bgcF>NXHk-)AfEo3egmv|0)pEn;j Hrla`(UiT^o literal 0 HcmV?d00001 diff --git a/xwords4/dawg/English/Makefile b/xwords4/dawg/English/Makefile new file mode 100644 index 000000000..95b975643 --- /dev/null +++ b/xwords4/dawg/English/Makefile @@ -0,0 +1,42 @@ +# -*-mode: Makefile -*- +# Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +TARGET_TYPE ?= FRANK + +include ../Makefile.langcommon + +# This represents the default -- for now +COMMAND = -f Makefile.BasEnglish TARGET_TYPE=FRANK + +alleng: + for mfile in Makefile.BasEnglish Makefile.OSW Makefile.TWL98 Makefile.CollegeEng; do \ + $(MAKE) -f $$mfile TARGET_TYPE=$(TARGET_TYPE); \ + done + +%: + $(MAKE) $(COMMAND) $@ + +all: + $(MAKE) $(COMMAND) + +clean: + $(MAKE) $(COMMAND) clean + +help: + @echo "try make -f Makefile.[BasEnglish|CollegeEng] \\" + @echo " TARGET_TYPE=[PALM|FRANK]" + diff --git a/xwords4/dawg/English/Makefile.BasEnglish b/xwords4/dawg/English/Makefile.BasEnglish new file mode 100644 index 000000000..03019e7a2 --- /dev/null +++ b/xwords4/dawg/English/Makefile.BasEnglish @@ -0,0 +1,35 @@ +# -*-mode: Makefile; compile-command: "make -f Makefile.BasEnglish"; -*- +# Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +XWLANG=BasEnglish +LANGCODE=en_US +DICT2DAWGARGS = -r -nosort + +TARGET_TYPE ?= PALM + +include ../Makefile.2to8 + +include ../Makefile.langcommon + +$(XWLANG)Main.dict.gz: BasEnglish.dict.gz + ln -s $< $@ + +# Everything but creating of the Main.dict file is inherited from the +# "parent" Makefile.langcommon in the parent directory. + +clean: clean_common + rm -f $(XWLANG)Main.dict.gz *.bin $(XWLANG)*.pdb $(XWLANG)*.seb diff --git a/xwords4/dawg/English/Makefile.CSW b/xwords4/dawg/English/Makefile.CSW new file mode 100644 index 000000000..e1cda017e --- /dev/null +++ b/xwords4/dawg/English/Makefile.CSW @@ -0,0 +1,36 @@ +# -*- mode: makefile; compile-command: "make -f Makefile.COSD"; -*- +# Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +XWLANG=CO +LANGCODE=en_US +TARGET_TYPE=WINCE + +include ../Makefile.2to8 + +include ../Makefile.langcommon + +SOURCEDICT ?= $(XWDICTPATH)/English/COSD.dict.gz + +$(XWLANG)Main.dict.gz: $(SOURCEDICT) Makefile + zcat $< | tr -d '\r' | tr [a-z] [A-Z] | grep -e "^[A-Z]\{2,15\}$$" | \ + gzip -c > $@ + +# Everything but creating of the Main.dict file is inherited from the +# "parent" Makefile.langcommon in the parent directory. + +clean: clean_common + rm -f $(XWLANG)Main.dict.gz *.bin $(XWLANG)*.pdb $(XWLANG)*.seb diff --git a/xwords4/dawg/English/Makefile.Enable b/xwords4/dawg/English/Makefile.Enable new file mode 100644 index 000000000..937b66676 --- /dev/null +++ b/xwords4/dawg/English/Makefile.Enable @@ -0,0 +1,39 @@ +# -*- mode: makefile; -*- +# Copyright 2002-2004 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# +# I got enable1.txt from ftp://puzzlers.org/pub/wordlists/enable1.txt, +# then gzippped it. +# + +XWLANG=Enable +LANGCODE=en_US +TARGET_TYPE=FRANK + +include ../Makefile.2to8 + +include ../Makefile.langcommon + +$(XWLANG)Main.dict.gz: Enable1.txt.gz + zcat $< | tr -d '\r' | tr [a-z] [A-Z] | grep -e "^[A-Z]\{2,15\}$$" | \ + gzip -c > $@ + +# Everything but creating of the Main.dict file is inherited from the +# "parent" Makefile.langcommon in the parent directory. + +clean: clean_common + rm -f $(XWLANG)Main.dict.gz *.bin $(XWLANG)*.pdb $(XWLANG)*.seb diff --git a/xwords4/dawg/English/Makefile.OWL2 b/xwords4/dawg/English/Makefile.OWL2 new file mode 100644 index 000000000..41479a843 --- /dev/null +++ b/xwords4/dawg/English/Makefile.OWL2 @@ -0,0 +1,36 @@ +# -*- mode: makefile; compile-command: "make -f Makefile.OWL2"; -*- +# Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +XWLANG=OWL2_ +LANGCODE=en_US +TARGET_TYPE=PALM + +include ../Makefile.2to8 + +include ../Makefile.langcommon + +SOURCEDICT ?= $(XWDICTPATH)/English/OWL2.dict.gz + +$(XWLANG)Main.dict.gz: $(SOURCEDICT) Makefile + zcat $< | tr -d '\r' | tr [a-z] [A-Z] | grep -e "^[A-Z]\{2,15\}$$" | \ + gzip -c > $@ + +# Everything but creating of the Main.dict file is inherited from the +# "parent" Makefile.langcommon in the parent directory. + +clean: clean_common + rm -f $(XWLANG)Main.dict.gz *.bin $(XWLANG)*.pdb $(XWLANG)*.seb diff --git a/xwords4/dawg/English/Makefile.SOWPODS b/xwords4/dawg/English/Makefile.SOWPODS new file mode 100644 index 000000000..d12476f32 --- /dev/null +++ b/xwords4/dawg/English/Makefile.SOWPODS @@ -0,0 +1,36 @@ +# -*- mode: makefile; -*- +# Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +XWLANG=SOWPODS +LANGCODE=en_US +TARGET_TYPE=FRANK + +include ../Makefile.2to8 + +include ../Makefile.langcommon + +SOURCEDICT ?= $(XWDICTPATH)/English/sow-twl.txt.gz + +$(XWLANG)Main.dict.gz: $(SOURCEDICT) Makefile + zcat $< | tr -d '\r' | tr [a-z] [A-Z] | grep -e "^[A-Z]\{2,15\}$$" | \ + gzip -c > $@ + +# Everything but creating of the Main.dict file is inherited from the +# "parent" Makefile.langcommon in the parent directory. + +clean: clean_common + rm -f $(XWLANG)Main.dict.gz *.bin $(XWLANG)*.pdb $(XWLANG)*.seb diff --git a/xwords4/dawg/English/README.txt b/xwords4/dawg/English/README.txt new file mode 100644 index 000000000..be58b761e --- /dev/null +++ b/xwords4/dawg/English/README.txt @@ -0,0 +1,38 @@ +This file describes how to build dictionaries for the various versions +of Crosswords. + +Short version: + +For a Palm dictionary, type: + +# make -f Makefile.BasEnglish TARGET_TYPE=PALM + +which will create BasEnglish2to8.pdb. + +For a Franklin or Wince or Linux dictionary, type + +# make -f Makefile.BasEnglish TARGET_TYPE=FRANK + +which will create BasEnglish2to8.seb and BasEnglish2to8.xwd.saved. + +The .seb file is for the eBookman, and is just a wrapper around an +.xwd file. Unwrapped .xwd files are for Wince and Linux versions of +Crosswords. Remove the .saved from the end of the filename. It's +only there because I haven't figure out how to stop the build system +from deleting .xwd files after making .seb files out of them. + + + +English is unusual in having multiple dictionaries. In most language +directories there's only one, and so only one Makefile. So you skip +the -f option to make. + +The 2to8 part of the name is a convention meaning that only words from +2 to 8 letters long are included. 2to8 is the default, but you can +explicitly use a different target and the build system will adjust what +words are included. For example + +# make -f Makefile.BasEnglish TARGET_TYPE=FRANK BasEnglish2to5.xwd + +will produce an even smaller dictionary for Wince and Linux. + diff --git a/xwords4/dawg/English/info.txt b/xwords4/dawg/English/info.txt new file mode 100644 index 000000000..70f8d8a49 --- /dev/null +++ b/xwords4/dawg/English/info.txt @@ -0,0 +1,70 @@ +# Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +LANGCODE:en_US + +# deal with DOS files +LANGFILTER: tr -d '\r' +LANGFILTER: | tr [a-z] [A-Z] +LANGFILTER: | grep '^[A-Z]*$' +LANGFILTER: | sort -u + +# We can trust sort (above) to do the right thing since there's no +# high ascii. dict2dawg.pl is much faster if I can trust that its +# input is in sorted order. +D2DARGS: -nosort -term 10 + + +LANGINFO:

English dictionaries can contain words with any of the 26 +LANGINFO: letters you think of as making up the alphabet: A-Z. At +LANGINFO: this point any word in your list containing anything else +LANGINFO: will simply be excluded from the dictionary.

+ +# High bit means "official". Next 7 bits are an enum where +# English==1. Low byte is padding +XLOC_HEADER:0x8100 + + +2 0 {"_"} +9 1 'A' +2 3 'B' +2 3 'C' +4 2 'D' +12 1 'E' +2 4 'F' +3 2 'G' +2 4 'H' +9 1 'I' +1 8 'J' +1 5 'K' +4 1 'L' +2 3 'M' +6 1 'N' +8 1 'O' +2 3 'P' +1 10 'Q' +6 1 'R' +4 1 'S' +6 1 'T' +4 1 'U' +2 4 'V' +2 4 'W' +1 8 'X' +2 4 'Y' +1 10 'Z' + + +# should ignore all after the above diff --git a/xwords4/dawg/French/.cvsignore b/xwords4/dawg/French/.cvsignore new file mode 100644 index 000000000..8437bef5b --- /dev/null +++ b/xwords4/dawg/French/.cvsignore @@ -0,0 +1,3 @@ +*.bin +*.pdb +*.xwd diff --git a/xwords4/dawg/French/Makefile b/xwords4/dawg/French/Makefile new file mode 100644 index 000000000..e7ba219ae --- /dev/null +++ b/xwords4/dawg/French/Makefile @@ -0,0 +1,34 @@ +# -*-mode: Makefile -*- +# Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +XWLANG=French +LANGCODE=fr_FR + +TARGET_TYPE ?= FRANK + +include ../Makefile.2to8 + +include ../Makefile.langcommon + +$(XWLANG)Main.dict.gz: ods3.txt.gz + zcat $< | tr a-z A-Z | gzip >$@ + +# Everything but creating of the Main.dict file is inherited from the +# "parent" Makefile.langcommon in the parent directory. + +clean: clean_common + rm -f $(XWLANG)Main.dict.gz *.bin $(XWLANG)*.pdb $(XWLANG)*.seb diff --git a/xwords4/dawg/French/Makefile.ODS4 b/xwords4/dawg/French/Makefile.ODS4 new file mode 100644 index 000000000..2851350bf --- /dev/null +++ b/xwords4/dawg/French/Makefile.ODS4 @@ -0,0 +1,34 @@ +# -*-mode: Makefile -*- +# Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +XWLANG=ODS4_ +LANGCODE=fr_FR + +TARGET_TYPE ?= PALM + +include ../Makefile.2to8 + +include ../Makefile.langcommon + +$(XWLANG)Main.dict.gz: $(XWDICTPATH)/$(XWLANG)/ods4c.txt.gz + zcat $< | tr a-z A-Z | tr -d '\r' | gzip >$@ + +# Everything but creating of the Main.dict file is inherited from the +# "parent" Makefile.langcommon in the parent directory. + +clean: clean_common + rm -f $(XWLANG)Main.dict.gz *.bin $(XWLANG)*.pdb $(XWLANG)*.seb diff --git a/xwords4/dawg/French/info.txt b/xwords4/dawg/French/info.txt new file mode 100755 index 000000000..00c5bfc0f --- /dev/null +++ b/xwords4/dawg/French/info.txt @@ -0,0 +1,67 @@ +# Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +LANGCODE:fr_FR + +# deal with DOS files +LANGFILTER: tr -d '\r' + +LANGFILTER: | tr [a-z] [A-Z] +LANGFILTER: | grep '^[A-Z]*$' +LANGFILTER: | tr '\n' '\000' +LANGFILTER: | sort -u -z + +D2DARGS: -r -nosort -term 0 + +LANGINFO:

At this point French is getting treated the same as +LANGINFO: English. But I think I should be transforming accented +LANGINFO: vowels into their unaccented equivalents rather than +LANGINFO: dropping those words from the list prior to compression.

+ + +# High bit means "official". Next 7 bits are an enum where +# French==2. Low byte is padding +XLOC_HEADER:0x8200 + + +2 0 {"_"} +9 1 'A' +2 3 'B' +2 3 'C' +3 2 'D' +15 1 'E' +2 4 'F' +2 2 'G' +2 4 'H' +8 1 'I' +1 8 'J' +1 10 'K' +5 1 'L' +3 2 'M' +6 1 'N' +6 1 'O' +2 3 'P' +1 8 'Q' +6 1 'R' +6 1 'S' +6 1 'T' +6 1 'U' +2 4 'V' +1 10 'W' +1 10 'X' +1 10 'Y' +1 10 'Z' + diff --git a/xwords4/dawg/German/.cvsignore b/xwords4/dawg/German/.cvsignore new file mode 100644 index 000000000..086c16f6f --- /dev/null +++ b/xwords4/dawg/German/.cvsignore @@ -0,0 +1,3 @@ +*.bin +*.xwd +*.pdb diff --git a/xwords4/dawg/German/Makefile b/xwords4/dawg/German/Makefile new file mode 100644 index 000000000..6452bcfbd --- /dev/null +++ b/xwords4/dawg/German/Makefile @@ -0,0 +1,42 @@ +# -*- mode: makefile -*- +# Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +XWLANG=German +LANGCODE=de_DE + +TARGET_TYPE ?= PALM + +include ../Makefile.2to8 + +include ../Makefile.langcommon + +SOURCEDICT ?= $(XWDICTPATH)/$(XWLANG)/HansGerman.dict.gz + +$(XWLANG)Main.dict.gz: $(SOURCEDICT) Makefile + zcat $< | tr [a-zäöü] [A-ZÄÖÜ] | \ + sed -e 's/ß/SS/g' | \ + grep '[AEIOUÄÖÜ]' | grep '^[A-ZÄÖÜ]\+$$' | \ + gzip -c > $@ + +# Everything but creating of the Main.dict file is inherited from the +# "parent" Makefile.langcommon in the parent directory. + +clean: clean_common + rm -f $(XWLANG)Main.dict.gz *.bin $(XWLANG)*.pdb $(XWLANG)*.seb + +help: + @echo 'make [SOURCEDICT=HansGerman.dict.gz|deutsch.dict.gz]' diff --git a/xwords4/dawg/German/info.txt b/xwords4/dawg/German/info.txt new file mode 100644 index 000000000..f6321981d --- /dev/null +++ b/xwords4/dawg/German/info.txt @@ -0,0 +1,82 @@ +# Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +LANGCODE:de_DE + +# deal with DOS files +LANGFILTER: tr -d '\r' +# substitute for sharfes-s +LANGFILTER: | sed -e 's/ß/SS/g' +# uppercase all +LANGFILTER: | tr [a-zäöü] [A-ZÄÖÜ] +# no words not containing a vowel +LANGFILTER: | grep '[AEIOUÄÖÜ]' +# none with illegal chars +LANGFILTER: | grep '^[A-ZÄÖÜ]\+$' + +# Until I can figure out how to force sort to use a locale's collation +# rules we can't trust sort in the filtering rules above and so must +# leave the sorting work to dict2dawg.pl. +D2DARGS: -r -term 10 + +LANGINFO:

German has the 26 English letters plus the three umlaut +LANGINFO: vowels. Scharfes-s is not a legal tile, but if present in +LANGINFO: the wordlist submitted it'll be converted to "SS" by our +LANGINFO: filtering rules. Additional filtering rules eliminate all +LANGINFO: words that don't contain at least one vowel and any that +LANGINFO: contain letters not found on tiles.

+ +# High bit means "official". Next 7 bits are an enum where +# German==3. Low byte is padding +XLOC_HEADER:0x8300 + + + +2 0 {"_"} +5 1 'A' +# A mit umlaut +1 6 196 +2 3 'B' +2 4 'C' +4 1 'D' +15 1 'E' +2 4 'F' +3 2 'G' +4 2 'H' +6 1 'I' +1 6 'J' +2 4 'K' +3 2 'L' +4 3 'M' +9 1 'N' +3 2 'O' +# O mit umlaut +1 8 214 +1 4 'P' +1 10 'Q' +6 1 'R' +7 1 'S' +6 1 'T' +6 1 'U' +# U mit umlaut +1 6 220 +1 6 'V' +1 3 'W' +1 8 'X' +1 10 'Y' +1 3 'Z' + +# should ignore all after the above diff --git a/xwords4/dawg/Hex/.cvsignore b/xwords4/dawg/Hex/.cvsignore new file mode 100644 index 000000000..f64e05f34 --- /dev/null +++ b/xwords4/dawg/Hex/.cvsignore @@ -0,0 +1,4 @@ +*.bin +*.pdb +*.xwd +*.seb diff --git a/xwords4/dawg/Hex/Makefile b/xwords4/dawg/Hex/Makefile new file mode 100644 index 000000000..cf710fa59 --- /dev/null +++ b/xwords4/dawg/Hex/Makefile @@ -0,0 +1,41 @@ +# Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +XWLANG = Hex +LANGCODE = hex + +TARGET_TYPE = WINCE + +include ../Makefile.2to8 + +include ../Makefile.langcommon + +# Pass in your own dict here by setting DICT +DICT ?= $(XWDICTPATH)/English/SOWPODS_official.txt.gz + +# Feel free to base this on whatever dictionary you have at hand. I'm +# using CollegeEng for no particular reason. +$(XWLANG)Main.dict.gz: $(DICT) + @echo "building $@ from $<" + zcat $< | tr [a-f] [A-F] | grep -e '^[A-F]\{2,8\}$$' | \ + echo CAFEBABE DEADBEEF $$(cat -) | \ + tr ' ' '\n' | sort | gzip > $@ + +# Everything but creating of the Main.dict file is inherited from the +# "parent" Makefile.langcommon in the parent directory. + +clean: clean_common + rm -f $(XWLANG)Main.dict.gz *.bin $(XWLANG)*.pdb $(XWLANG)*.seb diff --git a/xwords4/dawg/Hex/info.txt b/xwords4/dawg/Hex/info.txt new file mode 100755 index 000000000..fcd4f6baf --- /dev/null +++ b/xwords4/dawg/Hex/info.txt @@ -0,0 +1,61 @@ +# Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +LANGCODE:HEX + + + +# uppercase all +LANGFILTER: tr [a-f] [A-F] +LANGFILTER: | grep '^[A-F]*$' +LANGFILTER: | sort -u + +D2DARGS: -nosort -term 10 + +LANGINFO:

The hex "language" is something of a programmers' joke. +LANGINFO: Hex is short for hexadecimal, a 16-base number system whose +LANGINFO: "digits" are the numerals 0-9 plus the letters A-F. Hex +LANGINFO: letters are often used to represent computer data, and +LANGINFO: certain sequences are sometimes used as markers because +LANGINFO: they're easy to pick out in large dumps of otherwise +LANGINFO: meaningless (to humans) garbage. In staring at Mac memory +LANGINFO: dumps, for example, you'd occasionally see the letters +LANGINFO: DEADBEEF and know that memory in that area was probably +LANGINFO: undamaged.

+ +LANGINFO:

I use Hex dictionaries for testing since they have few +LANGINFO: tiles and games play quickly. That's also why the Hex +LANGINFO: tile set has four blanks; that's the largest number +LANGINFO: Crosswords supports and I needed to test at the limit.

+ + + +# High bit means "official". Next 7 bits are an enum where Hex==127 +# (I just made that up; not sure what it was originally.) Low byte is +# padding +XLOC_HEADER:0xFF00 + + + +4 0 {"_"} +9 1 'A' +2 3 'B' +2 3 'C' +4 2 'D' +12 1 'E' +2 4 'F' + +# should ignore all after the above diff --git a/xwords4/dawg/Italian/.cvsignore b/xwords4/dawg/Italian/.cvsignore new file mode 100644 index 000000000..086c16f6f --- /dev/null +++ b/xwords4/dawg/Italian/.cvsignore @@ -0,0 +1,3 @@ +*.bin +*.xwd +*.pdb diff --git a/xwords4/dawg/Italian/Makefile b/xwords4/dawg/Italian/Makefile new file mode 100644 index 000000000..d1ff35cb2 --- /dev/null +++ b/xwords4/dawg/Italian/Makefile @@ -0,0 +1,34 @@ +# -*-mode: Makefile -*- +# Copyright 2002-2004 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +XWLANG=Italian +LANGCODE=it_IT + +TARGET_TYPE ?= PALM + +include ../Makefile.2to8 + +include ../Makefile.langcommon + +$(XWLANG)Main.dict.gz: $(XWDICTPATH)/$(XWLANG)/ITALIANO.txt.gz + zcat $< | tr a-z A-Z | grep '^[A-IL-VZ]*$$' | gzip >$@ + +# Everything but creating of the Main.dict file is inherited from the +# "parent" Makefile.langcommon in the parent directory. + +clean: clean_common + rm -f $(XWLANG)Main.dict.gz *.bin $(XWLANG)*.pdb $(XWLANG)*.seb diff --git a/xwords4/dawg/Italian/info.txt b/xwords4/dawg/Italian/info.txt new file mode 100755 index 000000000..8b60c6478 --- /dev/null +++ b/xwords4/dawg/Italian/info.txt @@ -0,0 +1,60 @@ +# Copyright 2002-2006 by Eric House (xwords@eehouse.org). All rights +# reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +LANGCODE:it_IT + +# deal with DOS files +LANGFILTER: tr -d '\r' +LANGFILTER: | tr [a-z] [A-Z] +LANGFILTER: | grep '^[A-IL-VZ]*$' +LANGFILTER: | sort -u + +D2DARGS: -r -term 10 -nosort + +LANGINFO:

Italian is treated the same as English but for +LANGINFO: missing letters J, K, W, X and Y.

+ + +# High bit means "official". Next 7 bits are an enum where +# Italian==0xA. Low byte is padding +XLOC_HEADER:0x8A00 + +# tile values taken from http://www.gtoal.com/wordgames/details/italian/ + +2 0 {"_"} +13 1 'A' +3 5 'B' +4 4 'C' +3 5 'D' +13 1 'E' +2 8 'F' +3 5 'G' +2 8 'H' +13 1 'I' +5 3 'L' +5 3 'M' +6 2 'N' +13 1 'O' +3 5 'P' +1 10 'Q' +6 2 'R' +6 2 'S' +6 2 'T' +5 3 'U' +4 4 'V' +2 8 'Z' + diff --git a/xwords4/dawg/Makefile b/xwords4/dawg/Makefile new file mode 100644 index 000000000..3b4533c57 --- /dev/null +++ b/xwords4/dawg/Makefile @@ -0,0 +1,39 @@ +# -*-mode: Makefile -*- +# +# Copyright 2007 by Eric House (xwords@eehouse.org) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +all: byodfiles.tgz + +byodfiles.tgz: byodfiles.tar + gzip -c $< > $@ + +byodfiles.tar: + rm -f $@ + tar cvf $@ ./dict2dawg.cpp ./par.pl ./xloc.pl ./xloc.pm + for dir in $$(ls .); do \ + if [ -f $$dir/info.txt ] && [ -f $$dir/Makefile ]; then \ + (cd $$dir; make TARGET_TYPE=PALM table.bin values.bin palmvalues.bin); \ + (cd $$dir; make TARGET_TYPE=FRANK frankspecials.bin); \ + tar rvf $@ $$dir/table.bin $$dir/values.bin $$dir/palmvalues.bin $$dir/frankspecials.bin $$dir/info.txt; \ + fi \ + done + +clean: + rm -f byodfiles.tgz byodfiles.tar dict2dawg + +dict2dawg: dict2dawg.cpp + $(CXX) $< -o $@ diff --git a/xwords4/dawg/Makefile.2to8 b/xwords4/dawg/Makefile.2to8 new file mode 100644 index 000000000..bb1777d27 --- /dev/null +++ b/xwords4/dawg/Makefile.2to8 @@ -0,0 +1,7 @@ +# -*-mode: Makefile -*- + +# These are the targets that almost all language makefiles will want. + +SHORT_WORD = 2 +LONG_WORD = 8 + diff --git a/xwords4/dawg/Makefile.langcommon b/xwords4/dawg/Makefile.langcommon new file mode 100644 index 000000000..0553d144e --- /dev/null +++ b/xwords4/dawg/Makefile.langcommon @@ -0,0 +1,285 @@ +# -*-mode: Makefile -*- + +# Copyright 2000-2002 by Eric House (xwords@eehouse.org) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +XWLANG := $(XWLANG)_ + +FRANK_EXT = xwd + +# This is now on for all dicts and languages. dict2dawg.pl can't +# produce the old form any longer. +NEWDAWG=foo + +# this will make all dicts the new, larger type +#FORCE_4 = -force4 + +PALM_DICT_TYPE = DAWG +PAR = ../par.pl + +LANGUAGE = $(shell basename $$(pwd)) + +# prefer the compiled version if available. But don't compile it +# automatically until it's a bit better tested. +# DICT2DAWG = $(if $(shell test -x ../dict2dawg && echo foo),\ +# ../dict2dawg,../dict2dawg.pl) + +DICT2DAWG = ../dict2dawg + +#all: target_all + +# let languages set this first, but we always add blank to it. +BLANK_INFO = "_" /dev/null /dev/null + +# Supply a default so don't have to type so much; feel free to change +TARGET_TYPE ?= FRANK + +ifdef NEWDAWG +TABLE_ARG = -mn +else +TABLE_ARG = -m +endif + +ifeq ($(TARGET_TYPE),WINCE) +WINCE_ONLY = true +endif + +.PHONY: clean_common help allbins checkARCH + + +############################################################################## +# PalmOS rules +############################################################################## +ifeq ($(TARGET_TYPE),PALM) + +ifdef NEWDAWG +PDBTYPE = XwrD +else +PDBTYPE = Xwr3 +endif + +all: $(XWLANG)2to8.pdb $(XWLANG)2to9.pdb $(XWLANG)2to15.pdb + +empty: $(XWLANG)0to0.pdb + +# Those languages that have bitmap files for custom glyphs will need to +# define BMPBINFILES and perhaps provide a rule for building the files +binfiles.stamp: $(BMPBINFILES) + touch binfiles.stamp + +palmspecials.bin: ../palm_mkspecials.pl $(BMPFILES) + $< $(BLANK_INFO) $(LANG_SPECIAL_INFO) > $@ + +# can't just use values.bin because the specials bitmap info is +# platform-specific +palmvalues.bin: values.bin palmspecials.bin + cat $^ > $@ + +# values.bin: palmspecials.bin ../xloc binfiles.stamp +# cd ../ && $(MAKE) xloc +# binfileparms=""; \ +# if [ "$(BMPBINFILES)" != "" ]; then \ +# for f in $(BMPBINFILES)""; \ +# do binfileparms="$$binfileparms -i $$f"; \ +# done; \ +# fi; \ +# ../xloc -l $(LANGCODE) $$binfileparms -T $@ +# cat palmspecials.bin >> $@ + +# header (first record) is node count (long) and 4 chars: +# unsigned char firstEdgeRecNum; +# unsigned char charTableRecNum; +# unsigned char valTableRecNum; +# unsigned char reserved[3]; // worst case this points to a new resource + +# include "flags" as used on the other platforms +palmheader%.bin: $(XWLANG)%_wordcount.bin $(XWLANG)%_flags.bin + rm -f $@ + touch $@ + cat $< >> $@ + perl -e "print pack(\"C\",3)" >> $@ # first edge + perl -e "print pack(\"C\",1)" >> $@ # char table rec number + perl -e "print pack(\"C\",2)" >> $@ # valTable rec number + perl -e "print pack(\"CCC\",0)" >> $@ # reserved 3 bytes +ifdef NEWDAWG + cat $(XWLANG)$*_flags.bin >> $@ +else + perl -e "print pack(\"CC\",0)" >> $@ # c code added two more... +endif + + +# This works, but leaves out the header info that the current version +# has. I'm not sure anybody cares, though... +$(XWLANG)%.pdb: dawg$(XWLANG)%.stamp table.bin palmvalues.bin palmheader%.bin + $(PAR) c -a backup $@ \ + $(basename $(@F)) $(PALM_DICT_TYPE) $(PDBTYPE) \ + palmheader$*.bin table.bin palmvalues.bin dawg$(XWLANG)$*_*.bin + +# the files to export for byod +byodbins: table.bin values.bin palmvalues.bin info.txt + +#endif # TARGET_TYPE==PALM + +############################################################################## +# Franklin ebook rules +############################################################################## +else +ifeq ($(TARGET_TYPE),FRANK) + +# If we're on a system that can build for Franklin, assume that's what +# we want to build (and the .xwd.saved [<-bug] file for other non-palm +# platforms is a by-product). But if the EBM tools aren't there, just +# build the .xwd file. +ifeq (x$(shell echo -n $$EBOOKMAN_SDK)x,xx) +all: $(XWLANG)2to8.xwd $(XWLANG)2to9.xwd $(XWLANG)2to15.xwd +empty: $(XWLANG)0to0.xwd +else +all: checkARCH $(XWLANG)2to8.seb +empty: $(XWLANG)0to0.seb +include ${EBOOKMAN_SDK}/ebsdk.uses +endif + +checkARCH: + if [[ $$ARCH == "" ]]; then \ + $(error "ARCH must be defined in ENV if TARGET_TYPE==FRANK"); \ + fi + +$(XWLANG)%.seb: $(XWLANG)%.$(FRANK_EXT) $(XWLANG)%.atts + ${ESDK_CREATESEB_EXE} $< + cp $< $<.saved + +$(XWLANG)%.atts: #recreate it each time based on params + echo '_PUB|global+read-only|"Eric_House"' >> $@ + echo "_NAME|global+read-only|\"$(XWLANG)2to8\"" >> $@ + echo "_EXT|global+read-only|\"$(FRANK_EXT)\"" >> $@ + echo '_LCAT|nosign+global|"CONTENT"' >> $@ + echo '_PERM|global+read-only|"r"' >> $@ + +# the files to export for byod +byodbins: table.bin values.bin frankspecials.bin info.txt + + +else +ifeq ($(TARGET_TYPE),WINCE) + +### WINCE section here ### +all: $(XWLANG)2to8.xwd $(XWLANG)2to9.xwd $(XWLANG)2to15.xwd + ../mkxwdcab.pl -f $< + +else + (Need to define TARGET_TYPE if get error pointing to this line) +endif #ifeq ($(TARGET_TYPE),FRANK) +endif +endif + +ifeq (s$(TARGET_TYPE),s) +echo "It\'s an error not to specify a TARGET_TYPE" +endif + +############################################################################## +# shared rules +############################################################################## + +# For each entry in the table whose face < 32, there needs to be a pair of +# pbitm files and a string giving the printing form +frankspecials.bin: ../frank_mkspecials.pl $(BMPFILES) + $< $(BLANK_INFO) $(LANG_SPECIAL_INFO) > $@ + +# a binary file (one byte) giving the number of tiles in the dict +charcount.bin: table.bin +ifdef NEWDAWG + siz=$$(ls -l $< | awk '{print $$5}'); \ + perl -e "print pack(\"c\",$$siz/2)" > $@ +else + siz=$$(wc -c $< | sed -e 's/$ $@ +endif + +$(XWLANG)%.$(FRANK_EXT): dawg$(XWLANG)%.stamp $(XWLANG)%_flags.bin charcount.bin table.bin values.bin frankspecials.bin + cat $(XWLANG)$*_flags.bin charcount.bin table.bin values.bin \ + frankspecials.bin $(XWLANG)StartLoc.bin \ + $$(ls dawg$(XWLANG)$*_*.bin) > $@ + cp $@ saveme.bin + + +# For some reason I can't fathom dawg$(XWLANG)% gets nuked every time +# the top-level rule fires (all: for whatever TARGET_TYPE.) It +# happens after the rule finishes.... + +# 16 bits worth of flags for the start of the eventual file. At this +# point, the flags mean this: +# 1: old-style DAWG. +# 2: new-style DAWG, three bytes per node. +# 3: new-style DAWG, four bytes per node +$(XWLANG)%_flags.bin: dawg$(XWLANG)%.stamp +ifdef NEWDAWG + if [ 3 = $$(cat $(XWLANG)$*_nodesize.bin) ] ; \ + then perl -e "print pack(\"n\",0x0002)" > $@; echo "flags=2"; \ + elif [ 4 = $$(cat $(XWLANG)$*_nodesize.bin) ] ; \ + then perl -e "print pack(\"n\",0x0003)" > $@; echo "flags=3"; \ + elif true; \ + then echo "Unexpected node size"; exit 1; \ + fi +else + if [ 3 == $$(cat $(XWLANG)$*_nodesize.bin) ] ; \ + then perl -e "print pack(\"n\",0x0001)" > $@; echo "flags=1"; \ + else echo "ERROR: old format can't handle 4-byte"; exit 1; \ + fi +endif + +dawg$(XWLANG)%.stamp: $(XWLANG)Main.dict.gz $(DICT2DAWG) table.bin ../Makefile.langcommon + start=$$(echo $@ | sed -e 's/dawg$(XWLANG)\([0-9]*\)to[0-9]*.stamp/\1/'); \ + end=$$(echo $@ | sed -e 's/dawg$(XWLANG)[0-9]*to\([0-9]*\).stamp/\1/'); \ + echo $${start} and $${end}; \ + zcat $< | $(DICT2DAWG) $(DICT2DAWGARGS) $(TABLE_ARG) table.bin -b 28000 \ + -ob dawg$(XWLANG)$* \ + -sn $(XWLANG)StartLoc.bin -min $${start} -max $${end} \ + -wc $(XWLANG)$*_wordcount.bin $(FORCE_4) -ns $(XWLANG)$*_nodesize.bin + touch $@ + +$(XWLANG)%_wordcount.bin: dawg$(XWLANG)%.stamp + @echo + +# the files to export for byod +allbins: + $(MAKE) TARGET_TYPE=PALM byodbins + $(MAKE) TARGET_TYPE=FRANK byodbins + rm palmspecials.bin + +table.bin: ../xloc.pl +ifdef NEWDAWG + perl -I../ ../xloc.pl -tn $@ +else + perl -I../ ../xloc.pl -t $@ +endif + +values.bin: ../xloc.pl + perl -I../ ../xloc.pl -v $@ + +%.dict: %.dict.gz + zcat $< > $@ + +# clean this up.... +../dict2dawg: ../dict2dawg.cpp + cd ../ && g++ -DDEBUG -O -o dict2dawg dict2dawg.cpp + +clean_common: + rm -f $(XWLANG)Main.dict *.bin *.pdb *.seb dawg*.stamp *.$(FRANK_EXT) \ + $(XWLANG)*.pdb $(XWLANG)*.seb + +help: + @echo "make TARGET_TYPE=[FRANK|PALM]" + diff --git a/xwords4/dawg/Polish/.cvsignore b/xwords4/dawg/Polish/.cvsignore new file mode 100644 index 000000000..6e88a9fb5 --- /dev/null +++ b/xwords4/dawg/Polish/.cvsignore @@ -0,0 +1,8 @@ +*.bin +*.pdb +*.saved +*.pdr +*.ehouse +*.seb +decompose +temp diff --git a/xwords4/dawg/Polish/Makefile b/xwords4/dawg/Polish/Makefile new file mode 100644 index 000000000..3cfd60d1f --- /dev/null +++ b/xwords4/dawg/Polish/Makefile @@ -0,0 +1,35 @@ +# -*-mode: Makefile -*- +# Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +XWLANG=Polish +LANGCODE=pl_PL + +TARGET_TYPE ?= FRANK + +include ../Makefile.2to8 + +include ../Makefile.langcommon + +$(XWLANG)Main.dict.gz: slowa.txt.gz + zcat $< | tr -d '\r' | tr [a-z±æê³ñ󶼿] [A-Z¡ÆÊ£ÑÓ¦¬¯] | \ + grep '^[A-PR-UWYZ¡ÆÊ£ÑÓ¦¬¯]*$$' | gzip >$@ + +# Everything but creating of the Main.dict file is inherited from the +# "parent" Makefile.langcommon in the parent directory. + +clean: clean_common + rm -f $(XWLANG)Main.dict.gz *.bin $(XWLANG)*.pdb $(XWLANG)*.seb diff --git a/xwords4/dawg/Polish/info.txt b/xwords4/dawg/Polish/info.txt new file mode 100644 index 000000000..ceb88c569 --- /dev/null +++ b/xwords4/dawg/Polish/info.txt @@ -0,0 +1,86 @@ +# Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +LANGCODE:pl_PL + +CHARSET:iso-8859-2 + +# deal with DOS files +LANGFILTER_PRECLIP: tr -d '\r' | + +LANGFILTER_POSTCLIP: | tr [a-z±æê³ñ󶼿] [A-Z¡ÆÊ£ÑÓ¦¬¯] +LANGFILTER_POSTCLIP: | grep '^[A-PR-UWYZ¡ÆÊ£ÑÓ¦¬¯]*$' +LANGFILTER_POSTCLIP: | tr '\n' '\000' + +NEEDSSORT:true + + +LANGINFO:

Polish is interesting because it has 32 letters plus a +LANGINFO: blank, a larger number than any other supported language. +LANGINFO: Yet while I call it "supported", in fact this combination +LANGINFO: has never been tested because I don't have a Polish +LANGINFO: wordlist. So if you are the first and have problems you've +LANGINFO: probably found a bug. Please let me know so that I can get +LANGINFO: this working.

+ +LANGINFO:

Note that the blank is the last tile here, while with all +LANGINFO: other languages it's the first.

+ +LANGINFO:

Also, please note that we currently require the files you +LANGINFO: upload to use the iso-8859-2 character encoding.

+ +# High bit means "official". Next 7 bits are an enum where +# Polish==8. Low byte is padding +XLOC_HEADER:0x8800 + + + +9 1 'A' +1 5 '¡' +2 3 'B' +3 2 'C' +1 6 'Æ' +3 2 'D' +7 1 'E' +1 5 'Ê' +1 5 'F' +2 3 'G' +2 3 'H' +8 1 'I' +2 3 'J' +3 2 'K' +3 2 'L' +2 3 '£' +3 2 'M' +5 1 'N' +1 7 'Ñ' +6 1 'O' +1 5 'Ó' +3 2 'P' +4 1 'R' +4 1 'S' +1 5 '¦' +3 2 'T' +2 3 'U' +4 1 'W' +4 2 'Y' +5 1 'Z' +1 9 '¬' +1 5 '¯' +# the blank *must* be last here!!! +2 0 {"_"} + diff --git a/xwords4/dawg/Portuguese/Makefile.BrOffice b/xwords4/dawg/Portuguese/Makefile.BrOffice new file mode 100644 index 000000000..b68361de4 --- /dev/null +++ b/xwords4/dawg/Portuguese/Makefile.BrOffice @@ -0,0 +1,42 @@ +# -*- mode: makefile -*- +# Copyright 2002, 2006 by Eric House (xwords@eehouse.org). All rights +# reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +LANG=PortugueseBR +LANGCODE=pt_PT + +TARGET_TYPE ?= PALM + +include ../Makefile.2to8 + +include ../Makefile.langcommon + +SOURCEDICT ?= $(XWDICTPATH)/Portuguese/portugueseBR.txt.gz + +$(LANG)Main.dict.gz: $(SOURCEDICT) Makefile.BrOffice + zcat $< | tr [a-zç] [A-ZÇ] | \ + grep '^[A-JL-VXZÇ]\+$$' | \ + gzip -c > $@ + +# Everything but creating of the Main.dict file is inherited from the +# "parent" Makefile.langcommon in the parent directory. + +clean: clean_common + rm -f $(LANG)Main.dict.gz *.bin $(LANG)*.pdb $(LANG)*.seb + +help: + @echo "make [SOURCEDICT=$(XWDICTPATH)/Portuguese/portugueseBR.txt]" diff --git a/xwords4/dawg/Portuguese/Makefile.Minho b/xwords4/dawg/Portuguese/Makefile.Minho new file mode 100644 index 000000000..f4aef74f8 --- /dev/null +++ b/xwords4/dawg/Portuguese/Makefile.Minho @@ -0,0 +1,42 @@ +# -*- mode: makefile -*- +# Copyright 2002, 2006 by Eric House (xwords@eehouse.org). All rights +# reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +XWLANG=PortuguesePT +LANGCODE=pt_PT + +TARGET_TYPE ?= PALM + +include ../Makefile.2to8 + +include ../Makefile.langcommon + +SOURCEDICT ?= $(XWDICTPATH)/Portuguese/portuguese_pt.bz2 + +$(XWLANG)Main.dict.gz: $(SOURCEDICT) Makefile.Minho + bzcat $< | tr [a-zç] [A-ZÇ] | \ + grep '^[A-JL-VXZÇ]\+$$' | \ + gzip -c > $@ + +# Everything but creating of the Main.dict file is inherited from the +# "parent" Makefile.langcommon in the parent directory. + +clean: clean_common + rm -f $(XWLANG)Main.dict.gz *.bin $(XWLANG)*.pdb $(XWLANG)*.seb + +help: + @echo "make [SOURCEDICT=$(XWDICTPATH)/Portuguese/portuguese_pt.bz2]" diff --git a/xwords4/dawg/Portuguese/info.txt b/xwords4/dawg/Portuguese/info.txt new file mode 100644 index 000000000..1c52afbb5 --- /dev/null +++ b/xwords4/dawg/Portuguese/info.txt @@ -0,0 +1,70 @@ +# Copyright 2006 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +LANGCODE:pt_PT + +# deal with DOS files +LANGFILTER: tr -d '\r' +# uppercase all +LANGFILTER: | tr [a-zç] [A-ZÇ] +# no words not containing a vowel +LANGFILTER: | grep '[AEIOU]' +# none with illegal chars +LANGFILTER: | grep '^[A-JL-VXZÇ]\+$' + +# Until I can figure out how to force sort to use a locale's collation +# rules we can't trust sort in the filtering rules above and so must +# leave the sorting work to dict2dawg.pl. +D2DARGS: -r -term 10 + + +LANGINFO:

Portugese uses the letter A-Z, excluding K, W and Y, and adds +LANGINFO: Ç. Words containing any other letters are dropped.

+ +# High bit means "official". Next 7 bits are an enum where +# Portuguese==D. Low byte is padding +XLOC_HEADER:0x8D00 + + + +3 0 {"_"} +14 1 'A' +3 3 'B' +4 2 'C' +2 3 'Ç' +5 2 'D' +11 1 'E' +2 4 'F' +2 4 'G' +2 4 'H' +10 1 'I' +2 5 'J' +5 2 'L' +6 1 'M' +4 3 'N' +10 1 'O' +4 2 'P' +1 6 'Q' +6 1 'R' +8 1 'S' +5 1 'T' +7 1 'U' +2 4 'V' +1 8 'X' +1 8 'Z' + + +# should ignore all after the above diff --git a/xwords4/dawg/Russian/Makefile b/xwords4/dawg/Russian/Makefile new file mode 100644 index 000000000..e2d5ee127 --- /dev/null +++ b/xwords4/dawg/Russian/Makefile @@ -0,0 +1,41 @@ +# -*- mode: makefile -*- +# Copyright 2002-2007 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +XWLANG=Russian +LANGCODE=ru_RU +DICT2DAWGARGS = -r + +TARGET_TYPE ?= WINCE + +include ../Makefile.2to8 + +include ../Makefile.langcommon + +SOURCEDICT ?= $(XWDICTPATH)/$(XWLANG)/RU5000.txt.gz + +$(XWLANG)Main.dict.gz: $(SOURCEDICT) Makefile + zcat $< | tr -d '\r' | \ + tr [àáâãäåæçèéêëìíîïðñòóôõö÷øùÚûüýþÿ] [ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß] | \ + gzip -c > $@ + + +# Everything but creating of the Main.dict file is inherited from the +# "parent" Makefile.langcommon in the parent directory. + +clean: clean_common + rm -f $(XWLANG)Main.dict.gz *.bin $(XWLANG)*.pdb $(XWLANG)*.seb + diff --git a/xwords4/dawg/Russian/info.txt b/xwords4/dawg/Russian/info.txt new file mode 100644 index 000000000..912f508f4 --- /dev/null +++ b/xwords4/dawg/Russian/info.txt @@ -0,0 +1,76 @@ +# Copyright 2002,2007 by Eric House (xwords@eehouse.org). All rights +# reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +LANGCODE:ru_RU +CHARSET:windows-1251 + +# deal with DOS files +LANGFILTER: tr -d '\r' +# uppercase all +LANGFILTER: | tr [àáâãäåæçèéêëìíîïðñòóôõö÷øùÚûüýþÿ] [ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß] +# LANGFILTER: | tr -s '\n' '\000' + +# note: don't turn off sorting! Can't do it with GNU 'sort' without +# setting LANG +D2DARGS: -r -term 10 + +LANGINFO:

Russian wordlists must be in the Windows-1251 +LANGINFO: codepage. Lower-case letters are converted to upper case and +LANGINFO: any words that contain letters not listed below are +LANGINFO: removed.

+ +# High bit means "official". Next 7 bits are an enum where +# Russian==0x0F. Low byte is padding. +XLOC_HEADER:0x8F00 + + + +8 1 'À' +2 3 'Á' +4 1 'Â' +2 3 'Ã' +2 2 'Ä' +7 1 'Å' +1 4 'Æ' +1 3 'Ç' +7 1 'È' +1 2 'É' +4 2 'Ê' +4 2 'Ë' +2 3 'Ì' +4 1 'Í' +9 1 'Î' +4 2 'Ï' +5 1 'Ð' +5 1 'Ñ' +7 1 'Ò' +4 2 'Ó' +1 5 'Ô' +1 4 'Õ' +1 4 'Ö' +1 3 '×' +1 4 'Ø' +1 5 'Ù' +1 10 'Ú' +2 2 'Û' +4 1 'Ü' +1 8 'Ý' +1 5 'Þ' +2 2 'ß' +2 0 {"_"} + +# should ignore all after the above diff --git a/xwords4/dawg/Spanish/.cvsignore b/xwords4/dawg/Spanish/.cvsignore new file mode 100644 index 000000000..5048aa9a8 --- /dev/null +++ b/xwords4/dawg/Spanish/.cvsignore @@ -0,0 +1,7 @@ +*.bin +*.xwd +*.pdb +*.saved +*.pdr +*.ehouse +*.seb diff --git a/xwords4/dawg/Spanish/Makefile b/xwords4/dawg/Spanish/Makefile new file mode 100644 index 000000000..f3f170fad --- /dev/null +++ b/xwords4/dawg/Spanish/Makefile @@ -0,0 +1,61 @@ +# -*-mode: Makefile; compile-command: "make all"; -*- +# Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +XWLANG=SpanishFAA41 +LANGCODE=es_ES +TARGET_TYPE ?= PALM + +ifeq ($(TARGET_TYPE),PALM) +PBITMS = ./bmps/palm +else +ifeq ($(TARGET_TYPE),FRANK) +PBITMS = ./bmps/franklin +else +ifeq ($(TARGET_TYPE),WINCE) +PBITMS = ./bmps/franklin +endif +endif +endif + +LANG_SPECIAL_INFO = \ + "CH" $(PBITMS)/large_ch.pbitm $(PBITMS)/small_ch.pbitm \ + "LL" $(PBITMS)/large_ll.pbitm $(PBITMS)/small_ll.pbitm \ + "RR" $(PBITMS)/large_rr.pbitm $(PBITMS)/small_rr.pbitm \ + +include ../Makefile.2to8 + +include ../Makefile.langcommon + +#$(LANG)Main.dict.gz: SpanishMain.dict.gz +# ln -s $< $@ + +SOURCEDICT ?= $(XWDICTPATH)/Spanish/FAA_4.1.txt.gz + +$(XWLANG)Main.dict.gz: $(SOURCEDICT) Makefile + zcat $< \ + | tr -d '\r' \ + | tr '\207\216\222\227\234\237\226' 'aeiouu\321' \ + | tr [a-zñ] [A-ZÑ] \ + | grep '^[[A-JL-VX-ZÑ]*$$' \ + | sed 's/CH/1/g' \ + | sed 's/LL/2/g' \ + | sed 's/RR/3/g' \ + | tr '123' '\001\002\003' \ + | gzip - > $@ + +clean: clean_common + rm -rf *.saved \ No newline at end of file diff --git a/xwords4/dawg/Spanish/bmps/franklin/large_ch.pbitm b/xwords4/dawg/Spanish/bmps/franklin/large_ch.pbitm new file mode 100644 index 000000000..04f16c6c4 --- /dev/null +++ b/xwords4/dawg/Spanish/bmps/franklin/large_ch.pbitm @@ -0,0 +1,11 @@ +-###--##--## +##--#-##--## +##----##--## +##----##--## +##----##--## +##----###### +##----##--## +##----##--## +##----##--## +##--#-##--## +-###--##--## \ No newline at end of file diff --git a/xwords4/dawg/Spanish/bmps/franklin/large_ll.pbitm b/xwords4/dawg/Spanish/bmps/franklin/large_ll.pbitm new file mode 100644 index 000000000..abf929daa --- /dev/null +++ b/xwords4/dawg/Spanish/bmps/franklin/large_ll.pbitm @@ -0,0 +1,11 @@ +##-----##--- +##-----##--- +##-----##--- +##-----##--- +##-----##--- +##-----##--- +##-----##--- +##-----##--- +##-----##--- +##-----##--- +#####--##### \ No newline at end of file diff --git a/xwords4/dawg/Spanish/bmps/franklin/large_rr.pbitm b/xwords4/dawg/Spanish/bmps/franklin/large_rr.pbitm new file mode 100644 index 000000000..7ac5a9c7e --- /dev/null +++ b/xwords4/dawg/Spanish/bmps/franklin/large_rr.pbitm @@ -0,0 +1,11 @@ +####--####- +#---#-#---# +#---#-#---# +#---#-#---# +#---#-#---# +####--####- +####--####- +#-#---#--#- +#--#--#--#- +#--#--#---# +#---#-#---# diff --git a/xwords4/dawg/Spanish/bmps/franklin/small_ch.pbitm b/xwords4/dawg/Spanish/bmps/franklin/small_ch.pbitm new file mode 100644 index 000000000..1b2f46ab7 --- /dev/null +++ b/xwords4/dawg/Spanish/bmps/franklin/small_ch.pbitm @@ -0,0 +1,9 @@ +-##--#--# +#--#-#--# +#----#--# +#----#--# +#----#### +#----#--# +#----#--# +#--#-#--# +-##--#--# \ No newline at end of file diff --git a/xwords4/dawg/Spanish/bmps/franklin/small_ll.pbitm b/xwords4/dawg/Spanish/bmps/franklin/small_ll.pbitm new file mode 100644 index 000000000..8bf595ba2 --- /dev/null +++ b/xwords4/dawg/Spanish/bmps/franklin/small_ll.pbitm @@ -0,0 +1,9 @@ +#----#--- +#----#--- +#----#--- +#----#--- +#----#--- +#----#--- +#----#--- +#----#--- +####-#### \ No newline at end of file diff --git a/xwords4/dawg/Spanish/bmps/franklin/small_rr.pbitm b/xwords4/dawg/Spanish/bmps/franklin/small_rr.pbitm new file mode 100644 index 000000000..7a9c3950a --- /dev/null +++ b/xwords4/dawg/Spanish/bmps/franklin/small_rr.pbitm @@ -0,0 +1,9 @@ +####-###- +#---##--# +#---##--# +#---##--# +####-###- +#-#--#-#- +#--#-#-#- +#--#-#--# +#---##--# diff --git a/xwords4/dawg/Spanish/bmps/palm/large_ch.pbitm b/xwords4/dawg/Spanish/bmps/palm/large_ch.pbitm new file mode 100644 index 000000000..f90335d8c --- /dev/null +++ b/xwords4/dawg/Spanish/bmps/palm/large_ch.pbitm @@ -0,0 +1,9 @@ +-###--#--# +#---#-#--# +#-----#--# +#-----#--# +#-----#### +#-----#--# +#-----#--# +#---#-#--# +-###--#--# \ No newline at end of file diff --git a/xwords4/dawg/Spanish/bmps/palm/large_ll.pbitm b/xwords4/dawg/Spanish/bmps/palm/large_ll.pbitm new file mode 100644 index 000000000..8bf595ba2 --- /dev/null +++ b/xwords4/dawg/Spanish/bmps/palm/large_ll.pbitm @@ -0,0 +1,9 @@ +#----#--- +#----#--- +#----#--- +#----#--- +#----#--- +#----#--- +#----#--- +#----#--- +####-#### \ No newline at end of file diff --git a/xwords4/dawg/Spanish/bmps/palm/large_rr.pbitm b/xwords4/dawg/Spanish/bmps/palm/large_rr.pbitm new file mode 100644 index 000000000..8ba1630af --- /dev/null +++ b/xwords4/dawg/Spanish/bmps/palm/large_rr.pbitm @@ -0,0 +1,9 @@ +####-####- +#---##---# +#---##---# +#---##---# +####-####- +#-#--#--#- +#--#-#--#- +#--#-#---# +#---##---# diff --git a/xwords4/dawg/Spanish/bmps/palm/small_ch.pbitm b/xwords4/dawg/Spanish/bmps/palm/small_ch.pbitm new file mode 100644 index 000000000..401659a33 --- /dev/null +++ b/xwords4/dawg/Spanish/bmps/palm/small_ch.pbitm @@ -0,0 +1,7 @@ +-#--#-# +#-#-#-# +#---#-# +#---### +#---#-# +#-#-#-# +-#--#-# diff --git a/xwords4/dawg/Spanish/bmps/palm/small_ll.pbitm b/xwords4/dawg/Spanish/bmps/palm/small_ll.pbitm new file mode 100644 index 000000000..92f490947 --- /dev/null +++ b/xwords4/dawg/Spanish/bmps/palm/small_ll.pbitm @@ -0,0 +1,7 @@ +#---#-- +#---#-- +#---#-- +#---#-- +#---#-- +#---#-- +###-### \ No newline at end of file diff --git a/xwords4/dawg/Spanish/bmps/palm/small_rr.pbitm b/xwords4/dawg/Spanish/bmps/palm/small_rr.pbitm new file mode 100644 index 000000000..e652825e5 --- /dev/null +++ b/xwords4/dawg/Spanish/bmps/palm/small_rr.pbitm @@ -0,0 +1,7 @@ +###-##- +#--#--# +#--#--# +###-##- +#-#--#- +#--#--# +#--#--# diff --git a/xwords4/dawg/Spanish/info.txt b/xwords4/dawg/Spanish/info.txt new file mode 100644 index 000000000..f11f81318 --- /dev/null +++ b/xwords4/dawg/Spanish/info.txt @@ -0,0 +1,110 @@ +# -*- mode: conf; -*- +# Copyright 2002-2006 by Eric House (xwords@eehouse.org). All rights +# reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# no way can unix sort handle the control chars I'm adding to text +# below + +NEEDSSORT:true + +# MSDos LF chars go bye-bye +LANGFILTER: tr -d '\r' + +# convert accented vowels +LANGFILTER: | tr '\207\216\222\227\234\237\226' 'aeiouu\321' +# uppercase +LANGFILTER: | tr [a-zñ] [A-ZÑ] +# remove words with illegal letters +LANGFILTER: | grep '^[[A-JL-VX-ZÑ]*$' +# substitute pairs (can't figure out how to use octal values) +LANGFILTER: | sed 's/CH/1/g' +LANGFILTER: | sed 's/LL/2/g' +LANGFILTER: | sed 's/RR/3/g' +# substitute in the octal control character values +LANGFILTER: | tr '123' '\001\002\003' +# now add nulls as terminators +LANGFILTER: | tr -s '\n' '\000' +LANGFILTER: | sort -u -z + +D2DARGS: -r -term 0 + +LANGINFO:

Spanish words include all letters in the English alphabet +LANGINFO: except "K" and "W", and with "Ñ" added. Since there are no +LANGINFO: tiles for accented vowels, these are replaced by the +LANGINFO: unaccented forms.

+ + +LANGINFO:

In addition, there are three special two-letter tiles +LANGINFO: "CH", "LL" and "RR". The rules say that the corresponding +LANGINFO: two single tiles may not be used where a two-letter tile is +LANGINFO: possible (e.g. if a word contains "CH" you must use the "CH" +LANGINFO: tile rather than a "C" tile followed by an "H" tile. Thus +LANGINFO: we remove all of these pairs from your wordlist and replace +LANGINFO: them with the appropriate two-letter "letter".

+ + +LANGCODE:es_ES + +# I think dealing with "specials" goes like this. In the {} pairs +# below, if the first string is followed by other strings (one or two) +# they are assumed to be filenames. The filenames will need to be +# found, and converted into binary files appropriate for the platform +# by rules given somewhere -- here? No, since they're the same for +# all platforms. Just put 'em in the byod.cgi file for now. + +# It'll be assumed that the first name is for the "small" bitmap, and +# the second for the "large". It's ok for a file not to exist; it'll +# just be ignored. In the unlikely case that you wanted to specify +# the large but not the small this is what you'd need to do. + +# High bit means "official". Next 7 bits are an enum where +# Spanish==6. Low byte is padding +XLOC_HEADER:0x8600 + + +2 0 {"_"} +12 1 'A' +2 3 'B' +4 3 'C' +1 5 {"CH",true,true} +5 2 'D' +12 1 'E' +1 4 'F' +2 2 'G' +2 4 'H' +6 1 'I' +1 8 'J' +4 1 'L' +1 8 {"LL", true, true} +2 3 'M' +5 1 'N' +# /*'N~'*/ +1 8 209 +9 1 'O' +2 3 'P' +1 5 'Q' +5 1 'R' +1 8 {"RR",true,true} +6 1 'S' +4 1 'T' +5 1 'U' +1 4 'V' +1 8 'X' +1 4 'Y' +1 10 'Z' + +# should ignore all after the above diff --git a/xwords4/dawg/Swedish/.cvsignore b/xwords4/dawg/Swedish/.cvsignore new file mode 100644 index 000000000..f64e05f34 --- /dev/null +++ b/xwords4/dawg/Swedish/.cvsignore @@ -0,0 +1,4 @@ +*.bin +*.pdb +*.xwd +*.seb diff --git a/xwords4/dawg/Swedish/Makefile b/xwords4/dawg/Swedish/Makefile new file mode 100644 index 000000000..fadbfdc68 --- /dev/null +++ b/xwords4/dawg/Swedish/Makefile @@ -0,0 +1,45 @@ +# -*-mode: Makefile -*- +# Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +XWLANG=Swedish +LANGCODE=sv_SE + +# Swedish has too many chars for the old format. +NEWDAWG=whatever + +TARGET_TYPE ?= FRANK + +include ../Makefile.2to8 + +include ../Makefile.langcommon + +SOURCEDICT ?= $(XWDICTPATH)/$(XWLANG)/swedish15.dict.gz + +# Q and W are not available as tiles, but I'm told there's a custom in +# Swedish play of allowing blanks to stand for those letters as well. +# So we don't exclude words with those letters from the dictionary. +$(XWLANG)Main.dict.gz: $(SOURCEDICT) Makefile + zcat $< | tr [a-zäåæöü] [A-ZÄÅÆÖÜ] | \ + grep '^[A-ZÄÅÆÖÜ]\{2,15\}$$' | \ + gzip -c > $@ + +# Everything but creating of the Main.dict file is inherited from the +# "parent" Makefile.langcommon in the parent directory. + +clean: clean_common + rm -f $(XWLANG)Main.dict.gz *.bin $(XWLANG)*.pdb $(XWLANG)*.seb + diff --git a/xwords4/dawg/Swedish/info.txt b/xwords4/dawg/Swedish/info.txt new file mode 100644 index 000000000..161cbc060 --- /dev/null +++ b/xwords4/dawg/Swedish/info.txt @@ -0,0 +1,75 @@ +# Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +LANGCODE:sv_SE + +LANGFILTER: tr -d '\r' +LANGFILTER: | tr [a-zäåæöü] [A-ZÄÅÆÖÜ] +LANGFILTER: | grep '^[A-ZÄÅÆÖÜ]*$' + +D2DARGS: -r -term 10 + +LANGINFO:

From an English-speaker's perspective, Swedish drops Q +LANGINFO: and W, and adds Ä, Å, Æ, Ö and Ü.

+ +# High bit means "official". Next 7 bits are an enum where +# Swedish==7. Low byte is padding +XLOC_HEADER:0x8700 + + + + +2 0 {"_"} +8 1 'A' +# A with two dots +2 3 'Ä' +# A with circle +2 4 'Å' +# Æ tile only available for blanks +0 1 'Æ' +2 4 'B' +1 8 'C' +5 1 'D' +7 1 'E' +2 3 'F' +3 2 'G' +2 2 'H' +5 1 'I' +1 7 'J' +3 2 'K' +5 1 'L' +3 2 'M' +6 1 'N' +5 2 'O' +# O with two dots +2 4 'Ö' +2 4 'P' +# Q tile only available for blanks +0 1 'Q' +8 1 'R' +8 1 'S' +8 1 'T' +3 4 'U' +# Ü tile only available for blanks +0 1 'Ü' +2 3 'V' +# W tile only available for blanks +0 1 'W' +1 8 'X' +1 7 'Y' +1 10 'Z' + + diff --git a/xwords4/dawg/allchars.pl b/xwords4/dawg/allchars.pl new file mode 100755 index 000000000..361376ef8 --- /dev/null +++ b/xwords4/dawg/allchars.pl @@ -0,0 +1,10 @@ +#!/usr/bin/perl + +# Print all ascii characters (for pasting into Makefiles etc. when you +# don't know what key combination produces them) + +use strict; + +for ( my $i = int(' '); $i <= 255; ++$i ) { + printf "%.3d: %c\n", $i, $i; +} diff --git a/xwords4/dawg/dawg2dict.pl b/xwords4/dawg/dawg2dict.pl new file mode 100755 index 000000000..d6526d113 --- /dev/null +++ b/xwords4/dawg/dawg2dict.pl @@ -0,0 +1,368 @@ +#!/usr/bin/perl +# +# Copyright 2004 by Eric House (xwords@eehouse.org) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +# Given a .pdb or .xwd file, print all the words in the DAWG to +# stdout. + +use strict; +use Fcntl; + +my $gInFile; +my $gDoRaw = 0; +my $gFileType; +my $gNodeSize; + +sub usage() { + print STDERR "USAGE: $0 " + . "[-raw] " + . "-dict " + . "\n" + . "\t(Takes a .pdb or .xwd and prints its words to stdout)\n"; + exit 1; +} + +sub parseARGV() { + + while ( my $parm = shift(@ARGV) ) { + if ( $parm eq "-raw" ) { + $gDoRaw = 1; + } elsif ( $parm eq "-dict" ) { + $gInFile = shift(@ARGV); + } else { + usage(); + } + } + + if ( $gInFile =~ m|.xwd$| ) { + $gFileType = "xwd"; + } elsif ( $gInFile =~ m|.pdb$| ) { + $gFileType = "pdb"; + } else { + usage(); + } +} # parseARGV + +sub countSpecials($) { + my ( $facesRef ) = @_; + my $count = 0; + + map { ++$count if ( ord($_) < 32 ); } @$facesRef; + return $count; +} # countSpecials + +sub readXWDFaces($$$) { + my ( $fh, $facRef, $nSpecials ) = @_; + + my $buf; + my $nRead = sysread( $fh, $buf, 1 ); + my $nChars = unpack( 'c', $buf ); + + my @faces; + for ( my $i = 0; $i < $nChars; ++$i ) { + my $nRead = sysread( $fh, $buf, 2 ); + push( @faces, chr(unpack( "n", $buf ) ) ); + } + + ${$nSpecials} = countSpecials( \@faces ); + @{$facRef} = @faces; + return $nChars; +} # readXWDFaces + +sub skipBitmap($) { + my ( $fh ) = @_; + my $buf; + sysread( $fh, $buf, 1 ); + my $nCols = unpack( 'C', $buf ); + if ( $nCols > 0 ) { + sysread( $fh, $buf, 1 ); + my $nRows = unpack( 'C', $buf ); + my $nBytes = (($nRows * $nCols) + 7) / 8; + + sysread( $fh, $buf, $nBytes ); + } +} # skipBitmap + +sub getSpecials($$$) { + my ( $fh, $nSpecials, $specRef ) = @_; + + my @specials; + for ( my $i = 0; $i < $nSpecials; ++$i ) { + my $buf; + sysread( $fh, $buf, 1 ); + my $len = unpack( 'C', $buf ); + sysread( $fh, $buf, $len ); + push( @specials, $buf ); + skipBitmap( $fh ); + skipBitmap( $fh ); + } + + @{$specRef} = @specials; +} # getSpecials + +sub readNodesToEnd($) { + my ( $fh ) = @_; + my @nodes; + my $count = 0; + my $offset = 4 - $gNodeSize; + my ( $buf, $nRead ); + + do { + $nRead = sysread( $fh, $buf, $gNodeSize, $offset ); + $count += $nRead; + my $node = unpack( 'N', $buf ); + push( @nodes, $node ); + } while ( $nRead == $gNodeSize ); + die "out of sync? nRead=$nRead, count=$count" if $nRead != 0; + + return @nodes; +} # readNodesToEnd + +sub nodeSizeFromFlags($) { + my ( $flags ) = @_; + if ( $flags == 2 ) { + return 3; + } elsif ( $flags == 3 ) { + return 4; + } else { + die "invalid dict flags $flags"; + } +} # nodeSizeFromFlags + +sub mergeSpecials($$) { + my ( $facesRef, $specialsRef ) = @_; + for ( my $i = 0; $i < @$facesRef; ++$i ) { + my $ref = ord($$facesRef[$i]); + if ( $ref < 32 ) { + $$facesRef[$i] = $$specialsRef[$ref]; + #print STDERR "set $ref to $$specialsRef[$ref]\n"; + } + } +} + +sub prepXWD($$$$) { + my ( $fh, $facRef, $nodesRef, $startRef ) = @_; + + my $buf; + my $nRead = sysread( $fh, $buf, 2 ); + my $flags = unpack( "n", $buf ); + + $gNodeSize = nodeSizeFromFlags( $flags ); + + my $nSpecials; + my $faceCount = readXWDFaces( $fh, $facRef, \$nSpecials ); + + # skip xloc header + $nRead = sysread( $fh, $buf, 2 ); + + # skip values info. + sysread( $fh, $buf, $faceCount * 2 ); + + my @specials; + getSpecials( $fh, $nSpecials, \@specials ); + mergeSpecials( $facRef, \@specials ); + + sysread( $fh, $buf, 4 ); + $$startRef = unpack( 'N', $buf ); + + my @nodes = readNodesToEnd( $fh ); + + @$nodesRef = @nodes; +} # prepXWD + +sub readPDBSpecials($$$$$) { + my ( $fh, $nChars, $nToRead, $nSpecials, $specRef ) = @_; + + my ( $nRead, $buf ); + + # first skip counts and values, and xloc header + $nRead += sysread( $fh, $buf, ($nChars * 2) + 2 ); + + while ( $nSpecials-- ) { + $nRead += sysread( $fh, $buf, 8 ); # sizeof(Xloc_specialEntry) + my @chars = unpack( 'C8', $buf ); + my $str; + foreach my $char (@chars) { + if ( $char == 0 ) { # null-terminated on palm + last; + } + $str .= chr($char); + } + push( @$specRef, $str ); + } + + $nRead += sysread( $fh, $buf, $nToRead - $nRead ); # skip bitmaps + + return $nRead; +} # readPDBSpecials + +sub prepPDB($$$$) { + my ( $fh, $facRef, $nodesRef, $startRef ) = @_; + + $$startRef = 0; # always for palm? + + my $buf; + # skip header info + my $nRead = sysread( $fh, $buf, 76 ); + $nRead += sysread( $fh, $buf, 2 ); + my $nRecs = unpack( 'n', $buf ); + + my @offsets; + for ( my $i = 0; $i < $nRecs; ++$i ) { + $nRead += sysread( $fh, $buf, 4 ); + push( @offsets, unpack( 'N', $buf ) ); + $nRead += sysread( $fh, $buf, 4 ); # skip + } + + die "too far" if $nRead > $offsets[0]; + while ( $nRead < $offsets[0] ) { + $nRead += sysread( $fh, $buf, 1 ); + } + + my $facesOffset = $offsets[1]; + my $nChars = ($offsets[2] - $facesOffset) / 2; + $nRead += sysread( $fh, $buf, $facesOffset - $nRead ); + my @tmp = unpack( 'Nc6n', $buf ); + $gNodeSize = nodeSizeFromFlags( $tmp[7] ); + + my @faces; + for ( my $i = 0; $i < $nChars; ++$i ) { + $nRead += sysread( $fh, $buf, 2 ); + push( @faces, chr(unpack( "n", $buf ) ) ); + } + @{$facRef} = @faces; + + die "out of sync: $nRead != $offsets[2]" if $nRead != $offsets[2]; + + my @specials; + $nRead += readPDBSpecials( $fh, $nChars, $offsets[3] - $nRead, + countSpecials($facRef), \@specials ); + mergeSpecials( $facRef, \@specials ); + + die "out of sync" if $nRead != $offsets[3]; + my @nodes = readNodesToEnd( $fh ); + + @$nodesRef = @nodes; +} # prepPDB + +sub parseNode($$$$$) { + my ( $node, $chrIndex, $nextEdge, $accepting, $last ) = @_; + + if ( $gNodeSize == 4 ) { + $$accepting = ($node & 0x00008000) != 0; + $$last = ($node & 0x00004000) != 0; + $$chrIndex = ($node & 0x00003f00) >> 8; + $$nextEdge = ($node >> 16) + (($node & 0x000000FF) << 16); + } elsif( $gNodeSize == 3 ) { + $$accepting = ($node & 0x00000080) != 0; + $$last = ($node & 0x00000040) != 0; + $$chrIndex = $node & 0x0000001f; + $$nextEdge = ($node >> 8) + (($node & 0x00000020) << 11); + } + + # printf "%x: acpt=$$accepting; last=$$last; " + # . "next=$$nextEdge; ci=$$chrIndex\n", $node; +} # parseNode + +sub printStr($$) { + my ( $strRef, $facesRef ) = @_; + + print join( "", map {$$facesRef[$_]} @$strRef), "\n"; +} # printStr + +# Given an array of 4-byte nodes, a start index. and another array of +# two-byte faces, print out all of the words in the nodes array. +sub printDAWG($$$$) { + my ( $strRef, $arrRef, $start, $facesRef ) = @_; + + die "infinite recursion???" if @$strRef > 15; + + for ( ; ; ) { + my $node = $$arrRef[$start++]; + my $nextEdge; + my $chrIndex; + my $accepting; + my $lastEdge; + + parseNode( $node, \$chrIndex, \$nextEdge, \$accepting, \$lastEdge ); + + push( @$strRef, $chrIndex ); + if ( $accepting ) { + printStr( $strRef, $facesRef ); + } + + if ( $nextEdge != 0 ) { + printDAWG( $strRef, $arrRef, $nextEdge, $facesRef ); + } + + pop( @$strRef ); + + if ( $lastEdge ) { + last; + } + } +} # printDAWG + +sub printNodes($$) { + my ( $nr, $fr ) = @_; + + my $len = @$nr; + for ( my $i = 0; $i < $len; ++$i ) { + my $node = $$nr[$i]; + + my ( $chrIndex, $nextEdge, $accepting, $lastEdge ); + parseNode( $node, \$chrIndex, \$nextEdge, \$accepting, \$lastEdge ); + + printf "%.8x: (%.8x) %2d(%s) %.8x ", $i, $node, $chrIndex, + $$fr[$chrIndex], $nextEdge; + print ($accepting? "A":"a"); + print " "; + print ($lastEdge? "L":"l"); + print "\n"; + } +} + +################################################################# +# main +################################################################# + + +parseARGV(); + +sysopen(INFILE, $gInFile, O_RDONLY) or die "couldn't open $gInFile: $!\n";; +binmode INFILE; + +my @faces; +my @nodes; +my $startIndex; + +if ( $gFileType eq "xwd" ){ + prepXWD( *INFILE, \@faces, \@nodes, \$startIndex ); +} elsif ( $gFileType eq "pdb" ) { + prepPDB( *INFILE, \@faces, \@nodes, \$startIndex ); +} +close INFILE; + +die "no nodes!!!" if 0 == @nodes; +if ( $gDoRaw ) { + printNodes( \@nodes, \@faces ); +} else { + printDAWG( [], \@nodes, $startIndex, \@faces ); +} + +exit 0; diff --git a/xwords4/dawg/dict2dawg.cpp b/xwords4/dawg/dict2dawg.cpp new file mode 100644 index 000000000..22f310570 --- /dev/null +++ b/xwords4/dawg/dict2dawg.cpp @@ -0,0 +1,1184 @@ +/* -*- compile-command: "g++ -DDEBUG -O -o dict2dawg dict2dawg.cpp"; -*- */ +/************************************************************************* + * adapted from perl code that was itself adapted from C++ code + * Copyright (C) 2000 Falk Hueffner + + * This version Copyright (C) 2002,2006-2007 Eric House (xwords@eehouse.org) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA +************************************************************************** + * inputs: 0. Name of file mapping letters to 0..31 values. In English + * case just contains A..Z. This will be used to translate the tries + * on output. + * 1. Max number of bytes per binary output file. + * + * 2. Basename of binary files for output. + + * 3. Name of file to which to write the number of the + * startNode, since I'm not rewriting a bunch of code to expect Falk's + * '*' node at the start. + * + + * In STDIN, the text file to be compressed. It absolutely + * must be sorted. The sort doesn't have to follow the order in the + * map file, however. + + * This is meant eventually to be runnable as part of a cgi system for + * letting users generate Crosswords dicts online. +**************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +typedef unsigned int Node; +typedef std::vector NodeList; +typedef std::vector WordList; + +#define VERSION_STR "$Rev$" + +#define MAX_WORD_LEN 15 +#define T2ABUFLEN(s) (((s)*4)+3) + +int gFirstDiff; + +static char gCurrentWordBuf[MAX_WORD_LEN+1] = { '\0' }; + // this will never change for non-sort case +static char* gCurrentWord = gCurrentWordBuf; +static int gCurrentWordLen; + +char* gCurWord = NULL; // save so can check for sortedness +bool gDone = false; +static int gNextWordIndex; +static void (*gReadWordProc)(void) = NULL; +NodeList gNodes; // final array of nodes +unsigned int gNBytesPerOutfile = 0xFFFFFFFF; +char* gTableFile = NULL; +char* gOutFileBase = NULL; +char* gStartNodeOut = NULL; +static FILE* gInFile = NULL; +bool gKillIfMissing = true; +char gTermChar = '\n'; +bool gDumpText = false; // dump the dict as text after? +char* gCountFile = NULL; +char* gBytesPerNodeFile = NULL; // where to write whether node + // size 3 or 4 +int gWordCount = 0; +std::map gTableHash; +int gBlankIndex; +std::vector gRevMap; +#ifdef DEBUG +bool gDebug = false; +#endif +std::map gSubsHash; +bool gForceFour = false; // use four bytes regardless of need? +static int gFileSize = 0; +int gNBytesPerNode; +bool gUseUnicode; +int gLimLow = 2; +int gLimHigh = MAX_WORD_LEN; + + +// OWL is 1.7M +#define MAX_POOL_SIZE (10 * 0x100000) +#define ERROR_EXIT(...) error_exit( __LINE__, __VA_ARGS__ ); + +static char* parseARGV( int argc, char** argv, const char** inFileName ); +static void usage( const char* name ); +static void error_exit( int line, const char* fmt, ... ); +static char parsechar( const char* in ); +static void makeTableHash( void ); +static WordList* parseAndSort( FILE* file ); +static void printWords( WordList* strings ); +static bool firstBeforeSecond( const char* lhs, const char* rhs ); +static char* tileToAscii( char* out, int outSize, const char* in ); +static int buildNode( int depth ); +static void TrieNodeSetIsLastSibling( Node* nodeR, bool isLastSibling ); +static int addNodes( NodeList& newedgesR ); +static void TrieNodeSetIsTerminal( Node* nodeR, bool isTerminal ); +static bool TrieNodeGetIsTerminal( Node node ); +static void TrieNodeSetIsLastSibling( Node* nodeR, bool isLastSibling ); +static bool TrieNodeGetIsLastSibling( Node node ); +static void TrieNodeSetLetter( Node* nodeR, int letter ); +static int TrieNodeGetLetter( Node node ); +static void TrieNodeSetFirstChildOffset( Node* nodeR, int fco ); +static int TrieNodeGetFirstChildOffset( Node node ); +static int findSubArray( NodeList& newedgesR ); +static void registerSubArray( NodeList& edgesR, int nodeLoc ); +static Node MakeTrieNode( int letter, bool isTerminal, int firstChildOffset, + bool isLastSibling ); +static void printNodes( NodeList& nodesR ); +static void printNode( int index, Node node ); +static void moveTopToFront( int* firstRef ); +static void writeOutStartNode( const char* startNodeOut, + int firstRootChildOffset ); +static void emitNodes( unsigned int nBytesPerOutfile, const char* outFileBase ); +static void outputNode( Node node, int nBytes, FILE* outfile ); +static void printOneLevel( int index, char* str, int curlen ); +static void readFromSortedArray( void ); + +int +main( int argc, char** argv ) +{ + gReadWordProc = readFromSortedArray; + + const char* inFileName; + if ( NULL == parseARGV( argc, argv, &inFileName ) ) { + usage(argv[0]); + exit(1); + } + + makeTableHash(); + + // Do I need this stupid thing? Better to move the first row to + // the front of the array and patch everything else. Or fix the + // non-palm dictionary format to include the offset of the first + // node. + + Node dummyNode = (Node)0xFFFFFFFF; + assert( sizeof(Node) == 4 ); + gNodes.push_back(dummyNode); + + if ( NULL == inFileName ) { + gInFile = stdin; + } else { + gInFile = fopen( inFileName, "r" ); + } + + (*gReadWordProc)(); + + int firstRootChildOffset = buildNode(0); + moveTopToFront( &firstRootChildOffset ); + + if ( gStartNodeOut ) { + writeOutStartNode( gStartNodeOut, firstRootChildOffset ); + } + +#ifdef DEBUG + if ( gDebug ) { + fprintf( stderr, "\n... dumping table ...\n" ); + printNodes( gNodes ); + } +#endif + // write out the number of nodes if requested + if ( gCountFile ) { + FILE* OFILE; + OFILE = fopen( gCountFile, "w" ); + unsigned long be = htonl( gWordCount ); + fwrite( &be, sizeof(be), 1, OFILE ); + fclose( OFILE ); + fprintf( stderr, "Wrote %d (word count) to %s\n", gWordCount, + gCountFile ); + } + + if ( gOutFileBase ) { + emitNodes( gNBytesPerOutfile, gOutFileBase ); + } + + if ( gDumpText && gNodes.size() > 0 ) { + char buf[(MAX_WORD_LEN*2)+1]; + printOneLevel( firstRootChildOffset, buf, 0 ); + } + + if ( gBytesPerNodeFile ) { + FILE* OFILE = fopen( gBytesPerNodeFile, "w" ); + fprintf( OFILE, "%d", gNBytesPerNode ); + fclose( OFILE ); + } + fprintf( stderr, "Used %d per node.\n", gNBytesPerNode ); + + if ( NULL != inFileName ) { + fclose( gInFile ); + } + +} /* main */ + +// We now have an array of nodes with the last subarray being the +// logical top of the tree. Move them to the start, fixing all fco +// refs, so that legacy code like Palm can assume top==0. +// +// Note: It'd probably be a bit faster to integrate this with emitNodes +// -- unless I need to have an in-memory list that can be used for +// lookups. But that's best for debugging, so keep it this way for now. +// +// Also Note: the first node is a dummy that can and should be tossed +// now. + +static void +moveTopToFront( int* firstRef ) +{ + int firstChild = *firstRef; + *firstRef = 0; + + NodeList lastSub; + + if ( firstChild > 0 ) { + lastSub.assign( gNodes.begin() + firstChild, gNodes.end() ); + gNodes.erase( gNodes.begin() + firstChild, gNodes.end() ); + } else if ( gWordCount != 0 ) { + ERROR_EXIT( "there should be no words!!" ); + } + + // remove the first (garbage) node + gNodes.erase( gNodes.begin() ); + + int diff; + if ( firstChild > 0 ) { + // -1 because all move down by 1; see prev line + diff = lastSub.size() - 1; + if ( diff < 0 ) { + ERROR_EXIT( "something wrong with lastSub.size()" ); + } + } else { + diff = 0; + } + + // stick it on the front + gNodes.insert( gNodes.begin(), lastSub.begin(), lastSub.end() ); + + // We add diff to everything. There's no subtracting because + // nobody had any refs to the top list. + + for ( int i = 0; i < gNodes.size(); ++i ) { + int fco = TrieNodeGetFirstChildOffset( gNodes[i] ); + if ( fco != 0 ) { // 0 means NONE, not 0th!! + TrieNodeSetFirstChildOffset( &gNodes[i], fco + diff ); + } + } +} // moveTopToFront + +static int +buildNode( int depth ) +{ + if ( gCurrentWordLen == depth ) { + // End of word reached. If the next word isn't a continuation + // of the current one, then we've reached the bottom of the + // recursion tree. + (*gReadWordProc)(); + if (gFirstDiff < depth || gDone) { + return 0; + } + } + + NodeList newedges; + + bool wordEnd; + do { + char letter = gCurrentWord[depth]; + bool isTerminal = (gCurrentWordLen - 1) == depth; + + int nodeOffset = buildNode( depth + 1 ); + Node newNode = MakeTrieNode( letter, isTerminal, nodeOffset, false ); + + wordEnd = (gFirstDiff != depth) || gDone; + if ( wordEnd ) { + TrieNodeSetIsLastSibling( &newNode, true ); + } + + newedges.push_back( newNode ); + } while ( !wordEnd ); + + return addNodes( newedges ); +} // buildNode + +static int +addNodes( NodeList& newedgesR ) +{ + int found = findSubArray( newedgesR ); + + if ( found == 0 ) { + ERROR_EXIT( "0 is an invalid match!!!" ); + } + + if ( found < 0 ) { + found = gNodes.size(); +#if defined DEBUG && defined SEVERE_DEBUG + if ( gDebug ) { + fprintf( stderr, "adding...\n" ); + printNodes( newedgesR ); + } +#endif + gNodes.insert( gNodes.end(), newedgesR.begin(), newedgesR.end() ); + + registerSubArray( newedgesR, found ); + } +#ifdef DEBUG + if ( gDebug ) { + fprintf( stderr, "%s => %d\n", __func__, found ); + } +#endif + return found; +} // addNodes + +static void +printNode( int index, Node node ) +{ + int letter = TrieNodeGetLetter(node); + assert( letter < gRevMap.size() ); + fprintf( stderr, + "[%d] letter=%d(%c); isTerminal=%s; isLastSib=%s; fco=%d;\n", + index, letter, gRevMap[letter], + TrieNodeGetIsTerminal(node)?"true":"false", + TrieNodeGetIsLastSibling(node)?"true":"false", + TrieNodeGetFirstChildOffset(node)); +} // printNode + +static void +printNodes( NodeList& nodesR ) +{ + for ( int i = 0; i < nodesR.size(); ++i ) { + Node node = nodesR[i]; + printNode( i, node ); + } +} + +// Hashing. We'll keep a hash of offsets into the existing nodes +// array, and as the key use a string that represents the entire sub +// array. Since the key is what we're matching for, there should never +// be more than one value per hash and so we don't need buckets. +// Return -1 if there's no match. + +static int +findSubArray( NodeList& newedgesR ) +{ + std::map::iterator iter = gSubsHash.find( newedgesR ); + if ( iter != gSubsHash.end() ) { + return iter->second; + } else { + return -1; + } +} // findSubArray + +// add to the hash +static void +registerSubArray( NodeList& edgesR, int nodeLoc ) +{ +#ifdef DEBUG + std::map::iterator iter = gSubsHash.find( edgesR ); + if ( iter != gSubsHash.end() ) { + ERROR_EXIT( "entry for key shouldn't exist!!" ); + } +#endif + gSubsHash[edgesR] = nodeLoc; +} // registerSubArray + +static void +readFromSortedArray( void ) +{ + // The first time we need a new word, we read 'em all in. + static WordList* sInputStrings = NULL; // we'll just let this leak + + if ( sInputStrings == NULL ) { + sInputStrings = parseAndSort( gInFile ); + gNextWordIndex = 0; + +#ifdef DEBUG + if ( gDebug ) { + printWords( sInputStrings ); + } +#endif + } + + for ( ; ; ) { + char* word = (char*)""; + + if ( !gDone ) { + gDone = gNextWordIndex == sInputStrings->size(); + if ( !gDone ) { + word = sInputStrings->at(gNextWordIndex++); +#ifdef DEBUG + } else if ( gDebug ) { + fprintf( stderr, "gDone set to true\n" ); +#endif + } +#ifdef DEBUG + if ( gDebug ) { + char buf[T2ABUFLEN(MAX_WORD_LEN)]; + fprintf( stderr, "%s: got word: %s\n", __func__, + tileToAscii( buf, sizeof(buf), word ) ); + } +#endif + } + int numCommonLetters = 0; + int len = strlen( word ); + if ( gCurrentWordLen < len ) { + len = gCurrentWordLen; + } + + while ( gCurrentWord[numCommonLetters] == word[numCommonLetters] + && numCommonLetters < len ) { + ++numCommonLetters; + } + + gFirstDiff = numCommonLetters; + if ( (gCurrentWordLen > 0) && (strlen(word) > 0) + && !firstBeforeSecond( gCurrentWord, word ) ) { +#ifdef DEBUG + if ( gDebug ) { + char buf1[T2ABUFLEN(MAX_WORD_LEN)]; + char buf2[T2ABUFLEN(MAX_WORD_LEN)]; + fprintf( stderr, + "%s: words %s and %s are the same or out of order\n", + __func__, + tileToAscii( buf1, sizeof(buf1), gCurrentWord ), + tileToAscii( buf2, sizeof(buf2), word ) ); + } +#endif + continue; + } + + gCurrentWord = word; + gCurrentWordLen = strlen(word); + break; + } + +#ifdef DEBUG + if ( gDebug ) { + char buf[T2ABUFLEN(MAX_WORD_LEN)]; + fprintf( stderr, "gCurrentWord now %s\n", + tileToAscii( buf, sizeof(buf), gCurrentWord) ); + } +#endif +} // readFromSortedArray + +static char* +readOneWord( char* wordBuf, int bufLen, int* lenp, bool* gotEOF ) +{ + char* result = NULL; + int count = 0; + bool dropWord = false; + bool done = false; + + // for each byte, append to an internal buffer up to size limit. + // On reaching an end-of-word or EOF, check if the word formed is + // within the length range and contains no unknown chars. If yes, + // return it. If no, start over ONLY IF the terminator was not + // EOF. + for ( ; ; ) { + int byt = getc( gInFile ); + + // EOF is special: we don't try for another word even if + // dropWord is true; we must leave now. + if ( byt == EOF || byt == gTermChar ) { + bool isEOF = byt == EOF; + *gotEOF = isEOF; + + assert( isEOF || count < bufLen || dropWord ); + if ( !dropWord && (count >= gLimLow) && (count <= gLimHigh) ) { + assert( count < bufLen ); + wordBuf[count] = '\0'; + result = wordBuf; + *lenp = count; + ++gWordCount; + break; + } else if ( isEOF ) { + assert( !result ); + break; + } +#ifdef DEBUG + if ( gDebug ) { + char buf[T2ABUFLEN(count)]; + wordBuf[count] = '\0'; + fprintf( stderr, "%s: dropping word (len>=%d): %s\n", __func__, + count, tileToAscii( buf, sizeof(buf), wordBuf ) ); + } +#endif + count = 0; // we'll start over + dropWord = false; + + } else if ( count >= bufLen ) { + // Just drop it... + dropWord = true; + + // Don't call into the hashtable twice here!! + } else if ( gTableHash.find(byt) != gTableHash.end() ) { + assert( count < bufLen ); + wordBuf[count++] = (char)gTableHash[byt]; + if ( count >= bufLen ) { + dropWord = true; + } + } else if ( gKillIfMissing || !dropWord ) { + char buf[T2ABUFLEN(count)]; + wordBuf[count] = '\0'; + + tileToAscii( buf, sizeof(buf), wordBuf ); + + if ( gKillIfMissing ) { + ERROR_EXIT( "chr %c (%d) not in map file %s\n" + "last word was %s\n", + (char)byt, (int)byt, gTableFile, buf ); + } else if ( !dropWord ) { +#ifdef DEBUG + if ( gDebug ) { + fprintf( stderr, "%s: chr %c (%d) not in map file %s\n" + "dropping partial word %s\n", __func__, + (char)byt, (int)byt, gTableFile, buf ); + } +#endif + dropWord = true; + } + } + } + +// if ( NULL != result ) { +// char buf[MAX_WORD_LEN+1]; +// fprintf( stderr, "%s returning %s\n", __func__, +// tileToAscii( buf, sizeof(buf), result ) ); +// } + return result; +} // readOneWord + +static void +readFromFile( void ) +{ + char wordBuf[MAX_WORD_LEN+1]; + static bool s_eof = false; + char* word; + int len; + + gDone = s_eof; + + // Repeat until we get a new word that's not "out-of-order". When + // we see this the problem isn't failure to sort, it's duplicates. + // So dropping is ok. The alternative would be detecting dupes + // during the sort. This seems easier. + for ( ; ; ) { + if ( !gDone ) { + word = readOneWord( wordBuf, sizeof(wordBuf), &len, &s_eof ); + gDone = NULL == word; + } + if ( gDone ) { + word = (char*)""; + len = 0; + } + + int numCommonLetters = 0; + if ( gCurrentWordLen < len ) { + len = gCurrentWordLen; + } + + while ( gCurrentWord[numCommonLetters] == word[numCommonLetters] + && numCommonLetters < len ) { + ++numCommonLetters; + } + + gFirstDiff = numCommonLetters; + if ( (gCurrentWordLen > 0) && (strlen(word) > 0) + && !firstBeforeSecond( gCurrentWord, word ) ) { +#ifdef DEBUG + if ( gDebug ) { + char buf1[T2ABUFLEN(MAX_WORD_LEN)]; + char buf2[T2ABUFLEN(MAX_WORD_LEN)]; + fprintf( stderr, + "%s: words %s and %s are the smae or out of order\n", + __func__, + tileToAscii( buf1, sizeof(buf1), gCurrentWord ), + tileToAscii( buf2, sizeof(buf2), word ) ); + } +#endif + continue; + } + break; + } + gCurrentWordLen = strlen(word); + strncpy( gCurrentWordBuf, word, sizeof(gCurrentWordBuf) ); + +#ifdef DEBUG + if ( gDebug ) { + char buf[T2ABUFLEN(MAX_WORD_LEN)]; + fprintf( stderr, "gCurrentWord now %s\n", + tileToAscii( buf, sizeof(buf), gCurrentWord) ); + } +#endif +} // readFromFile + +static bool +firstBeforeSecond( const char* lhs, const char* rhs ) +{ + bool gt = 0 > strcmp( lhs, rhs ); + return gt; +} + +static char* +tileToAscii( char* out, int outSize, const char* in ) +{ + char tiles[outSize]; + int tilesLen = 1; + tiles[0] = '['; + + char* orig = out; + for ( ; ; ) { + char ch = *in++; + if ( '\0' == ch ) { + break; + } + assert( ch < gRevMap.size() ); + *out++ = gRevMap[ch]; + tilesLen += sprintf( &tiles[tilesLen], "%d,", ch ); + assert( (out - orig) < outSize ); + } + + tiles[tilesLen] = ']'; + tiles[tilesLen+1] = '\0'; + strcpy( out, tiles ); + + return orig; +} + +static WordList* +parseAndSort( FILE* infile ) +{ + WordList* wordlist = new WordList; + + // allocate storage for the actual chars. wordlist's char* + // elements will point into this. It'll leak. So what. + + int memleft = gFileSize; + if ( memleft == 0 ) { + memleft = MAX_POOL_SIZE; + } + char* str = (char*)malloc( memleft ); + if ( NULL == str ) { + ERROR_EXIT( "can't allocate main string storage" ); + } + + bool eof = false; + for ( ; ; ) { + int len; + char buf[MAX_WORD_LEN+1]; + char* word = readOneWord( str, memleft, &len, &eof ); + + if ( NULL == word ) { + break; + } + + wordlist->push_back( str ); + ++len; // include null byte + str += len; + memleft -= len; + + if ( eof ) { + break; + } + if ( memleft < 0 ) { + ERROR_EXIT( "no memory left\n" ); + } + } + + if ( gWordCount > 1 ) { +#ifdef DEBUG + if ( gDebug ) { + fprintf( stderr, "starting sort...\n" ); + } +#endif + std::sort( wordlist->begin(), wordlist->end(), firstBeforeSecond ); +#ifdef DEBUG + if ( gDebug ) { + fprintf( stderr, "sort finished\n" ); + } +#endif + } + return wordlist; +} // parseAndSort + +static void +printWords( std::vector* strings ) +{ + std::vector::iterator iter = strings->begin(); + while ( iter != strings->end() ) { + char buf[T2ABUFLEN(MAX_WORD_LEN)]; + tileToAscii( buf, sizeof(buf), *iter ); + fprintf( stderr, "%s\n", buf ); + ++iter; + } +} + +/***************************************************************************** + * Little node-field setters and getters to hide what bits represent + * what. + + * high bit (31) is ACCEPTING bit + * next bit (30) is LAST_SIBLING bit + * next 6 bits (29-24) are tile bit (allowing alphabets of 64 letters) + * final 24 bits (23-0) are the index of the first child (fco) +******************************************************************************/ + +static void +TrieNodeSetIsTerminal( Node* nodeR, bool isTerminal ) +{ + if ( isTerminal ) { + *nodeR |= (1 << 31); + } else { + *nodeR &= ~(1 << 31); + } +} + +static bool +TrieNodeGetIsTerminal( Node node ) +{ + return (node & (1 << 31)) != 0; +} + +static void +TrieNodeSetIsLastSibling( Node* nodeR, bool isLastSibling ) +{ + if ( isLastSibling ) { + *nodeR |= (1 << 30); + } else { + *nodeR &= ~(1 << 30); + } +} + +static bool +TrieNodeGetIsLastSibling( Node node ) +{ + return (node & (1 << 30)) != 0; +} + +static void +TrieNodeSetLetter( Node* nodeR, int letter ) +{ + if( letter >= 64 ) { + ERROR_EXIT( "letter %d too big", letter ); + } + + int mask = ~(0x3F << 24); + *nodeR &= mask; // clear all the bits + *nodeR |= (letter << 24); // set new ones +} + +static int +TrieNodeGetLetter( Node node ) +{ + node >>= 24; + node &= 0x3F; // is 3f ok for 3-byte case??? + return node; +} + +static void +TrieNodeSetFirstChildOffset( Node* nodeR, int fco ) +{ + if ( (fco & 0xFF000000) != 0 ) { + ERROR_EXIT( "%x larger than 24 bits", fco ); + } + + int mask = ~0x00FFFFFF; + *nodeR &= mask; // clear all the bits + *nodeR |= fco; // set new ones +} + +static int +TrieNodeGetFirstChildOffset( Node node ) +{ + node &= 0x00FFFFFF; // 24 bits + return node; +} + +static Node +MakeTrieNode( int letter, bool isTerminal, int firstChildOffset, + bool isLastSibling ) +{ + Node result = 0; + + TrieNodeSetIsTerminal( &result, isTerminal ); + TrieNodeSetIsLastSibling( &result, isLastSibling ); + TrieNodeSetLetter( &result, letter ); + TrieNodeSetFirstChildOffset( &result, firstChildOffset ); + + return result; +} // MakeTrieNode + +// Caller may need to know the offset of the first top-level node. +// Write it here. +static void +writeOutStartNode( const char* startNodeOut, int firstRootChildOffset ) +{ + FILE* nodeout; + nodeout = fopen( startNodeOut, "w" ); + unsigned long be = htonl( firstRootChildOffset ); + (void)fwrite( &be, sizeof(be), 1, nodeout ); + fclose( nodeout ); +} // writeOutStartNode + +// build the hash for translating. I'm using a hash assuming it'll be +// fast. Key is the letter; value is the 0..31 value to be output. +static void +makeTableHash( void ) +{ + int ii; + FILE* TABLEFILE = fopen( gTableFile, "r" ); + if ( NULL == TABLEFILE ) { + ERROR_EXIT( "unable to open %s\n", gTableFile ); + } + + // Fill the 0th space since references are one-based + gRevMap.push_back(0); + + for ( ii = 0; ; ++ii ) { + int ch = getc(TABLEFILE); + if ( ch == EOF ) { + break; + } + + if ( gUseUnicode ) { // skip the first byte each time: tmp HACK!!! + ch = getc(TABLEFILE); + } + if ( ch == EOF ) { + break; + } + + gRevMap.push_back(ch); + + if ( ch == 0 ) { // blank + gBlankIndex = ii; + // we want to increment i when blank seen since it is a + // tile value + continue; + } + // die "$0: $gTableFile too large\n" + assert( ii < 64 ); + // die "$0: only blank (0) can be 64th char\n" ; + assert( ii < 64 || ch == 0 ); + + // Add 1 to i so no tile-strings contain 0 and we can treat as + // null-terminated. The 1 is subtracted again in + // outputNode(). + gTableHash[ch] = ii + 1; + } + + fclose( TABLEFILE ); +} // makeTableHash + +// emitNodes. "input" is $gNodes. From it we write up to +// $nBytesPerOutfile to files named $outFileBase0..n, mapping the +// letter field down to 5 bits with a hash built from $tableFile. If +// at any point we encounter a letter not in the hash we fail with an +// error. + +static void +emitNodes( unsigned int nBytesPerOutfile, const char* outFileBase ) +{ + // now do the emit. + + // is 17 bits enough? + fprintf( stderr, "There are %d (0x%x) nodes in this DAWG.\n", + gNodes.size(), gNodes.size() ); + int nTiles = gTableHash.size(); // blank is not included in this count! + if ( gNodes.size() > 0x1FFFF || gForceFour || nTiles > 32 ) { + gNBytesPerNode = 4; + } else if ( nTiles < 32 ) { + gNBytesPerNode = 3; + } else { + if ( gBlankIndex == 32 ) { // blank + gNBytesPerNode = 3; + } else { + ERROR_EXIT( "move blank to last position in info.txt " + "for smaller DAWG." ); + } + } + + int nextIndex = 0; + int nextFileNum; + + for ( nextFileNum = 0; ; ++nextFileNum ) { + + if ( nextIndex >= gNodes.size() ) { + break; // we're done + } + + if ( nextFileNum > 99 ) { + ERROR_EXIT( "Too many outfiles; infinite loop?" ); + } + + char outName[256]; + snprintf( outName, sizeof(outName), "%s_%03d.bin", + outFileBase, nextFileNum); + FILE* OUTFILE = fopen( outName, "w" ); + assert( OUTFILE ); + int curSize = 0; + + while ( nextIndex < gNodes.size() ) { + // scan to find the next terminal + int i; + for ( i = nextIndex; !TrieNodeGetIsLastSibling(gNodes[i]); ++i ) { + + // do nothing but a sanity check + if ( i >= gNodes.size() ) { + ERROR_EXIT( "bad trie format: last node not last sibling" ); + } + + } + ++i; // move beyond the terminal + int nextSize = (i - nextIndex) * gNBytesPerNode; + if (curSize + nextSize > nBytesPerOutfile ) { + break; + } else { + // emit the subarray + while ( nextIndex < i ) { + outputNode( gNodes[nextIndex], gNBytesPerNode, OUTFILE ); + ++nextIndex; + } + curSize += nextSize; + } + } + + fclose( OUTFILE ); + } + +} // emitNodes + +// print out the entire dictionary, as text, to STDERR. +static void +printOneLevel( int index, char* str, int curlen ) +{ + int inlen = curlen; + for ( ; ; ) { + Node node = gNodes[index++]; + + assert( TrieNodeGetLetter(node) < gRevMap.size() ); + char lindx = gRevMap[TrieNodeGetLetter(node)]; + + if ( (int)lindx >= 0x20 ) { + str[curlen++] = lindx; + } else { +#ifdef DEBUG + if ( gDebug ) { + fprintf( stderr, "sub space\n" ); + } +#endif + str[curlen++] = '\\'; + str[curlen++] = '0' + lindx; + } + str[curlen] = '\0'; + + if ( TrieNodeGetIsTerminal(node) ) { + fprintf( stderr, "%s\n", str ); + } + + int fco = TrieNodeGetFirstChildOffset( node ); + if ( fco != 0 ) { + printOneLevel( fco, str, curlen ); + } + + if ( TrieNodeGetIsLastSibling(node) ) { + break; + } + curlen = inlen; + } + str[inlen] = '\0'; +} + +static void +outputNode( Node node, int nBytes, FILE* outfile ) +{ + unsigned int fco = TrieNodeGetFirstChildOffset(node); + unsigned int fourthByte; + + if ( nBytes == 4 ) { + fourthByte = fco >> 16; + if ( fourthByte > 0xFF ) { + ERROR_EXIT( "fco too big" ); + } + fco &= 0xFFFF; + } + + // Formats are different depending on whether it's to have 3- or + // 4-byte nodes. + + // Here's what the three-byte node looks like. 16 bits plus one + // burried in the last byte for the next node address, five for a + // character/tile and one each for accepting and last-edge. + + // 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + // |-------- 16 bits of next node address -------| | | | |-tile indx-| + // | | | + // accepting bit ---+ | | + // last edge bit ------+ | + // ---- last bit (17th on next node addr)---------+ + + // The four-byte format adds a byte at the right end for + // addressing, but removes the extra bit (5) in order to let the + // chars field be six bits. Bits 7 and 6 remain the same. + + // write the fco (less that one bit). We want two bytes worth + // in three-byte mode, and three in four-byte mode + + // first two bytes are low-word of fco, regardless of format + for ( int i = 1; i >= 0; --i ) { + unsigned char tmp = (fco >> (i * 8)) & 0xFF; + fwrite( &tmp, 1, 1, outfile ); + } + fco >>= 16; // it should now be 1 or 0 + if ( fco > 1 ) { + ERROR_EXIT( "fco not 1 or 0" ); + } + + // - 1 below reverses + 1 in makeTableHash() + unsigned char chIn5 = TrieNodeGetLetter(node) - 1; + unsigned char bits = chIn5; + if ( bits > 0x1F && nBytes == 3 ) { + ERROR_EXIT( "char %d too big", bits ); + } + + if ( TrieNodeGetIsLastSibling(node) ) { + bits |= 0x40; + } + if ( TrieNodeGetIsTerminal(node) ) { + bits |= 0x80; + } + + // We set the 17th next-node bit only in 3-byte case (where char is + // 5 bits) + if ( nBytes == 3 && fco != 0 ) { + bits |= 0x20; + } + fwrite( &bits, 1, 1, outfile ); + + // the final byte, if in use + if ( nBytes == 4 ) { + unsigned char tmp = (unsigned char)fourthByte; + fwrite( &tmp, 1, 1, outfile ); + } +} // outputNode + +static void +usage( const char* name ) +{ + fprintf( stderr, "usage: %s \n" + "\t[-v] (print version and exit)\n" + "\t[-poolsize] (print hardcoded size of pool and exit)\n" + "\t[-b bytesPerFile] (default = 0xFFFFFFFF)\n" + "\t[-min ]\n" + "\t[-max ]\n" + "\t-m mapFile\n" + "\t-mn mapFile (unicode)\n" + "\t-ob outFileBase\n" + "\t-sn start node out file\n" + "\t[-if input file name] -- default = stdin\n" + "\t[-term ch] (word terminator -- default = '\\0'\n" + "\t[-nosort] (input already sorted in accord with -m; " + " default=sort'\n" + "\t[-dump] (write dictionary as text to STDERR for testing)\n" +#ifdef DEBUG + "\t[-debug] (turn on verbose output)\n" +#endif + "\t[-force4](use 4 bytes per node regardless of need)\n" + "\t[-r] (reject words with letters not in mapfile)\n" + "\t[-k] (kill if any letters not in mapfile -- default)\n", + name + ); +} // usage + +static void +error_exit( int line, const char* fmt, ... ) +{ + fprintf( stderr, "Error on line %d: ", line ); + va_list ap; + va_start( ap, fmt ); + vfprintf( stderr, fmt, ap ); + va_end( ap ); + fprintf( stderr, "\n" ); + exit( 1 ); +} + +static char* +parseARGV( int argc, char** argv, const char** inFileName ) +{ + *inFileName = NULL; + int index = 1; + while ( index < argc ) { + + char* arg = argv[index++]; + + if ( 0 == strcmp( arg, "-v" ) ) { + fprintf( stderr, "%s (Subversion revision %s)\n", argv[0], + VERSION_STR ); + exit( 0 ); + } else if ( 0 == strcmp( arg, "-poolsize" ) ) { + printf( "%d", MAX_POOL_SIZE ); + exit( 0 ); + } else if ( 0 == strcmp( arg, "-b" ) ) { + gNBytesPerOutfile = atol( argv[index++] ); + } else if ( 0 == strcmp( arg, "-mn" ) ) { + gTableFile = argv[index++]; + gUseUnicode = true; + } else if ( 0 == strcmp( arg, "-min" ) ) { + gLimLow = atoi(argv[index++]); + } else if ( 0 == strcmp( arg, "-max" ) ) { + gLimHigh = atoi(argv[index++]); + } else if ( 0 == strcmp( arg, "-m" ) ) { + gTableFile = argv[index++]; + } else if ( 0 == strcmp( arg, "-ob" ) ) { + gOutFileBase = argv[index++]; + } else if ( 0 == strcmp( arg, "-sn" ) ) { + gStartNodeOut = argv[index++]; + } else if ( 0 == strcmp( arg, "-if" ) ) { + *inFileName = argv[index++]; + } else if ( 0 == strcmp( arg, "-r" ) ) { + gKillIfMissing = false; + } else if ( 0 == strcmp( arg, "-k" ) ) { + gKillIfMissing = true; + } else if ( 0 == strcmp( arg, "-term" ) ) { + gTermChar = (char)atoi(argv[index++]); + } else if ( 0 == strcmp( arg, "-dump" ) ) { + gDumpText = true; + } else if ( 0 == strcmp( arg, "-nosort" ) ) { + gReadWordProc = readFromFile; + } else if ( 0 == strcmp( arg, "-wc" ) ) { + gCountFile = argv[index++]; + } else if ( 0 == strcmp( arg, "-ns" ) ) { + gBytesPerNodeFile = argv[index++]; + } else if ( 0 == strcmp( arg, "-force4" ) ) { + gForceFour = true; + } else if ( 0 == strcmp( arg, "-fsize" ) ) { + gFileSize = atoi(argv[index++]); +#ifdef DEBUG + } else if ( 0 == strcmp( arg, "-debug" ) ) { + gDebug = true; +#endif + } else { + ERROR_EXIT( "%s: unexpected arg %s", __func__, arg ); + } + } + + if ( gLimHigh > MAX_WORD_LEN || gLimLow > MAX_WORD_LEN ) { + usage( argv[0] ); + exit(1); + } + +#ifdef DEBUG + if ( gDebug ) { + fprintf( stderr, "gNBytesPerOutfile=%d\n", gNBytesPerOutfile ); + fprintf( stderr, "gTableFile=%s\n", gTableFile ); + fprintf( stderr, "gOutFileBase=%s\n", gOutFileBase ); + fprintf( stderr, "gStartNodeOut=%s\n", gStartNodeOut ); + fprintf( stderr, "gTermChar=%c(%d)\n", gTermChar, (int)gTermChar ); + fprintf( stderr, "gFileSize=%d\n", gFileSize ); + fprintf( stderr, "gLimLow=%d\n", gLimLow ); + fprintf( stderr, "gLimHigh=%d\n", gLimHigh ); + } +#endif + return gTableFile; +} // parseARGV diff --git a/xwords4/dawg/dict2dawg.pl b/xwords4/dawg/dict2dawg.pl new file mode 100755 index 000000000..064dff84e --- /dev/null +++ b/xwords4/dawg/dict2dawg.pl @@ -0,0 +1,857 @@ +#!/usr/bin/perl + +############################################################################## +# adapted from C++ code Copyright (C) 2000 Falk Hueffner +# This version Copyright (C) 2002 Eric House (xwords@eehouse.org) +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA +############################################################################## + +# inputs: 0. Name of file mapping letters to 0..31 values. In English +# case just contains A..Z. This will be used to translate the tries +# on output. +# 1. Max number of bytes per binary output file. +# +# 2. Basename of binary files for output. + +# 3. Name of file to which to write the number of the +# startNode, since I'm not rewriting a bunch of code to expect Falk's +# '*' node at the start. +# + +# In STDIN, the text file to be compressed. It absolutely +# must be sorted. The sort doesn't have to follow the order in the +# map file, however. + +# This is meant eventually to be runnable as part of a cgi system for +# letting users generate Crosswords dicts online. + + + +use strict; +use POSIX; + +my $gFirstDiff; +my @gCurrentWord; +my $gCurrentWord; # save so can check for sortedness +my $gDone = 0; +my @gInputStrings; +my $gNeedsSort = 1; # read from cmd line eventually +my @gNodes; # final array of nodes +my $gNBytesPerOutfile = 0xFFFFFFFF; +my $gTableFile; +my $gOutFileBase; +my $gStartNodeOut; +my $gInFileName; +my $gKillIfMissing = 1; +my $gTermChar = '/n'; +my $gDumpText = 0; # dump the dict as text after? +my $gCountFile; +my $gBytesPerNodeFile; # where to write whether node size 3 or 4 +my $gWordCount = 0; +my %gTableHash; +my $gBlankIndex; +my @gRevMap; +my $debug = 0; +my %gSubsHash; +my $gForceFour = 0; # use four bytes regardless of need? +my $gNBytesPerNode; +my $gUseUnicode; + +main(); + +exit; + +############################################################################## + +sub main() { + + if ( !parseARGV() ) { + usage(); + exit(); + } + + makeTableHash(); + + my $infile; + + if ( $gInFileName ) { + open $infile, "<$gInFileName"; + } else { + $infile = \*STDIN; + } + + @gInputStrings = parseAndSort( $infile ); + if ( $gInFileName ) { + close $infile; + } + + # Do I need this stupid thing? Better to move the first row to + # the front of the array and patch everything else. Or fix the + # non-palm dictionary format to include the offset of the first + # node. + + my $dummyNode = 0xFFFFFFFF; + @gNodes = ( $dummyNode ); + + readNextWord(); + + my $firstRootChildOffset = buildNode(0); + + moveTopToFront( \$firstRootChildOffset ); + + if ( $gStartNodeOut ) { + writeOutStartNode( $gStartNodeOut, $firstRootChildOffset ); + } + + print STDERR "\n... dumping table ...\n" if $debug; + printNodes( \@gNodes, "done with main" ) if $debug; + + # write out the number of nodes if requested + if ( $gCountFile ) { + open OFILE, "> $gCountFile"; + print OFILE pack( "N", $gWordCount ); + close OFILE; + print STDERR "wrote out: got $gWordCount words\n"; + } + + if ( $gOutFileBase ) { + emitNodes( $gNBytesPerOutfile, $gOutFileBase ); + } + + if ( $gDumpText && @gNodes > 0 ) { + printOneLevel( $firstRootChildOffset, "" ); + } + + if ( $gBytesPerNodeFile ) { + open OFILE, "> $gBytesPerNodeFile"; + print OFILE $gNBytesPerNode; + close OFILE; + } + print STDERR "Used $gNBytesPerNode per node.\n"; +} # main + +# We now have an array of nodes with the last subarray being the +# logical top of the tree. Move them to the start, fixing all fco +# refs, so that legacy code like Palm can assume top==0. +# +# Note: It'd probably be a bit faster to integrate this with emitNodes +# -- unless I need to have an in-memory list that can be used for +# lookups. But that's best for debugging, so keep it this way for now. +# +# Also Note: the first node is a dummy that can and should be tossed +# now. + +sub moveTopToFront($) { + my ( $firstRef ) = @_; + + my $firstChild = ${$firstRef}; + ${$firstRef} = 0; + my @lastSub; + + if ( $firstChild > 0 ) { + # remove the last (the root) subarray + @lastSub = splice( @gNodes, $firstChild ); + } else { + die "there should be no words!!" if $gWordCount != 0; + } + # remove the first (garbage) node + shift @gNodes; + + my $diff; + if ( $firstChild > 0 ) { + # -1 because all move down by 1; see prev line + $diff = @lastSub - 1; + die "something wrong with len\n" if $diff < 0; + } else { + $diff = 0; + } + + # stick it on the front + splice( @gNodes, 0, 0, @lastSub); + + # We add $diff to everything. There's no subtracting because + # nobody had any refs to the top list. + + for ( my $i = 0; $i < @gNodes; ++$i ) { + my $fco = TrieNodeGetFirstChildOffset( $gNodes[$i] ); + if ( $fco != 0 ) { # 0 means NONE, not 0th!! + TrieNodeSetFirstChildOffset( \$gNodes[$i], $fco+$diff ); + } + } +} # moveTopToFront + + +sub buildNode { + my ( $depth ) = @_; + + if ( @gCurrentWord == $depth ) { + # End of word reached. If the next word isn't a continuation + # of the current one, then we've reached the bottom of the + # recursion tree. + readNextWord(); + if ($gFirstDiff < $depth || $gDone) { + return 0; + } + } + + my @newedges; + + do { + my $letter = $gCurrentWord[$depth]; + my $isTerminal = @gCurrentWord - 1 == $depth ? 1:0; + + my $nodeOffset = buildNode($depth+1); + my $newNode = MakeTrieNode($letter, $isTerminal, $nodeOffset); + push( @newedges, $newNode ); + + } while ( ($gFirstDiff == $depth) && !$gDone); + + TrieNodeSetIsLastSibling( \@newedges[@newedges-1], 1 ); + + return addNodes( \@newedges ); +} # buildNode + +sub addNodes { + my ( $newedgesR ) = @_; + + my $found = findSubArray( $newedgesR ); + + if ( $found >= 0 ) { + die "0 is an invalid match!!!" if $found == 0; + return $found; + } else { + + my $firstFreeIndex = @gNodes; + + print STDERR "adding...\n" if $debug; + printNodes( $newedgesR ) if $debug; + + push @gNodes, (@{$newedgesR}); + + registerSubArray( $newedgesR, $firstFreeIndex ); + return $firstFreeIndex; + } +} # addNodes + +sub printNode { + my ( $index, $node ) = @_; + + print STDERR "[$index] "; + + my $letter = TrieNodeGetLetter($node); + printf( STDERR + "letter=%d(%s); isTerminal=%d; isLastSib=%d; fco=%d;\n", + $letter, "" . $gRevMap[$letter], + TrieNodeGetIsTerminal($node), + TrieNodeGetIsLastSibling($node), + TrieNodeGetFirstChildOffset($node)); +} # printNode + +sub printNodes { + my ( $nodesR, $name ) = @_; + + my $len = @{$nodesR}; + # print "printNodes($name): len = $len\n"; + + for ( my $i = 0; $i < $len; ++$i ) { + my $node = ${$nodesR}[$i]; + printNode( $i, $node ); + } + +} + + +# Hashing. We'll keep a hash of offsets into the existing nodes +# array, and as the key use a string that represents the entire sub +# array. Since the key is what we're matching for, there should never +# be more than one value per hash and so we don't need buckets. +# Return -1 if there's no match. + +sub findSubArray { + my ( $newedgesR ) = @_; + + my $key = join('', @{$newedgesR}); + + if ( exists( $gSubsHash{$key} ) ) { + return $gSubsHash{$key}; + } else { + return -1; + } +} # findSubArray + +# add to the hash +sub registerSubArray { + my ( $edgesR, $nodeLoc ) = @_; + + my $key = join( '', @{$edgesR} ); + + if ( exists $gSubsHash{$key} ) { + die "entry for key shouldn't exist!!"; + } else { + $gSubsHash{$key} = $nodeLoc; + } + +} # registerSubArray + +sub toWord($) { + my ( $tileARef ) = @_; + my $word = ""; + + foreach my $tile (@$tileARef) { + foreach my $letter (keys (%gTableHash) ) { + if ( $tile == $gTableHash{$letter} ) { + $word .= $letter; + last; + } + } + } + + return $word; +} + +sub readNextWord() { + my @word; + + if ( !$gDone ) { + $gDone = @gInputStrings == 0; + if ( !$gDone ) { + @word = @{shift @gInputStrings}; + } else { + print STDERR "gDone set to true\n" if $debug; + } + + print STDERR "got word: ", join(',',@word), "\n" if $debug; + } + my $numCommonLetters = 0; + my $len = @word; + if ( @gCurrentWord < $len ) { + $len = @gCurrentWord; + } + + while ( @gCurrentWord[$numCommonLetters] eq @word[$numCommonLetters] + && $numCommonLetters < $len) { + ++$numCommonLetters; + } + + $gFirstDiff = $numCommonLetters; + if ( #$debug && + @gCurrentWord > 0 && @word > 0 + && !firstBeforeSecond( \@gCurrentWord, \@word ) ) { + die "words ", join(",",@gCurrentWord), " (" . toWord(\@gCurrentWord) . + ") and " . join(",", @word) . " (" . toWord(\@word) . + ") out of order"; + } + @gCurrentWord = @word; +} # readNextWord + +sub firstBeforeSecond { + my ( $firstR, $secondR ) = @_; + + for ( my $i = 0; ; ++$i ) { + + # if we reach the end of the first word/list, we're done. + if ( $i == @{$firstR} ) { + die "duplicate!!!" if $i == @{$secondR}; + return 1; + # but if we reach the second end first, we've failed + } elsif ( $i == @{$secondR} ) { + return 0; + } + + my $diff = ${$firstR}[$i] <=> ${$secondR}[$i]; + + if ( $diff == 0 ) { + next; + } else { + return $diff < 0; + } + } +} # firstBeforeSecond + +# passed to sort. Should remain unprototyped for effeciency's sake + +sub cmpWords { + + my $lenA = @{$a}; + my $lenB = @{$b}; + my $min = $lenA > $lenB? $lenB: $lenA; + + for ( my $i = 0; $i < $min; ++$i ) { + my $ac = ${$a}[$i]; + my $bc = ${$b}[$i]; + + my $res = $ac <=> $bc; + + if ( $res != 0 ) { + return $res; # we're done + } + } + + # If we got here, they match up to their common length. Longer is + # greater. + my $res = @{$a} <=> @{$b}; + return $res; # which is longer? +} # cmpWords + +sub parseAndSort() { + my ( $infile ) = @_; + + my @wordlist; + my @word; + + my $lastWord; + WORDLOOP: + for ( ; ; ) { + + my $dropWord = 0; + splice @word; # empty it + + # for each byte + for ( ; ; ) { + my $byt = getc($infile); + + if ( $byt eq undef ) { + last WORDLOOP; + } elsif ( $byt eq $gTermChar ) { + if ( !$dropWord ) { + push @wordlist, [ @word ]; + ++$gWordCount; + } + $lastWord = ""; + next WORDLOOP; + } elsif ( exists( $gTableHash{$byt} ) ) { + if ( !$dropWord ) { + push @word, $gTableHash{$byt}; + die "word too long" if @word > 15; + if ( $gKillIfMissing ) { + $lastWord .= $byt; + } + } + } elsif ($gKillIfMissing) { + die "$0: chr $byt (", $byt+0, ") not in map file $gTableFile\n" + . "last word was $lastWord\n"; + } else { + $dropWord = 1; + splice @word; # lose anything we already have + } + } + } + + if ( $gNeedsSort && ($gWordCount > 0) ) { + print STDERR "starting sort...\n" if $debug; + @wordlist = sort cmpWords @wordlist; + print STDERR "sort finished\n" if $debug; + } + + print STDERR "length of list is ", @wordlist + 0, ".\n" if $debug; + + return @wordlist; +} # parseAndSort + +# Print binary representation of trie array. This isn't used yet, but +# eventually it'll want to dump to multiple files appropriate for Palm +# that can be catenated together on other platforms. There'll need to +# be a file giving the offset of the first node too. Also, might want +# to move to 4-byte representation when the input can't otherwise be +# handled. + +sub dumpNodes { + + for ( my $i = 0; $i < @gNodes; ++$i ) { + my $node = $gNodes[$i]; + my $bstr = pack( "I", $node ); + print STDOUT $bstr; + } +} + +############################################################################## +# Little node-field setters and getters to hide what bits represent +# what. +# +# high bit (31) is ACCEPTING bit +# next bit (30) is LAST_SIBLING bit +# next 6 bits (29-24) are tile bit (allowing alphabets of 64 letters) +# final 24 bits (23-0) are the index of the first child (fco) +# +############################################################################## + +sub TrieNodeSetIsTerminal { + my ( $nodeR, $isTerminal ) = @_; + + if ( $isTerminal ) { + ${$nodeR} |= (1 << 31); + } else { + ${$nodeR} &= ~(1 << 31); + } +} + +sub TrieNodeGetIsTerminal { + my ( $node ) = @_; + return ($node & (1 << 31)) != 0; +} + +sub TrieNodeSetIsLastSibling { + my ( $nodeR, $isLastSibling ) = @_; + if ( $isLastSibling ) { + ${$nodeR} |= (1 << 30); + } else { + ${$nodeR} &= ~(1 << 30); + } +} + +sub TrieNodeGetIsLastSibling { + my ( $node ) = @_; + return ($node & (1 << 30)) != 0; +} + +sub TrieNodeSetLetter { + my ( $nodeR, $letter ) = @_; + + die "$0: letter ", $letter, " too big" if $letter >= 64; + + my $mask = ~(0x3F << 24); + ${$nodeR} &= $mask; # clear all the bits + ${$nodeR} |= ($letter << 24); # set new ones +} + +sub TrieNodeGetLetter { + my ( $node ) = @_; + $node >>= 24; + $node &= 0x3F; # is 3f ok for 3-byte case??? + return $node; +} + +sub TrieNodeSetFirstChildOffset { + my ( $nodeR, $fco ) = @_; + + die "$0: $fco larger than 24 bits" if ($fco & 0xFF000000) != 0; + + my $mask = ~0x00FFFFFF; + ${$nodeR} &= $mask; # clear all the bits + ${$nodeR} |= $fco; # set new ones +} + +sub TrieNodeGetFirstChildOffset { + my ( $node ) = @_; + $node &= 0x00FFFFFF; # 24 bits + return $node; +} + + +sub MakeTrieNode { + my ( $letter, $isTerminal, $firstChildOffset, $isLastSibling ) = @_; + my $result = 0; + + TrieNodeSetIsTerminal( \$result, $isTerminal ); + TrieNodeSetIsLastSibling( \$result, $isLastSibling ); + TrieNodeSetLetter( \$result, $letter ); + TrieNodeSetFirstChildOffset( \$result, $firstChildOffset ); + + return $result; +} # MakeTrieNode + +# Caller may need to know the offset of the first top-level node. +# Write it here. +sub writeOutStartNode { + my ( $startNodeOut, $firstRootChildOffset ) = @_; + + open NODEOUT, ">$startNodeOut"; + print NODEOUT pack( "N", $firstRootChildOffset ); + close NODEOUT; +} # writeOutStartNode + +# build the hash for translating. I'm using a hash assuming it'll be +# fast. Key is the letter; value is the 0..31 value to be output. +sub makeTableHash { + my $i; + open TABLEFILE, "< $gTableFile"; + + splice @gRevMap; # empty it + + for ( $i = 0; ; ++$i ) { + my $ch = getc(TABLEFILE); + if ( $ch eq undef ) { + last; + } + + if ( $gUseUnicode ) { # skip the first byte each time: tmp HACK!!! + $ch = getc(TABLEFILE); + } + if ( $ch eq undef ) { + last; + } + + push @gRevMap, $ch; + + if ( ord($ch) == 0 ) { # blank + $gBlankIndex = $i; + next; # we want to increment i when blank seen since + # it is a tile value + } + + die "$0: $gTableFile too large\n" if $i > 64; + die "$0: only blank (0) can be 64th char\n" if ($i == 64 && $ch != 0); + + $gTableHash{$ch} = $i; + } + + close TABLEFILE; +} # makeTableHash + +# emitNodes. "input" is $gNodes. From it we write up to +# $nBytesPerOutfile to files named $outFileBase0..n, mapping the +# letter field down to 5 bits with a hash built from $tableFile. If +# at any point we encounter a letter not in the hash we fail with an +# error. + +sub emitNodes($$) { + my ( $nBytesPerOutfile, $outFileBase ) = @_; + + # now do the emit. + + # is 17 bits enough? + printf STDERR ("There are %d (0x%x) nodes in this DAWG.\n", + 0 + @gNodes, 0 + @gNodes ); + my $nTiles = 0 + keys(%gTableHash); # blank is not included in this count! + if ( @gNodes > 0x1FFFF || $gForceFour || $nTiles > 32 ) { + $gNBytesPerNode = 4; + } elsif ( $nTiles < 32 ) { + $gNBytesPerNode = 3; + } else { + if ( $gBlankIndex == 32 ) { # blank + print STDERR "blank's at 32; 3-byte-nodes still ok\n"; + $gNBytesPerNode = 3; + } else { + die "$0: move blank to last position in info.txt for smaller DAWG"; + } + } + + my $nextIndex = 0; + my $nextFileNum = 0; + + for ( $nextFileNum = 0; ; ++$nextFileNum ) { + + if ( $nextIndex >= @gNodes ) { + last; # we're done + } + + die "Too many outfiles; infinite loop?" if $nextFileNum > 99; + + my $outName = sprintf("${outFileBase}_%03d.bin", $nextFileNum); + open OUTFILE, "> $outName"; + binmode( OUTFILE ); + my $curSize = 0; + + while ( $nextIndex < @gNodes ) { + + # scan to find the next terminal + my $i; + for ( $i = $nextIndex; + !TrieNodeGetIsLastSibling($gNodes[$i]); + ++$i ) { + + # do nothing but a sanity check + if ( $i >= @gNodes) { + die "bad trie format: last node not last sibling" ; + } + + } + ++$i; # move beyond the terminal + my $nextSize = ($i - $nextIndex) * $gNBytesPerNode; + if ($curSize + $nextSize > $nBytesPerOutfile) { + last; + } else { + # emit the subarray + while ( $nextIndex < $i ) { + outputNode( $gNodes[$nextIndex], $gNBytesPerNode, + \*OUTFILE ); + ++$nextIndex; + } + $curSize += $nextSize; + } + } + + close OUTFILE; + } + +} # emitNodes + +sub printWord { + my ( $str ) = @_; + + print STDERR "$str\n"; +} + +# print out the entire dictionary, as text, to STDERR. + +sub printOneLevel { + + my ( $index, $str ) = @_; + + for ( ; ; ) { + + my $newStr = $str; + my $node = $gNodes[$index++]; + + my $lindx = $gRevMap[TrieNodeGetLetter($node)]; + + if ( ord($lindx) >= 0x20 ) { + $newStr .= "$lindx"; + } else { + print STDERR "sub space" if $debug; + $newStr .= "\\" . chr('0'+$lindx); + } + + if ( TrieNodeGetIsTerminal($node) ) { + printWord( $newStr ); + } + + my $fco = TrieNodeGetFirstChildOffset( $node ); + if ( $fco != 0 ) { + printOneLevel( $fco, $newStr ); + } + + if ( TrieNodeGetIsLastSibling($node) ) { + last; + } + } +} + +sub outputNode ($$$) { + my ( $node, $nBytes, $outfile ) = @_; + + my $fco = TrieNodeGetFirstChildOffset($node); + my $fourthByte; + + if ( $nBytes == 4 ) { + $fourthByte = $fco >> 16; + die "$0: fco too big" if $fourthByte > 0xFF; + $fco &= 0xFFFF; + } + + # Formats are different depending on whether it's to have 3- or + # 4-byte nodes. + + # Here's what the three-byte node looks like. 16 bits plus one + # burried in the last byte for the next node address, five for a + # character/tile and one each for accepting and last-edge. + + # 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + # |-------- 16 bits of next node address -------| | | | |-tile indx-| + # | | | + # accepting bit ---+ | | + # last edge bit ------+ | + # ---- last bit (17th on next node addr)---------+ + + # The four-byte format adds a byte at the right end for + # addressing, but removes the extra bit (5) in order to let the + # chars field be six bits. Bits 7 and 6 remain the same. + + # write the fco (less that one bit). We want two bytes worth + # in three-byte mode, and three in four-byte mode + + # first two bytes are low-word of fco, regardless of format + for ( my $i = 1; $i >= 0; --$i ) { + my $tmp = ($fco >> ($i * 8)) & 0xFF; + print $outfile pack( "C", $tmp ); + } + $fco >>= 16; # it should now be 1 or 0 + die "fco not 1 or 0" if $fco > 1; + + my $chIn5 = TrieNodeGetLetter($node); + my $bits = $chIn5; + die "$0: char $bits too big" if $bits > 0x1F && $nBytes == 3; + + if ( TrieNodeGetIsLastSibling($node) ) { + $bits |= 0x40; + } + if ( TrieNodeGetIsTerminal($node) ) { + $bits |= 0x80; + } + + # We set the 17th next-node bit only in 3-byte case (where char is + # 5 bits) + if ( $nBytes == 3 && $fco != 0 ) { + $bits |= 0x20; + } + print $outfile pack( "C", $bits ); + + # the final byte, if in use + if ( $nBytes == 4 ) { + print $outfile pack( "C", $fourthByte ); + } +} # outputNode + +sub usage { + print STDERR "usage: $0 \n" + . "\t[-b bytesPerFile] (default = 0xFFFFFFFF)\n" + . "\t-m mapFile\n" + . "\t-mn mapFile (unicode)\n" + . "\t-ob outFileBase\n" + . "\t-sn start node out file\n" + . "\t[-if input file name] -- default = stdin\n" + . "\t[-term ch] (word terminator -- default = '\\0'\n" + . "\t[-nosort] (input already sorted in accord with -m; " . + " default=sort'\n" + . "\t[-dump] (write dictionary as text to STDERR for testing)\n" + . "\t[-force4](use 4 bytes per node regardless of need)\n" + . "\t[-r] (reject words with letters not in mapfile)\n" + . "\t[-k] (kill if any letters no in mapfile -- default)\n" + . "\t[-debug] (print a bunch of stuff)\n" + ; + +} # usage + +sub parseARGV { + + my $arg; + while ( my $arg = shift(@ARGV) ) { + + SWITCH: { + if ($arg =~ /-b/) {$gNBytesPerOutfile = shift(@ARGV), last SWITCH;} + if ($arg =~ /-mn/) {$gTableFile = shift(@ARGV); + $gUseUnicode = 1; + last SWITCH;} + if ($arg =~ /-m/) {$gTableFile = shift(@ARGV); last SWITCH;} + if ($arg =~ /-ob/) {$gOutFileBase = shift(@ARGV), last SWITCH;} + if ($arg =~ /-sn/) {$gStartNodeOut = shift(@ARGV), last SWITCH;} + if ($arg =~ /-if/) {$gInFileName = shift(@ARGV), last SWITCH;} + if ($arg =~ /-r/) {$gKillIfMissing = 0; last SWITCH;} + if ($arg =~ /-k/) {$gKillIfMissing = 1; last SWITCH;} + if ($arg =~ /-term/) {$gTermChar = chr(shift(@ARGV)); last SWITCH;} + if ($arg =~ /-dump/) {$gDumpText = 1; last SWITCH;} + if ($arg =~ /-nosort/) {$gNeedsSort = 0; last SWITCH;} + if ($arg =~ /-wc/) {$gCountFile = shift(@ARGV); last SWITCH;} + if ($arg =~ /-ns/) {$gBytesPerNodeFile = shift(@ARGV); last SWITCH;} + if ($arg =~ /-force4/) {$gForceFour = 1; last SWITCH;} + # accept -fsize for compatibility with c++ version (but drop it) + if ($arg =~ /-fsize/) {shift(@ARGV); last SWITCH;} + if ($arg =~ /-debug/) {$debug = 1; last SWITCH;} + die "unexpected arg $arg\n"; + } + } + + + print STDERR "gNBytesPerOutfile=$gNBytesPerOutfile\n" if $debug; + print STDERR "gTableFile=$gTableFile\n" if $debug; + print STDERR "gOutFileBase=$gOutFileBase\n" if $debug; + print STDERR "gStartNodeOut=$gStartNodeOut\n" if $debug; + printf STDERR "gTermChar=%s(%d)\n", $gTermChar, ord($gTermChar) if $debug; + + return $gTableFile; + +} # parseARGV diff --git a/xwords4/dawg/dictstats.pl b/xwords4/dawg/dictstats.pl new file mode 100755 index 000000000..567a77f16 --- /dev/null +++ b/xwords4/dawg/dictstats.pl @@ -0,0 +1,67 @@ +#!/usr/bin/perl + +# print stats about in input stream that's assumed to be a dictionary. +# Counts and percentages of each letter, as well as total numbers of +# words. This is not part of the dictionary build process. I use it +# for creating info.txt files for new languages and debugging the +# creation of dictionaries from new wordlists. +# +# Something like this might form the basis for choosing counts and +# values for tiles without using the conventions established by +# Scrabble players. This isn't enough, though: the frequency of +# letter tuples and triples -- how often letters appear together -- is +# a better indicator than just letter count. + +use strict; + +my @wordSizeCounts; +my @letterCounts; +my $wordCount; +my $letterCount; + +while (<>) { + + chomp; + + ++$wordSizeCounts[length]; + ++$wordCount; + + foreach my $letter (split( / */ ) ) { + my $i = ord($letter); + # special-case the bogus chars we add for "specials" + die "$0: this is a letter?: $i" if $i <= 32 && $i >= 4 && $i != 0; + ++$letterCounts[$i]; + ++$letterCount; + } +} + +print "Number of words: $wordCount\n"; +print "Number of letters: $letterCount\n\n"; + + +print "**** word sizes ****\n"; +print "SIZE COUNT PERCENT\n"; +for ( my $i = 1 ; $i <= 99; ++$i ) { + my $count = $wordSizeCounts[$i]; + if ( $count > 0 ) { + my $pct = (100.00 * $count)/$wordCount; + printf "%2d %5d %.2f\n", $i, $count, $pct; + } +} + + + +print "\n\n**** Letter counts ****\n"; +print " ASCII ORD HEX PCT (of $letterCount)\n"; +my $lineNo = 1; +for ( my $i = 0; $i < 255; ++$i ) { + my $count = $letterCounts[$i]; + if ( $count > 0 ) { + my $pct = (100.00 * $count) / $letterCount; + printf( "%2d: %3s %3d %x %5.2f (%d)\n", + $lineNo, chr($i), $i, $i, $pct, $count ); + ++$lineNo; + } +} + +print "\n"; diff --git a/xwords4/dawg/frank_mkspecials.pl b/xwords4/dawg/frank_mkspecials.pl new file mode 100755 index 000000000..5c0ed4465 --- /dev/null +++ b/xwords4/dawg/frank_mkspecials.pl @@ -0,0 +1,47 @@ +#!/usr/bin/perl + +# Copyright 2001 by Eric House (xwords@eehouse.org) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# Given arguments consisting of triples, first a string and then pbitm +# files representing bitmaps. For each triple, print out the string and +# then the converted bitmaps. + +use strict; + +while ( @ARGV ) { + my $str = shift(); + my $largebmp = shift(); + my $smallbmp = shift(); + + doOne( $str, $largebmp, $smallbmp ); +} + +sub doOne { + my ( $str, $largebmp, $smallbmp ) = @_; + + print pack( "C", length($str) ); + print $str; + + print STDERR "looking at $largebmp", "\n"; + + die "file $largebmp does not exist\n" unless -e $largebmp; + print `cat $largebmp | ../pbitm2bin.pl`; + die "file $smallbmp does not exist\n" unless -e $smallbmp; + print `cat $smallbmp | ../pbitm2bin.pl`; +} + + diff --git a/xwords4/dawg/mkxwdcab.pl b/xwords4/dawg/mkxwdcab.pl new file mode 100755 index 000000000..a64727c32 --- /dev/null +++ b/xwords4/dawg/mkxwdcab.pl @@ -0,0 +1,87 @@ +#!/usr/bin/perl + +# Script for wrapping an xwd file as a CAB to ease installation. + +use strict; +use File::Basename; + +my $force = 0; +my $path; + +sub usage() { + print STDERR "Usage: $0 [-f] dictName.xwd\n"; + exit 1; +} + +my $cabwiz = $ENV{"CABWIZPATH"}; +if ( ! -x $cabwiz ) { + print STDERR < $infname"; + +print INFFILE < $tmpfile"; + +for ( my $i = 0; $i < $nSpecials; ++$i ) { + + my $size; + + my $str = shift( @ARGV ); + my $len = length($str); + die "string $str too long" if $len > 3; + print $str; + while ( $len < 4 ) { + ++$len; + print pack("c", 0 ); + } + + doOneFile( shift( @ARGV ), \*TMPFILE, \$gOffset ); + doOneFile( shift( @ARGV ), \*TMPFILE, \$gOffset ); +} + +close TMPFILE; + +# now append the tempfile +open TMPFILE, "< $tmpfile"; +while ( read( TMPFILE, my $buffer, 128 ) ) { + print $buffer; +} +close TMPFILE; + +unlink $tmpfile; + +exit 0; + + +sub doOneFile($$$) { + my ( $fil, $fh, $offsetR ) = @_; + + my $size = convertBmp($fil, $fh ); + if ( ($size % 2) != 0 ) { + ++$size; + print $fh pack( "c", 0 ); + } + + print pack( "n", $size > 0? ${$offsetR} : 0 ); + + ${$offsetR} += $size; +} # doOneFile + +sub convertBmp($$) { + my ( $pbitmfile, $fhandle ) = @_; + + if ( $pbitmfile eq "/dev/null" ) { + return 0; + } else { + + # for some reason I can't get quote marks to print into tmp.rcp using just `echo` + open TMP, "> tmp.rcp"; + print TMP "BITMAP ID 1000 \"$pbitmfile\" AUTOCOMPRESS"; + close TMP; + + `pilrc tmp.rcp`; + print $fhandle `cat Tbmp03e8.bin`; + my $siz = -s "Tbmp03e8.bin"; + `rm -f tmp.rcp Tbmp03e8.bin`; + + return $siz; + } +} diff --git a/xwords4/dawg/par.pl b/xwords4/dawg/par.pl new file mode 100755 index 000000000..024c58943 --- /dev/null +++ b/xwords4/dawg/par.pl @@ -0,0 +1,234 @@ +#!/usr/bin/perl + +# Copyright 2002 by Eric House (xwords@eehouse.org) All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# Only enough of par's features to support building a crosswords dict +# pdb + +use strict; + +my $debug = 0; + + +# stolen from par source +my $PRC_FLAGS_RESOURCE = (0x1<<0); +my $PRC_FLAGS_READONLY = (0x1<<1); +my $PRC_FLAGS_DIRTY = (0x1<<2); +my $PRC_FLAGS_BACKUP = (0x1<<3); +my $PRC_FLAGS_NEWER = (0x1<<4); +my $PRC_FLAGS_RESET = (0x1<<5); +my $PRC_FLAGS_COPYPREVENT = (0x1<<6); +my $PRC_FLAGS_STREAM = (0x1<<7); +my $PRC_FLAGS_HIDDEN = (0x1<<8); +my $PRC_FLAGS_LAUNCHABLE = (0x1<<9); +my $PRC_FLAGS_RECYCLABLE = (0x1<<10); +my $PRC_FLAGS_BUNDLE = (0x1<<11); +my $PRC_FLAGS_OPEN = (0x1<<15); + + +my $gAttrs = 0; +my $gVersion = 1; # par defaults this to 1 + +my $cmd = shift( @ARGV ); +die "only 'c' supported now" if $cmd ne "c" && $cmd ne "-c"; + +readHOptions( \@ARGV ); + +my $dbfile = shift( @ARGV ); +my $name = shift( @ARGV ); +die "name $name too long" if length($name) > 31; +my $type = shift( @ARGV ); +die "type $type must be of length 4" if length($type) != 4; +my $cid = shift( @ARGV ); +die "cid $cid must be of length 4" if length($cid) != 4; + +my @fileNames; +my @fileLengths; + +my $nFiles = 0; + +while ( @ARGV > 0 ) { + my $filename = shift( @ARGV ); + push @fileNames, $filename; + push @fileLengths, -s $filename; + ++$nFiles; +} + +# from par's prcp.h; thanks djw! +# typedef struct prc_file_t { +# prc_byte_t name[32]; +# prc_byte_t flags[2]; +# prc_byte_t version[2]; +# prc_byte_t ctime[4]; +# prc_byte_t mtime[4]; +# prc_byte_t btime[4]; +# prc_byte_t modnum[4]; +# prc_byte_t appinfo[4]; +# prc_byte_t sortinfo[4]; +# prc_byte_t type[4]; +# prc_byte_t cid[4]; +# prc_byte_t unique_id_seed[4]; +# prc_byte_t next_record_list[4]; +# prc_byte_t nrecords[2]; +# } prc_file_t; + +my $str; +my $offset = 0; + +open OUTFILE, "> $dbfile" or die "couldn't open outfile $dbfile for writing"; + +# print the string, then pad with 0s +$offset = length($name); +print OUTFILE $name; +while ( $offset < 32 ) { + print OUTFILE pack("c", 0); + ++$offset; +} + +$str = pack("n", $gAttrs); # flags +print OUTFILE $str; +$offset += length($str); + +$str = pack("n", $gVersion); # version +print OUTFILE $str; +$offset += length($str); + +my $time = time() + 2082844800; +$str = pack("NNN", $time, $time, 0); # ctime, mtime, btime +print OUTFILE $str; +$offset += length($str); + +$str = pack("N", 0 ); # mod num +print OUTFILE $str; +$offset += length($str); + +$str = pack("N", 0 ); # appinfo +print OUTFILE $str; +$offset += length($str); + +$str = pack("N", 0 ); # sortinfo +print OUTFILE $str; +$offset += length($str); + + +print OUTFILE $type; # type +print OUTFILE $cid; # cid +$offset += 8; + +$str = pack("NN", 0, 0 ); # unique_id_seed, next_record_list +print OUTFILE $str; +$offset += length($str); + +$str = pack("n", $nFiles ); # nrecords +print OUTFILE $str; +$offset += length($str); + +$offset += $nFiles * 8; +$offset += 2; # djw adds 2 bytes after size list; see below +foreach my $len ( @fileLengths ) { + print OUTFILE pack( "N", $offset ); + print OUTFILE pack( "N", 0 ); + $offset += $len; +} + +print OUTFILE pack( "n", 0 ); # djw does this sans comment: flush.c, line 87 + +foreach my $file ( @fileNames ) { + open INFILE, "<$file" or die "couldn't open infile $file\n"; + my $buffer; + while ( read INFILE, $buffer, 1024 ) { + print OUTFILE $buffer; + } + close INFILE; +} + + +close OUTFILE; + +exit 0; + +############################################################################## +# Subroutines +############################################################################## + +sub readHOptions { + + my ( $argvR ) = @_; + + for ( ; ; ) { + my $opt = ${$argvR}[0]; + + if ( $opt !~ /^-/ ) { + last; + } + + # it starts with a '-': use it; else don't consume anything + shift @{$argvR}; + + if ( $opt eq "-a" ) { + my $attrs = shift @{$argvR}; + processAttrString( $attrs ); + } elsif ( $opt eq "-v" ) { + $gVersion = shift @{$argvR}; + } else { + die "what's with \"$opt\": -a and -v are the only hattrs supported"; + } + } + +} # readHOptions + +sub processAttrString { + + my ( $attrs ) = @_; + + foreach my $flag ( split /\|/, $attrs ) { + + print STDERR "looking at flag $flag\n" if $debug; + + if ( $flag =~ /resource/ ) { + $gAttrs |= $PRC_FLAGS_RESOURCE; + die "resource attr not supported"; + } elsif ( $flag =~ /readonly/ ) { + $gAttrs |= $PRC_FLAGS_READONLY; + } elsif ( $flag =~ /dirty/ ) { + $gAttrs |= $PRC_FLAGS_DIRTY; + } elsif ( $flag =~ /backup/ ) { + $gAttrs |= $PRC_FLAGS_BACKUP; + } elsif ( $flag =~ /newer/ ) { + $gAttrs |= $PRC_FLAGS_NEWER; + } elsif ( $flag =~ /reset/ ) { + $gAttrs |= $PRC_FLAGS_RESET; + } elsif ( $flag =~ /copyprevent/ ) { + $gAttrs |= $PRC_FLAGS_COPYPREVENT; + } elsif ( $flag =~ /stream/ ) { + $gAttrs |= $PRC_FLAGS_STREAM; + die "stream attr not supported"; + } elsif ( $flag =~ /hidden/ ) { + $gAttrs |= $PRC_FLAGS_HIDDEN; + } elsif ( $flag =~ /launchable/ ) { + $gAttrs |= $PRC_FLAGS_LAUNCHABLE; + } elsif ( $flag =~ /recyclable/ ) { + $gAttrs |= $PRC_FLAGS_RECYCLABLE; + } elsif ( $flag =~ /bundle/ ) { + $gAttrs |= $PRC_FLAGS_BUNDLE; + } elsif ( $flag =~ /open/ ) { + $gAttrs |= $PRC_FLAGS_OPEN; + } else { + die "flag $flag not supportd"; + } + } +} # processAttrString diff --git a/xwords4/dawg/pbitm2bin.pl b/xwords4/dawg/pbitm2bin.pl new file mode 100755 index 000000000..67efffcc5 --- /dev/null +++ b/xwords4/dawg/pbitm2bin.pl @@ -0,0 +1,87 @@ +#!/usr/bin/perl +# +# Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# +# Given a pbitm on stdin, a text bitmap file where '#' indicates a set +# bit and '-' indicates a clear bit, convert into binary form (on +# stdout) where there's one bit per bit plus a byte each for the width +# and height. Nothing for bitdepth at this point. And no padding: if +# the number of bits in a row isn't a multiple of 8 then one byte will +# hold the last bits of one row and the first of another. + +use strict; + +my $nRows = 0; +my $nCols = 0; +my $bits = ""; # save the chars in a single string to start + +# first gather information and sanity-check the data + +while (<>) { + chomp; + my $len = length(); + + if ( $nCols == 0 ) { + $nCols = $len; + } else { + die "line of inconsistent length" if $nCols != $len ; + } + if ( $nCols == 0 ) { + last; + } + + $bits .= $_; + ++$nRows; +} + +my $len = length($bits); +print pack( "C", $nCols ); + +# if we've been given an empty file, print out a single null byte and +# be done. That'll be the convention for "non-existant bitmap". +if ( $len == 0 ) { + exit 0; +} +print pack( "C", $nRows ); +printf STDERR "emitting %dx%d bitmap\n", $nCols, $nRows; + + +my @charlist = split( //,$bits); +my $byte = 0; + +for ( my $count = 0; ; ++$count ) { + + my $ch = $charlist[$count]; + my $bitindex = $count % 8; + + $ch == '-' || $ch == '#' || die "unknown char $ch"; + + my $bit = ($ch eq '#')? 1:0; + + $byte |= $bit << (7 - $bitindex); + + my $lastPass = $count + 1 == $len; + if ( $bitindex == 7 || $lastPass ) { + print pack( "C", $byte ); + if ( $lastPass ) { + last; + } + $byte = 0; + } + +} # for loop diff --git a/xwords4/dawg/xloc.pl b/xwords4/dawg/xloc.pl new file mode 100755 index 000000000..cb21db81b --- /dev/null +++ b/xwords4/dawg/xloc.pl @@ -0,0 +1,47 @@ +#!/usr/bin/perl + +# Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# test and wrapper file for xloc.pm + +use strict; +use xloc; + +my $arg = shift(@ARGV); +my $outfile = shift(@ARGV); +my $lang = shift(@ARGV); +my $path = "./$lang"; +my $infoFile = "$path/info.txt"; + +die "info file $infoFile not found\n" if ! -s $infoFile; + + +my $xlocToken = xloc::ParseTileInfo($infoFile); + +open OUTFILE, "> $outfile"; +# For f*cking windoze linefeeds +binmode( OUTFILE ); + +if ( $arg eq "-t" ) { + xloc::WriteMapFile( $xlocToken, 0, \*OUTFILE ); +} elsif ( $arg eq "-tn" ) { + xloc::WriteMapFile( $xlocToken, 1, \*OUTFILE ); +} elsif ( $arg eq "-v" ) { + xloc::WriteValuesFile( $xlocToken, \*OUTFILE ); +} + +close OUTFILE; diff --git a/xwords4/dawg/xloc.pm b/xwords4/dawg/xloc.pm new file mode 100644 index 000000000..4aefe7440 --- /dev/null +++ b/xwords4/dawg/xloc.pm @@ -0,0 +1,180 @@ +#!/usr/bin/perl + +# Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# The idea here is that all that matters about a language is stored in +# one file (possibly excepting rules for prepping a dictionary). +# There's a list of tile faces, counts and values, and also some +# name-value pairs as needed. The pairs come first, and then a list +# of tiles. + +package xloc; + +use strict; +use warnings; + +BEGIN { + use Exporter (); + our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS); + + $VERSION = 1.00; + + @ISA = qw(Exporter); + @EXPORT = qw(&ParseTileInfo &GetNTiles &TileFace &TileValue + &TileCount &GetValue &WriteMapFile &WriteValuesFile); + %EXPORT_TAGS = ( ); +} + +# Returns what's meant to be an opaque object that can be passed back +# for queries. It's a hash with name-value pairs and an _INFO entry +# containing a list of tile info lists. + +sub ParseTileInfo($) { + my ( $filePath ) = @_; + my %result; + + open INPUT, "<$filePath" or die "couldn't open $filePath"; + + my $inTiles = 0; + my @tiles; + while ( ) { + + chomp; + s/\#.*$//; + s/^\s*$//; # nuke all-white-space lines + next if !length; + + if ( $inTiles ) { + if ( // ) { + last; + } else { + my ( $count, $val, $face ) = m/^\s*(\w+)\s+(\w+)\s+(.*)\s*$/; + push @tiles, [ $count, $val, $face ]; + } + } elsif ( /\w:/ ) { + my ( $nam, $val ) = split ':', $_, 2; + $result{$nam} .= $val; + } elsif ( // ) { + $inTiles = 1; + } + + } + + close INPUT; + + $result{"_TILES"} = [ @tiles ]; + + return \%result; +} + +sub GetNTiles($) { + my ( $hashR ) = @_; + + my $listR = ${$hashR}{"_TILES"}; + + return 0 + @{$listR}; +} + +sub GetValue($$) { + my ( $hashR, $name ) = @_; + return ${$hashR}{$name}; +} + +sub WriteMapFile($$$) { + my ( $hashR, $unicode, $fhr ) = @_; + + my $packStr; + if ( $unicode ) { + $packStr = "n"; + } else { + $packStr = "C"; + } + + my $count = GetNTiles($hashR); + my $specialCount = 0; + for ( my $i = 0; $i < $count; ++$i ) { + my $tileR = GetNthTile( $hashR, $i ); + my $str = ${$tileR}[2]; + + if ( $str =~ /\'(.)\'/ ) { + print $fhr pack($packStr, ord($1) ); + } elsif ( $str =~ /\"(.+)\"/ ) { + print $fhr pack($packStr, $specialCount++ ); + } elsif ( $str =~ /(\d+)/ ) { + print $fhr pack( $packStr, $1 ); + } else { + die "WriteMapFile: unrecognized face format $str, elem $i"; + } + } +} # WriteMapFile + +sub WriteValuesFile($$) { + my ( $hashR, $fhr ) = @_; + + my $header = GetValue( $hashR,"XLOC_HEADER" ); + die "no XLOC_HEADER found" if ! $header; + + print STDERR "header is $header\n"; + + print $fhr pack( "n", hex($header) ); + + my $count = GetNTiles($hashR); + for ( my $i = 0; $i < $count; ++$i ) { + my $tileR = GetNthTile( $hashR, $i ); + + print $fhr pack( "c", TileValue($tileR) ); + print $fhr pack( "c", TileCount($tileR) ); + } + +} # WriteValuesFile + +sub GetNthTile($$) { + my ( $hashR, $n ) = @_; + my $listR = ${$hashR}{"_TILES"}; + + return ${$listR}[$n]; +} + +sub TileFace($) { + my ( $tileR ) = @_; + + my $str = ${$tileR}[2]; + + if ( $str =~ /\'(.)\'/ ) { + return $1; + } elsif ( $str =~ /\"(.+)\"/ ) { + return $1; + } elsif ( $str =~ /(\d+)/ ) { + return chr($1); + } else { + die "TileFace: unrecognized face format: $str"; + } +} + +sub TileValue($) { + my ( $tileR ) = @_; + + return ${$tileR}[0]; +} + +sub TileCount($) { + my ( $tileR ) = @_; + + return ${$tileR}[1]; +} + +1; diff --git a/xwords4/franklin/.cvsignore b/xwords4/franklin/.cvsignore new file mode 100644 index 000000000..00859fdbe --- /dev/null +++ b/xwords4/franklin/.cvsignore @@ -0,0 +1,23 @@ +xwords4.seb +xwords4_sa.coff +xwords4_sa.link +xwords4_sa.map +xwords4_sa.seg +xwords4_sa.sym +xwords4.chk +xwords4.coff +xwords4.fxe +xwords4.icn +xwords4.link +xwords4.map +bmps_includes.h +lib.snk32 +libraries.link +os.cmd +sGDB +GDB +xwords4.mp +xwords4.seg +xwords4.sym +xwords4_sa +xwords4 \ No newline at end of file diff --git a/xwords4/franklin/LocalizedStrIncludes.h b/xwords4/franklin/LocalizedStrIncludes.h new file mode 100644 index 000000000..3c4f412aa --- /dev/null +++ b/xwords4/franklin/LocalizedStrIncludes.h @@ -0,0 +1,53 @@ +/* + * Copyright 1997 - 2002 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + * For faking out franklin compiler for now. + */ + +enum { + STRD_REMAINING_TILES_ADD, + STRD_UNUSED_TILES_SUB, + STR_COMMIT_CONFIRM, + STR_BONUS_ALL, + STRD_TURN_SCORE, + STR_NONLOCAL_NAME, + STR_LOCAL_NAME, + STRD_TIME_PENALTY_SUB, + + STRD_CUMULATIVE_SCORE, + STRS_TRAY_AT_START, + STRS_MOVE_DOWN, + STRS_MOVE_ACROSS, + STRS_NEW_TILES, + STRSS_TRADED_FOR, + STR_PASS, + STR_PHONY_REJECTED, + STRD_ROBOT_TRADED, + STR_ROBOT_MOVED, + STR_REMOTE_MOVED, + + STRD_TRADED, + STRSD_SUMMARYSCORED, + STR_PASSED, + STR_LOSTTURN, + + STRS_VALUES_HEADER, + + STR_LAST +}; diff --git a/xwords4/franklin/Makefile b/xwords4/franklin/Makefile new file mode 100644 index 000000000..c91e2ad53 --- /dev/null +++ b/xwords4/franklin/Makefile @@ -0,0 +1,166 @@ +# -*- mode: Makefile; compile-command: "make -k ARCH=i686"; -*- +# ©2001 Franklin Electronic Publishers, Inc. Burlington, NJ. +# File: gui/test/Makefile +# +PLATFORM = franklin +COMMON = ../common + +ifndef ARCH +$(error "Franklin builds require the environment variable ARCH. ") +endif + +XW_DEFINES = -DPLATFORM_EBOOK -DPOINTER_SUPPORT -DKEY_SUPPORT -DSHOW_PROGRESS \ + -DXWFEATURE_STANDALONE_ONLY -DFEATURE_TRAY_EDIT -DXWFEATURE_SEARCHLIMIT \ + -DNODE_CAN_4 -DUSE_PATTERNS -D__LITTLE_ENDIAN +include ../common/config.mk + +APPNAME = xwords4 + +# Define the list of source files +SRCS_CPP = frankdraw.cpp \ + frankmain.cpp \ + franksavedgames.cpp \ + frankask.cpp \ + frankgamesdb.cpp \ + frankpasswd.cpp \ + frankshowtext.cpp \ + frankdict.cpp \ + frankletter.cpp \ + frankplayer.cpp \ + frankdlist.cpp + +SRCS_C = +SRCS_S = + +# Program parameters (ebo_locate needs 12K of stack) +#HEAPSPACE = 98304 +HEAPSPACE = 409600 # 0x64000 +MAINSTACKSPACE = 16384 +TIMERSTACKSPACE = 4096 + +# Environment: Use "yes" or "no" +GUI_LOCAL_COPY = no +OS_LOCAL_COPY = no +LANG_LOCAL_COPY = no +GUI_LANGUAGE = GUI_Amr.pkg +OS_BASEDIR = ../../../os +GUI_BASEDIR = ../source + +#To prevent warnings from stopping the compilation, change the following line +#to "ERROR=" or start the make using "make ERROR=". +ERROR = -Werror + +OBJS = ${COMMONOBJ} + +# Include the standard Makefile targets +ifeq (x$(EBSDK_LIBS)x,xx) +# Normal: binary release version + include ${EBOOKMAN_SDK}/ebsdk.uses + include ${ESDK_TARGET_INC}/Makefile.GUI +else +# Special: building the SDK + include ${EBSDK_LIBS}/ebookman_libs.uses + include ../source/Makefile.GUI +endif + + +# +# At this point in the Makefile, you can put overrides for variables that +# were set in "Makefile.GUI". +# + +#Create linkable version of VoiceAge file +#${OBJDIR}/objects.link: welcome.voiceage + +#welcome.voiceage: welcome.seb +# tail -c+4097 welcome.voiceage + +#CLEANLIST += welcome.voiceage + + +#Need this for an os1 link of a program that uses floating-point +#GCC_LIB = ${ESDK_LIBGCC_INSTALL_LIBFILE_FLOAT} + +INCFLAGS += -I$(COMMON) -I../relay -I./ +CFLAGS += ${XW_DEFINES} -DCPLUS +CFL_NOCPLUS = $(subst -DCPLUS,,${CFLAGS}) + +CLEANLIST += ${COMMONOBJ} + +# dependencies for image generation +BMPS = ./bmps +frankmain.cpp: bmps_includes.h + +$(BMPS)/%.cpp: $(BMPS)/%.pbitm + ./pbitm2frank.pl $< $* > $@ + +GENIMGS = $(BMPS)/flip.cpp \ + $(BMPS)/lightbulb.cpp \ + $(BMPS)/valuebutton.cpp \ + $(BMPS)/undo.cpp \ + +# listing the .cpp files here as dependencies should be all that's +# needed to get them generated. +bmps_includes.h: $(GENIMGS) + @echo "/* This file generated in Makefile; do not edit!!! */" > $@ + for f in $^; do echo "#include \"$$f\"" >> $@; done + + + +# +# Generic rules for invoking the compiler and assembler. Stolen from +# Makefile.GUI +# + +${COMMONOBJDIR}/%.s :: ${COMMON}/%.c + ${ESDK_GCC32_EXE} -b sneak32 -g $(CFL_NOCPLUS) -mclist -S -o $@ -c $< + +${COMMONOBJDIR}/%.i :: ${COMMON}/%.c + ${ESDK_GCC32_EXE} -b sneak32 -g $(CFL_NOCPLUS) -mclist -E -o $@ -c $< + +${COMMONOBJDIR}/%.i :: ${COMMON}/%.cpp + ${ESDK_GXX32_EXE} -b sneak32 -g $(CFLAGS) -mclist -E -o $@ -c $< + +${COMMONOBJDIR}/%.s :: ${COMMON}/%.cpp + ${ESDK_GXX32_EXE} -b sneak32 -g $(CFLAGS) -mclist -S -o $@ -c $< + +${COMMONOBJDIR}/%.o :: ${COMMON}/%.cpp + ${ESDK_GXX32_EXE} -b sneak32 -g $(CFLAGS) -o $@ -c $< + +${COMMONOBJDIR}/%.o :: ${COMMON}/%.c + ${ESDK_GCC32_EXE} -b sneak32 -g $(CFL_NOCPLUS) -o $@ -c $< + +${COMMONOBJDIR}/%.o :: ${COMMONOBJDIR}/%.s + ${ESDK_ASM32_EXE} -case ${INCFLAGS} -po ${COMMONOBJDIR} -o $< + +# Targets to allow build without copying lots of files in. First has +# hack to force directory creation +os.link os.def standalone.link: + mkdir -p ../common/franklin + ln -s $$(find $${EBOOKMAN_SDK} -follow -name $@ | grep gui) $@ + +# get rid of the welcome crap +libraries.link: + cat $$(find $${EBOOKMAN_SDK} -follow -name $@ | grep gui) | \ + grep -v Welcome > $@ + + + +# +# The following targets are usable only during SDK creation. They are for +# copying this directory to samples/gui/test. +# + +build: os sa + +configure: + +do_install_out: build + mkdir -p ${ELIB_GUI_INSTALL_SAMPLE_TESTDIR} + (tar cf - . | (cd ${ELIB_GUI_INSTALL_SAMPLE_TESTDIR} && tar xf -)) + +memdebug: + $(MAKE) "XW_DEFINES = ${XW_DEFINES} -DMEM_DEBUG -DDEBUG" + +test: + echo ${OBJS} diff --git a/xwords4/franklin/README.txt b/xwords4/franklin/README.txt new file mode 100644 index 000000000..cdcafac37 --- /dev/null +++ b/xwords4/franklin/README.txt @@ -0,0 +1,56 @@ +This file describes how to build Crosswords for Franklin's eBookman. + +It should be possible to do eBookman development on either Windows +(with cygwin) or Linux, but I've only done it on Linux, so that's what +I'll describe. I expect it's not much different on Windows once +cygwin's installed. + +You can get the SDK, and instructions for installing it, here: +http://download.franklin.com/franklin/ebookman/developer/ + +There are two environment variables you'll need to build for eBookman. +Add these to your shell startup script. Here's mine (for bash): + +export ARCH=i686 +export EBOOKMAN_SDK=/home/ehouse/franklin/SDK + +You can build either for the simulator/debugger, or to run on a +device. For the simulator, type at a commandline in this directory: + +# make memdebug + +or + +# make + +Provided you have a copy of BasEnglish2to8.xwd in this directory, you +can then run Crosswords in the simulator by typing: + +# ./sGDB + +(To run with additional dictionaries, edit initial.mom.) + +The command ./GDB is also available. The difference is that the +former is much faster and launches you directly into Crosswords, while +the latter launches the device's App Picker screen, allowing you to +launch Crosswords the way an end-user would. sGDB is what I use 95% +of the time. + +(The simulator is a bit rough. Not all of the buttons work, etc. To +at least get started, wait for the two windows to come up. Go to the +one titled "Source Window", and choose "Continue" on the "Control" +menu. That'll get Crosswords running.) + +To build a .seb file you can install on a device, type + +# make xwords4.seb + +Install this on an eBookman together with at least one dictionary +(e.g. BasEnglish2to8.seb) and you're good to go. + +****************** + +On debugging: I've never had much luck with the source-level debugger +in the eBookman SDK. I use XP_DEBUGF statements a lot, and do common +code development on the Linux port where debugging's better. If you're +using cygwin on Windows you may have better luck. diff --git a/xwords4/franklin/bmps/.cvsignore b/xwords4/franklin/bmps/.cvsignore new file mode 100644 index 000000000..d1a8a81f2 --- /dev/null +++ b/xwords4/franklin/bmps/.cvsignore @@ -0,0 +1,4 @@ +flip.cpp +lightbulb.cpp +undo.cpp +valuebutton.cpp diff --git a/xwords4/franklin/bmps/flip.pbitm b/xwords4/franklin/bmps/flip.pbitm new file mode 100644 index 000000000..9118656f8 --- /dev/null +++ b/xwords4/franklin/bmps/flip.pbitm @@ -0,0 +1,12 @@ +############ +#-########## +#--######### +#---######## +#----####### +#-----###### +#------##### +#-------#### +#--------### +#---------## +#----------# +############ diff --git a/xwords4/franklin/bmps/lightbulb.pbitm b/xwords4/franklin/bmps/lightbulb.pbitm new file mode 100644 index 000000000..fab145196 --- /dev/null +++ b/xwords4/franklin/bmps/lightbulb.pbitm @@ -0,0 +1,11 @@ +############ +#-###--###-# +####-##-#### +###-####-### +#-#-####-#-# +####-##-#### +####-##-#### +##-##--##-## +#-###--###-# +#####--##### +############ \ No newline at end of file diff --git a/xwords4/franklin/bmps/undo.pbitm b/xwords4/franklin/bmps/undo.pbitm new file mode 100644 index 000000000..9045477a8 --- /dev/null +++ b/xwords4/franklin/bmps/undo.pbitm @@ -0,0 +1,11 @@ +############ +#####-###### +####--###### +###---###### +##--------## +#---------## +##--------## +###---###### +####--###### +#####-###### +############ diff --git a/xwords4/franklin/bmps/valuebutton.pbitm b/xwords4/franklin/bmps/valuebutton.pbitm new file mode 100644 index 000000000..7767bc67a --- /dev/null +++ b/xwords4/franklin/bmps/valuebutton.pbitm @@ -0,0 +1,12 @@ +############ +##--######-# +###-#####-## +###-####-### +###-###-#### +##---#-##### +#####-#---## +####-#-###-# +###-##-----# +##-###-###-# +#-####-###-# +############ diff --git a/xwords4/franklin/frankask.cpp b/xwords4/franklin/frankask.cpp new file mode 100644 index 000000000..ca0f9bf1c --- /dev/null +++ b/xwords4/franklin/frankask.cpp @@ -0,0 +1,108 @@ +// -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- +/* + * Copyright 1999-2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#if 0 +#include +#include +#include +#include +#include +#include "sys.h" +#include "gui.h" +#include "OpenDatabases.h" +#include "ebm_object.h" + +extern "C" { +#include "xptypes.h" +#include "board.h" +#include "model.h" +} + +#include "frankids.h" +#include "frankask.h" + +#define FIRST_BUTTON_ID 2000 + +CAskDialog::CAskDialog( U16* resultP, char* question, U16 numButtons, ... ) + : CWindow( ASK_WINDOW_ID, 2, 50, 196, 80, "Question..." ) +{ + U16 i; + va_list ap; + + this->resultP = resultP; + this->question = question; + this->drawInProgress = FALSE; + + U16 horSpacing = 196 / (numButtons+1); + + va_start(ap, numButtons); + for ( i = 0; i < numButtons; ++i ) { + char* buttName = va_arg( ap, char*); + XP_DEBUGF( "button %d's title is %s\n", i, buttName ); + CButton* button = new CButton( i+FIRST_BUTTON_ID, 0, 0, buttName ); + + U16 width = button->GetWidth(); + U16 bx = horSpacing * (i+1); + this->AddChild( button, bx - (width/2), 40 ); + } + va_end(ap); + + + this->textY = 10; + this->textX = 5; +} // CAskDialog::CAskDialog + +void +CAskDialog::Draw() +{ + if ( !this->drawInProgress ) { + + this->drawInProgress = TRUE; + + CWindow::Draw(); // buttons, etc. + + this->DrawText( this->question, this->textX, this->textY ); + + this->drawInProgress = FALSE; + } +} // CAskDialog::Draw + +S32 +CAskDialog::MsgHandler( MSG_TYPE type, CViewable *object, S32 data ) +{ + S32 result = 0; + S16 id; + + switch (type) { + case MSG_BUTTON_SELECT: + id = object->GetID(); + *this->resultP = id - FIRST_BUTTON_ID; + this->Close(); + result = 1; + default: + break; + } + + if ( result == 0 ) { + result = CWindow::MsgHandler( type, object, data ); + } + return result; +} // CGamesBrowserWindow::MsgHandler + +#endif /* 0 */ diff --git a/xwords4/franklin/frankask.h b/xwords4/franklin/frankask.h new file mode 100644 index 000000000..9999ce98c --- /dev/null +++ b/xwords4/franklin/frankask.h @@ -0,0 +1,40 @@ +// -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#if 0 +#ifndef _FRANKASK_H_ +#define _FRANKASK_H_ + +class CAskDialog : public CWindow { + + private: + U16* resultP; + U16 textY; + U16 textX; + char* question; + BOOL drawInProgress; + + public: + CAskDialog( U16* resultP, char* question, U16 numButtons, ...); + void Draw(); + S32 MsgHandler( MSG_TYPE type, CViewable *object, S32 data ); +}; + + +#endif +#endif /* 0 */ diff --git a/xwords4/franklin/frankdict.cpp b/xwords4/franklin/frankdict.cpp new file mode 100644 index 000000000..4e9a32de3 --- /dev/null +++ b/xwords4/franklin/frankdict.cpp @@ -0,0 +1,445 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4;-*- */ +/* + * Copyright 1997-2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include + +#include "dictnryp.h" +#include "frankdict.h" +#include "frankids.h" + +#ifdef CPLUS +extern "C" { +#endif + +typedef struct FrankDictionaryCtxt { + DictionaryCtxt super; + + void* base; + size_t dictSize; + BOOL isMMC; /* means the base must be free()d */ +} FrankDictionaryCtxt; + +static void frank_dictionary_destroy( DictionaryCtxt* dict ); +static void loadSpecialData( FrankDictionaryCtxt* ctxt, U8** ptr ); +static U16 countSpecials( FrankDictionaryCtxt* ctxt ); +static int tryLoadMMCFile( MPFORMAL XP_UCHAR* dictName, U8** ptrP, + size_t* size ); +static U32 ntohl_noalign(U8* n); +static U16 ntohs_noalign(U8** n); + +U16 +GetDictFlags( ebo_enumerator_t* eboe, FileLoc loc ) +{ + U16 flags = FRANK_DICT_FLAGS_ERROR; + U16 tmp; + + if ( loc == IN_RAM ) { + ebo_name_t* name = &eboe->name; + size_t size = EBO_BLK_SIZE; + U8* ptr = (U8*)OS_availaddr + FLAGS_CHECK_OFFSET; +#ifdef DEBUG + int result = +#endif + ebo_mapin( name, 0, (void*)ptr, &size, 0 ); + XP_ASSERT( result >= 0 ); + + tmp = *(U16*)ptr; + (void)ebo_unmap( ptr, size ); + + } else if ( loc == ON_MMC ) { +#ifdef DEBUG + long read = +#endif + ebo_iread( eboe->index, &tmp, 0, sizeof(tmp) ); + XP_ASSERT( read == sizeof(tmp) ); + } + + XP_DEBUGF( "raw value: 0x%x", tmp ); +#if BYTE_ORDER==LITTLE_ENDIAN + ((char*)&flags)[0] = ((char*)&tmp)[1]; + ((char*)&flags)[1] = ((char*)&tmp)[0]; +#else + flags = tmp; +#endif + + XP_DEBUGF( "GetDictFlags returning 0x%x", flags ); + return flags; +} /* GetDictFlags */ + +DictionaryCtxt* +frank_dictionary_make( MPFORMAL XP_UCHAR* dictName ) +{ + FrankDictionaryCtxt* ctxt = (FrankDictionaryCtxt*)NULL; + + ctxt = (FrankDictionaryCtxt*)XP_MALLOC(mpool, sizeof(*ctxt)); + XP_MEMSET( ctxt, 0, sizeof(*ctxt) ); + + dict_super_init( (DictionaryCtxt*)ctxt ); + + MPASSIGN( ctxt->super.mpool, mpool ); + + if ( !!dictName ) { + ebo_enumerator_t eboe; + + XP_MEMSET( &eboe.name, 0, sizeof(eboe.name) ); + + U8* ptr = (U8*)OS_availaddr + DICT_OFFSET; + size_t size = EBO_BLK_SIZE * 75; /* PENDING(ehouse) how to find size + of file */ + strcpy( (char*)eboe.name.name, (char*)dictName ); + strcpy( eboe.name.publisher, PUB_ERICHOUSE ); + strcpy( eboe.name.extension, EXT_XWORDSDICT ); + XP_DEBUGF( "makedict: looking for %s.%s\n", dictName, + &eboe.name.extension ); + int result = ebo_mapin( &eboe.name, 0, (void*)ptr, &size, 0 ); + XP_DEBUGF( "ebo_mapin returned %d; size=%d\n", result, size ); + + int flags; + if ( result >= 0 ) { + flags = GetDictFlags( &eboe, IN_RAM ); + if ( flags != 0x0001 && flags != 0x0002 && flags != 0x0003 ) { + result = -1; + } + } + + if ( result < 0 ) { + result = tryLoadMMCFile( MPPARM(mpool) dictName, &ptr, &size ); + if ( result >= 0 ) { + ctxt->isMMC = true; + } + } + + if ( result >= 0 ) { + XP_U16 numFaces; + XP_U16 facesSize; + XP_U16 charSize; + + /* save for later */ + ctxt->base = (void*)ptr; + ctxt->dictSize = size; + + ctxt->super.destructor = frank_dictionary_destroy; + + U16 flags = ntohs_noalign( &ptr ); + if ( flags == 0x0001 ) { + charSize = 1; + ctxt->super.nodeSize = 3; + } else if ( flags == 0x0002 ) { + charSize = 2; + ctxt->super.nodeSize = 3; + } else if ( flags == 0x0003 ) { + charSize = 2; + ctxt->super.nodeSize = 4; + } else { + XP_ASSERT( XP_FALSE ); + charSize = 0; /* shut up compiler */ + } + + ctxt->super.nFaces = numFaces = *ptr++; + XP_DEBUGF( "read %d faces from dict\n", numFaces ); + + facesSize = numFaces * sizeof(ctxt->super.faces16[0]); + ctxt->super.faces16 = (XP_U16*)XP_MALLOC( mpool, facesSize ); + XP_MEMSET( ctxt->super.faces16, 0, facesSize ); + + for ( XP_U16 i = 0; i < numFaces; ++i ) { + if ( charSize == 2 ) { + ++ptr; /* skip the extra byte; screw unicode for + now. :-) */ + } + ctxt->super.faces16[i] = *ptr++; + } + + ctxt->super.countsAndValues = + (XP_U8*)XP_MALLOC(mpool, numFaces*2); + + ptr += 2; /* skip xloc header */ + for ( U16 i = 0; i < numFaces*2; i += 2 ) { + ctxt->super.countsAndValues[i] = *ptr++; + ctxt->super.countsAndValues[i+1] = *ptr++; + } + + loadSpecialData( ctxt, &ptr ); + + U32 topOffset = ntohl_noalign( ptr ); + ptr += sizeof(topOffset); + XP_DEBUGF( "incremented ptr by startOffset: %ld", topOffset ); + + ctxt->super.topEdge = ptr + topOffset; + ctxt->super.base = ptr; + +#ifdef DEBUG + XP_U32 dictLength = size - (ptr - ((U8*)ctxt->base)); + /* Can't do this because size is a multiple of 0x1000 created + during the memory-mapping process. Need to figure out how to + get the actual size if it ever matter. */ +/* XP_ASSERT( (dictLength % 3) == 0 ); */ + ctxt->super.numEdges = dictLength / 3; +#endif + } + + setBlankTile( (DictionaryCtxt*)ctxt ); + + ctxt->super.name = frankCopyStr(MPPARM(mpool) dictName); + } + return (DictionaryCtxt*)ctxt; +} // frank_dictionary_make + +static int +tryLoadMMCFile( MPFORMAL XP_UCHAR* dictName, U8** ptrP, size_t* size ) +{ + int result; + ebo_enumerator_t eboe; + + for ( result = ebo_first_xobject( &eboe ); + result == EBO_OK; + result = ebo_next_xobject( &eboe ) ) { + U16 flags; + + if ( strcmp( eboe.name.publisher, PUB_ERICHOUSE ) == 0 + && strcmp( eboe.name.extension, EXT_XWORDSDICT ) == 0 + && strcmp( eboe.name.name, (char*)dictName ) == 0 + && ( ((flags = GetDictFlags(&eboe, ON_MMC)) == 0x0001) + || (flags == 0x0002) + || (flags == 0x0003) ) ) { + + XP_DEBUGF( "looking to allocate %ld bytes", eboe.size ); + + void* buf = (void*)XP_MALLOC( mpool, eboe.size ); + long read = ebo_iread (eboe.index, buf, 0, eboe.size ); + if ( read != (long)eboe.size ) { + XP_FREE( mpool, buf ); + result = -1; + } else { + *ptrP = (U8*)buf; + *size = eboe.size; + } + break; + } + } + return result; +} /* tryLoadMMCFile */ + +static U32 +ntohl_noalign( U8* np ) +{ + union { + U32 num; + unsigned char aschars[4]; + } u; + S16 i; + +#if BYTE_ORDER==LITTLE_ENDIAN + for ( i = 3; i >= 0; --i ) { + u.aschars[i] = *np++; + } +#else + for ( i = 0; i < 4; ++i ) { + u.aschars[i] = *np++; + } +#endif + XP_DEBUGF( "ntohl_noalign returning %ld", u.num ); + return u.num; +} /* ntohl_noalign */ + +static U16 +ntohs_noalign( U8** p ) +{ + U8* np = *p; + union { + U16 num; + unsigned char aschars[2]; + } u; + S16 i; + +#if BYTE_ORDER==LITTLE_ENDIAN + for ( i = 1; i >= 0; --i ) { + u.aschars[i] = *np++; + } +#else + for ( i = 0; i < 2; ++i ) { + u.aschars[i] = *np++; + } +#endif + XP_DEBUGF( "ntohl_noalign returning %ld", u.num ); + *p = np; + return u.num; +} /* */ + +static U16 +countSpecials( FrankDictionaryCtxt* ctxt ) +{ + U16 result = 0; + + for ( U16 i = 0; i < ctxt->super.nFaces; ++i ) { + if ( IS_SPECIAL(ctxt->super.faces16[i] ) ) { + ++result; + } + } + + return result; +} /* countSpecials */ + +static XP_Bitmap* +makeBitmap( FrankDictionaryCtxt* ctxt, U8** ptrp ) +{ + U8* ptr = *ptrp; + IMAGE* bitmap = (IMAGE*)NULL; + U8 nCols = *ptr++; + + if ( nCols > 0 ) { + U8 nRows = *ptr++; + U16 rowBytes = (nCols+7) / 8; + + bitmap = (IMAGE*)XP_MALLOC( ctxt->super.mpool, + sizeof(IMAGE) + (nRows * rowBytes) ); + bitmap->img_width = nCols; + bitmap->img_height = nRows; + bitmap->img_rowbytes = rowBytes; + bitmap->img_colmode = COLOR_MODE_MONO; + bitmap->img_palette = (COLOR*)NULL; + U8* dest = ((U8*)&bitmap->img_buffer) + sizeof(bitmap->img_buffer); + bitmap->img_buffer = dest; + + U8 srcByte = 0, destByte = 0; + U8 nBits = nRows*nCols; + for ( U16 i = 0; i < nBits; ++i ) { + U8 srcBitIndex = i % 8; + U8 destBitIndex = (i % nCols) % 8; + + if ( srcBitIndex == 0 ) { + srcByte = *ptr++; + } + + U8 srcMask = 1 << (7 - srcBitIndex); + U8 bit = (srcByte & srcMask) != 0; + destByte |= bit << (7 - destBitIndex); + + /* we need to put the byte if we've filled it or if we're done + with the row */ + if ( (destBitIndex==7) || ((i%nCols) == (nCols-1)) ) { + *dest++ = destByte; + destByte = 0; + } + } + } + + *ptrp = ptr; + return (XP_Bitmap*)bitmap; +} /* makeBitmap */ + +static void +loadSpecialData( FrankDictionaryCtxt* ctxt, U8** ptrp ) +{ + U16 nSpecials = countSpecials( ctxt ); + U8* ptr = *ptrp; + + XP_DEBUGF( "loadSpecialData: there are %d specials\n", nSpecials ); + + XP_UCHAR** texts = (XP_UCHAR**)XP_MALLOC( ctxt->super.mpool, + nSpecials * sizeof(*texts) ); + SpecialBitmaps* bitmaps = (SpecialBitmaps*) + XP_MALLOC( ctxt->super.mpool, nSpecials * sizeof(*bitmaps) ); + + for ( Tile i = 0; i < ctxt->super.nFaces; ++i ) { + + XP_UCHAR face = ctxt->super.faces16[(short)i]; + if ( IS_SPECIAL(face) ) { + + /* get the string */ + U8 txtlen = *ptr++; + XP_UCHAR* text = (XP_UCHAR*)XP_MALLOC(ctxt->super.mpool, txtlen+1); + XP_MEMCPY( text, ptr, txtlen ); + ptr += txtlen; + text[txtlen] = '\0'; + XP_ASSERT( face < nSpecials ); + texts[face] = text; + + XP_DEBUGF( "making bitmaps for %s", texts[face] ); + bitmaps[face].largeBM = makeBitmap( ctxt, &ptr ); + bitmaps[face].smallBM = makeBitmap( ctxt, &ptr ); + } + } + + ctxt->super.chars = texts; + ctxt->super.bitmaps = bitmaps; + + XP_DEBUGF( "loadSpecialData consumed %ld bytes", + ptr - *ptrp ); + + *ptrp = ptr; +} /* loadSpecialData */ + +#if 0 +char* +frank_dictionary_getName(DictionaryCtxt* dict ) +{ + FrankDictionaryCtxt* ctxt = (FrankDictionaryCtxt*)dict; + return ctxt->name; +} /* frank_dictionary_getName */ +#endif + +static void +frank_dictionary_destroy( DictionaryCtxt* dict ) +{ + FrankDictionaryCtxt* ctxt = (FrankDictionaryCtxt*)dict; + U16 nSpecials = countSpecials( ctxt ); + U16 i; + + if ( !!dict->chars ) { + for ( i = 0; i < nSpecials; ++i ) { + XP_UCHAR* text = dict->chars[i]; + if ( !!text ) { + XP_FREE( dict->mpool, text ); + } + } + XP_FREE( dict->mpool, dict->chars ); + } + + if ( !!dict->countsAndValues ) { + XP_FREE( dict->mpool, dict->countsAndValues ); + } + if ( !!ctxt->super.faces16 ) { + XP_FREE( dict->mpool, ctxt->super.faces16 ); + } + + if ( !!ctxt->super.bitmaps ) { + for ( i = 0; i < nSpecials; ++i ) { + XP_Bitmap bmp = ctxt->super.bitmaps[i].largeBM; + if ( !!bmp ) { + XP_FREE( ctxt->super.mpool, bmp ); + } + bmp = ctxt->super.bitmaps[i].smallBM; + if ( !!bmp ) { + XP_FREE( ctxt->super.mpool, bmp ); + } + } + XP_FREE( ctxt->super.mpool, ctxt->super.bitmaps ); + } + + if ( ctxt->isMMC ) { + XP_FREE( ctxt->super.mpool, ctxt->base ); + } else { + (void)ebo_unmap( ctxt->base, ctxt->dictSize ); + } + XP_FREE( ctxt->super.mpool, ctxt ); +} // frank_dictionary_destroy + +#ifdef CPLUS +} +#endif diff --git a/xwords4/franklin/frankdict.h b/xwords4/franklin/frankdict.h new file mode 100644 index 000000000..ba89894f7 --- /dev/null +++ b/xwords4/franklin/frankdict.h @@ -0,0 +1,47 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1999-2002 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _FRANKDICT_H_ +#define _FRANKDICT_H_ + +#include + +#ifdef CPLUS +extern "C" { +#endif + +typedef enum { + LOC_UNKNOWN, + IN_RAM, + ON_MMC +} FileLoc ; + +#define FRANK_DICT_FLAGS_ERROR 0xFFFF + +U16 GetDictFlags( ebo_enumerator_t* eboe, FileLoc loc ); + +DictionaryCtxt* frank_dictionary_make( MPFORMAL XP_UCHAR* name ); + +void debugf( char* format, ... ); + +#ifdef CPLUS +} +#endif + +#endif /* _FRANKDICT_H_ */ diff --git a/xwords4/franklin/frankdlist.cpp b/xwords4/franklin/frankdlist.cpp new file mode 100755 index 000000000..283581a7c --- /dev/null +++ b/xwords4/franklin/frankdlist.cpp @@ -0,0 +1,122 @@ +// -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- +/* + * Copyright 2001-2002 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "frankdlist.h" +#include "frankids.h" + +#include "strutils.h" + +FrankDictList::FrankDictList(MPFORMAL_NOCOMMA) +{ + MPASSIGN( this->mpool, mpool ); + fNDicts = 0; + + for ( XP_U16 i = 0; i < MAX_DICTS; ++i ) { + fDictNames[i] = (XP_UCHAR*)NULL; + } + + populateList(); +} // FrankDictList::FrankDictList + +FrankDictList::~FrankDictList() +{ + for ( XP_U16 i = 0; i < fNDicts; ++i ) { + XP_ASSERT( !!fDictNames[i] ); + XP_FREE( mpool, fDictNames[i] ); + } +} + +XP_S16 +FrankDictList::IndexForName( XP_UCHAR* name ) +{ + XP_ASSERT( !!name ); + for ( XP_S16 i = 0; i < fNDicts; ++i ) { + if ( 0 == XP_STRCMP( name, fDictNames[i] ) ) { + return i; + } + } + + XP_ASSERT(0); + return -1; +} // FrankDictList::IndexForName + +XP_S16 +FrankDictList::dictListInsert( ebo_enumerator_t* eboep, FileLoc loc ) +{ + U16 flags; + if ( strcmp( eboep->name.publisher, PUB_ERICHOUSE ) == 0 + && strcmp( eboep->name.extension, EXT_XWORDSDICT ) == 0 + && ( ((flags = GetDictFlags( eboep, loc )) == 0x0001 ) + || (flags == 0x0002) || (flags == 0x0003) ) ) { + + XP_UCHAR* newName = (XP_UCHAR*)eboep->name.name; + XP_U16 nDicts = fNDicts; + XP_S16 pred; // predecessor + + // it's a keeper. Insert in alphabetical order + for ( pred = nDicts - 1; pred >= 0; --pred ) { + XP_S16 cmps = XP_STRCMP( newName, fDictNames[pred] ); + + // 0 means a duplicate, e.g one on MMC and another in + // RAM. Drop the dup in favor of the RAM copy. + if ( cmps == 0 ) { + return -1; + } + + if ( cmps > 0 ) { + break; // found it + } + } + + /* Now move any above the new location up */ + XP_S16 newLoc = pred + 1; + for ( XP_U16 j = nDicts; j > newLoc; --j ) { + fDictNames[j] = fDictNames[j-1]; + } + + XP_ASSERT( newLoc >= 0 ); + fDictNames[newLoc] = copyString( MPPARM(mpool) newName ); + fDictLocs[newLoc] = loc; + ++fNDicts; + + return newLoc; + + } else { + return -1; + } +} + +void +FrankDictList::populateList() +{ + int result; + ebo_enumerator_t eboe; + + for ( result = ebo_first_object( &eboe ); + result == EBO_OK; + result = ebo_next_object( &eboe ) ) { + dictListInsert( &eboe, IN_RAM ); + } + + for ( result = ebo_first_xobject( &eboe ); + result == EBO_OK; + result = ebo_next_xobject( &eboe ) ) { + dictListInsert( &eboe, ON_MMC ); + } +} // FrankDictList::populateList diff --git a/xwords4/franklin/frankdlist.h b/xwords4/franklin/frankdlist.h new file mode 100755 index 000000000..6affbd04f --- /dev/null +++ b/xwords4/franklin/frankdlist.h @@ -0,0 +1,65 @@ +// -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- +/* + * Copyright 2001-2002 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _FRANKDLIST_H_ +#define _FRANKDLIST_H_ + +#include + +extern "C" { +#include "comtypes.h" +} + +#include "mempool.h" +#include "frankdict.h" + +#define MAX_DICTS 16 + +class FrankDictList { + + public: + + FrankDictList( MPFORMAL_NOCOMMA ); + ~FrankDictList(); + + XP_U16 GetDictCount() { return fNDicts; } + + XP_UCHAR* GetNthName( XP_U16 n ) { + XP_ASSERT( n < fNDicts ); + return fDictNames[n]; + } + + FileLoc GetNthLoc( XP_U16 n ) { + XP_ASSERT( n < fNDicts ); + return fDictLocs[n]; + } + + XP_S16 IndexForName( XP_UCHAR* name ); + + private: + void populateList(); + XP_S16 dictListInsert( ebo_enumerator_t* eboep, FileLoc loc ); + + XP_U16 fNDicts; + XP_UCHAR* fDictNames[MAX_DICTS]; + FileLoc fDictLocs[MAX_DICTS]; + + MPSLOT +}; +#endif diff --git a/xwords4/franklin/frankdraw.cpp b/xwords4/franklin/frankdraw.cpp new file mode 100644 index 000000000..c0c633fcd --- /dev/null +++ b/xwords4/franklin/frankdraw.cpp @@ -0,0 +1,638 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4;-*- */ +/* + * Copyright 1997-2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include + +extern "C" { +#include "xptypes.h" +#include "board.h" +#include "draw.h" +#include "mempool.h" +} /* extern "C" */ + +#include "frankmain.h" + +static void +insetRect( RECT* r, short i ) +{ + r->x += i; + r->y += i; + + i *= 2; + r->width -= i; + r->height -= i; +} /* insetRect */ + +static void +eraseRect(FrankDrawCtx* dctx, RECT* rect ) +{ + dctx->window->DrawRectFilled( rect, COLOR_WHITE ); +} /* eraseRect */ + +static XP_Bool +frank_draw_boardBegin( DrawCtx* p_dctx, const DictionaryCtxt* dict, + const XP_Rect* rect, XP_Bool hasfocus ) +{ + return XP_TRUE; +} /* draw_finish */ + +static void +frank_draw_boardFinished( DrawCtx* p_dctx ) +{ + // GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; +} /* draw_finished */ + +static void +XP_RectToRECT( RECT* d, const XP_Rect* s ) +{ + d->x = s->left; + d->y = s->top; + d->width = s->width; + d->height = s->height; +} /* XP_RectToRECT */ + +/* Called by methods that draw cells, i.e. drawCell and drawCursor, to do + * common prep of the area. + */ +static void +cellDrawPrep( FrankDrawCtx* dctx, const XP_Rect* xprect, RECT* insetRect ) +{ + XP_RectToRECT( insetRect, xprect ); + + ++insetRect->height; + ++insetRect->width; + + eraseRect( dctx, insetRect ); +} /* cellDrawPrep */ + +static XP_Bool +frank_draw_drawCell( DrawCtx* p_dctx, const XP_Rect* xprect, + const XP_UCHAR* letters, const XP_Bitmap* bitmap, + Tile tile, XP_S16 owner, XWBonusType bonus, + HintAtts hintAtts, + XP_Bool isBlank, XP_Bool isPending, XP_Bool isStar ) +{ + FrankDrawCtx* dctx = (FrankDrawCtx*)p_dctx; + RECT rectInset; + XP_Bool showGrid = XP_TRUE; + + cellDrawPrep( dctx, xprect, &rectInset ); + + if ( !!letters ) { + if ( *letters == LETTER_NONE ) { + if ( bonus != BONUS_NONE ) { + XP_ASSERT( bonus <= 4 ); +#ifdef USE_PATTERNS + dctx->window->DrawTiledImage( xprect->left, xprect->top, + xprect->width, + xprect->height, + &dctx->bonusImages[bonus-1] ); +#else + RECT filledR = rectInset; + COLOR color; + insetRect( &filledR, 1 ); + + switch( bonus ) { + case BONUS_DOUBLE_LETTER: + color = COLOR_GRAY53; + break; + case BONUS_TRIPLE_LETTER: + color = COLOR_GRAY40; + break; + case BONUS_DOUBLE_WORD: + color = COLOR_GRAY27; + break; + case BONUS_TRIPLE_WORD: + color = COLOR_GRAY13; + break; + default: + color = COLOR_WHITE; /* make compiler happy */ + XP_ASSERT(0); + } + + dctx->window->DrawRectFilled( &filledR, color ); +#endif + } + + if ( isStar ) { + dctx->window->DrawImage( xprect->left+2, xprect->top+2, + &dctx->startMark ); + } + + } else { + dctx->window->DrawText( (const char*)letters, rectInset.x+2, + rectInset.y+1 ); + } + } else if ( !!bitmap ) { + dctx->window->DrawImage( xprect->left+2, xprect->top+2, + (IMAGE*)bitmap ); + } + + if ( showGrid ) { + COLOR color = isBlank? COLOR_GRAY53:COLOR_BLACK; + dctx->window->DrawRect( &rectInset, color ); + } + if ( isPending ) { + insetRect( &rectInset, 1 ); + dctx->window->InvertRect( &rectInset ); + } + + return XP_TRUE; +} /* frank_draw_drawCell */ + +static void +frank_draw_invertCell( DrawCtx* p_dctx, const XP_Rect* rect ) +{ + FrankDrawCtx* dctx = (FrankDrawCtx*)p_dctx; + RECT r; + + XP_RectToRECT( &r, rect ); + r.x += 3; + r.y += 3; + r.width -= 5; + r.height -= 5; + +/* insetRect( &r, 2 ); */ + dctx->window->InvertRect( &r ); + GUI_UpdateNow(); +} /* frank_draw_invertCell */ + +static XP_Bool +frank_draw_trayBegin( DrawCtx* p_dctx, const XP_Rect* rect, XP_U16 owner, + XP_Bool hasfocus ) +{ +/* FrankDrawCtx* dctx = (FrankDrawCtx*)p_dctx; */ +/* clip? */ +/* eraseRect( dctx, rect ); */ + return XP_TRUE; +} /* frank_draw_trayBegin */ + +static void +frank_draw_drawTile( DrawCtx* p_dctx, const XP_Rect* xprect, + const XP_UCHAR* letter, + XP_Bitmap* bitmap, XP_S16 val, XP_Bool highlighted ) +{ + FrankDrawCtx* dctx = (FrankDrawCtx*)p_dctx; + char numbuf[3]; + U32 width; + XP_Rect insetR = *xprect; + RECT rect; + + XP_RectToRECT( &rect, xprect ); + + eraseRect( dctx, &rect ); + + /* frame the tile */ + + dctx->window->DrawRect( &rect, COLOR_BLACK ); + + if ( !!letter ) { + if ( *letter != LETTER_NONE ) { /* blank */ + dctx->window->DrawText( (char*)letter, rect.x+1, rect.y+1, + dctx->trayFont ); + } + } else if ( !!bitmap ) { + dctx->window->DrawImage( rect.x+2, rect.y+3, (IMAGE*)bitmap ); + } + + if ( val >= 0 ) { + sprintf( (char*)numbuf, (const char*)"%d", val ); + width = GUI_TextWidth( dctx->valFont, numbuf, strlen(numbuf)); + U16 height = GUI_FontHeight( dctx->valFont ); + dctx->window->DrawText( (char*)numbuf, rect.x+rect.width - width - 1, + rect.y + rect.height - height - 1, + dctx->valFont ); + } + + if ( highlighted ) { + insetRect( &rect, 1 ); + dctx->window->DrawRect( &rect, COLOR_BLACK ); + } +} /* frank_draw_drawTile */ + +static void +frank_draw_drawTileBack( DrawCtx* p_dctx, const XP_Rect* xprect ) +{ +/* FrankDrawCtx* dctx = (FrankDrawCtx*)p_dctx; */ + + frank_draw_drawTile( p_dctx, xprect, (XP_UCHAR*)"?", + (XP_Bitmap*)NULL, -1, XP_FALSE ); +} /* frank_draw_drawTileBack */ + +static void +frank_draw_drawTrayDivider( DrawCtx* p_dctx, const XP_Rect* rect, + XP_Bool selected ) +{ + FrankDrawCtx* dctx = (FrankDrawCtx*)p_dctx; + RECT winRect; + XP_RectToRECT( &winRect, rect ); + + eraseRect( dctx, &winRect ); + + ++winRect.x; + winRect.width -= 2; + + COLOR color; + if ( selected ) { + color = COLOR_GRAY27; + } else { + color = COLOR_BLACK; + } + + dctx->window->DrawRectFilled( &winRect, color ); +} /* frank_draw_drawTrayDivider */ + +static void +frank_draw_clearRect( DrawCtx* p_dctx, const XP_Rect* rectP ) +{ + FrankDrawCtx* dctx = (FrankDrawCtx*)p_dctx; + RECT rect; + + XP_RectToRECT( &rect, rectP ); + + eraseRect( dctx, &rect ); + +} /* frank_draw_clearRect */ + +static void +frank_draw_drawBoardArrow( DrawCtx* p_dctx, const XP_Rect* xprect, + XWBonusType cursorBonus, XP_Bool vertical, + HintAtts hintAtts ) +{ + FrankDrawCtx* dctx = (FrankDrawCtx*)p_dctx; + RECT rect; + + cellDrawPrep( dctx, xprect, &rect ); + + dctx->window->DrawImage( rect.x+3, rect.y+2, + vertical?&dctx->downcursor:&dctx->rightcursor ); + /* frame the cell */ + dctx->window->DrawRect( &rect, COLOR_BLACK ); +} /* frank_draw_drawBoardArrow */ + +static void +frank_draw_scoreBegin( DrawCtx* p_dctx, const XP_Rect* rect, + XP_U16 numPlayers, XP_Bool hasfocus ) +{ + FrankDrawCtx* dctx = (FrankDrawCtx*)p_dctx; + RECT r; + + XP_RectToRECT( &r, rect ); + eraseRect( dctx, &r ); +} /* frank_draw_scoreBegin */ + +static void +frank_draw_measureRemText( DrawCtx* p_dctx, const XP_Rect* r, + XP_S16 nTilesLeft, + XP_U16* width, XP_U16* height ) +{ + FrankDrawCtx* dctx = (FrankDrawCtx*)p_dctx; + + *height = SCORE_HEIGHT; + + char buf[15]; + sprintf( (char*)buf, "rem:%d", nTilesLeft ); + *width = GUI_TextWidth( dctx->scoreFnt, (char*)buf, + strlen(buf) ); +} /* frank_draw_measureRemText */ + +static void +frank_draw_drawRemText( DrawCtx* p_dctx, const XP_Rect* rInner, + const XP_Rect* rOuter, XP_S16 nTilesLeft ) +{ + FrankDrawCtx* dctx = (FrankDrawCtx*)p_dctx; + char buf[15]; + sprintf( (char*)buf, "rem:%d", nTilesLeft ); + dctx->window->DrawText( (char*)buf, rInner->left, rInner->top, + dctx->scoreFnt ); +} /* frank_draw_drawRemText */ + +static XP_U16 +scoreWidthAndText( char* buf, const FONT* font, const DrawScoreInfo* dsi ) +{ + char* borders = ""; + if ( dsi->isTurn ) { + borders = "*"; + } + + sprintf( buf, "%s%.3d", borders, dsi->score ); + if ( dsi->nTilesLeft >= 0 ) { + char nbuf[10]; + sprintf( nbuf, ":%d", dsi->nTilesLeft ); + strcat( buf, nbuf ); + } + strcat( buf, borders ); + + S32 width = GUI_TextWidth( font, buf, strlen(buf) ); + return width; +} /* scoreWidthAndText */ + +static void +frank_draw_measureScoreText( DrawCtx* p_dctx, const XP_Rect* r, + const DrawScoreInfo* dsi, + XP_U16* width, XP_U16* height ) +{ + FrankDrawCtx* dctx = (FrankDrawCtx*)p_dctx; + char buf[20]; + const FONT* font = dsi->selected? dctx->scoreFntBold:dctx->scoreFnt; + + *height = SCORE_HEIGHT; + *width = scoreWidthAndText( buf, font, dsi ); +} /* frank_draw_measureScoreText */ + +static void +frank_draw_score_drawPlayer( DrawCtx* p_dctx, + const XP_Rect* rInner, const XP_Rect* rOuter, + const DrawScoreInfo* dsi ) +{ + FrankDrawCtx* dctx = (FrankDrawCtx*)p_dctx; + char buf[20]; + XP_U16 x; + const FONT* font = dsi->selected? dctx->scoreFntBold:dctx->scoreFnt; + + S32 width = scoreWidthAndText( buf, font, dsi ); + + x = rInner->left + ((rInner->width - width) /2); + + dctx->window->DrawText( buf, x, rInner->top, font ); +} /* frank_draw_score_drawPlayer */ + +static void +frank_draw_score_pendingScore( DrawCtx* p_dctx, const XP_Rect* rect, + XP_S16 score, XP_U16 playerNum ) +{ + FrankDrawCtx* dctx = (FrankDrawCtx*)p_dctx; + char buf[5]; + XP_U16 left; + + if ( score >= 0 ) { + sprintf( buf, "%.3d", score ); + } else { + strcpy( buf, "???" ); + } + + RECT r; + XP_RectToRECT( &r, rect ); + eraseRect( dctx, &r ); + + left = r.x+1; + dctx->window->DrawText( "Pts:", left, r.y, dctx->valFont ); + dctx->window->DrawText( buf, left, r.y+(r.height/2), + dctx->scoreFnt ); +} /* frank_draw_score_pendingScore */ + +static void +frank_draw_scoreFinished( DrawCtx* p_dctx ) +{ + +} /* frank_draw_scoreFinished */ + +static U16 +frankFormatTimerText( char* buf, XP_S16 secondsLeft ) +{ + XP_U16 minutes, seconds; + XP_U16 nChars = 0; + + if ( secondsLeft < 0 ) { + *buf++ = '-'; + secondsLeft *= -1; + ++nChars; + } + + minutes = secondsLeft / 60; + seconds = secondsLeft % 60; + + char secBuf[6]; + sprintf( secBuf, "0%d", seconds ); + + nChars += sprintf( buf, "%d:%s", minutes, + secBuf[2] == '\0'? secBuf:&secBuf[1] ); + return nChars; +} /* frankFormatTimerText */ + +static void +frank_draw_drawTimer( DrawCtx* p_dctx, const XP_Rect* rInner, + const XP_Rect* rOuter, XP_U16 player, + XP_S16 secondsLeft ) +{ + FrankDrawCtx* dctx = (FrankDrawCtx*)p_dctx; + char buf[12]; + + (void)frankFormatTimerText( buf, secondsLeft ); + + XP_DEBUGF( "drawing timer text: %s at %d,%d", buf, rInner->left, + rInner->top ); + RECT r; + XP_RectToRECT( &r, rInner ); + eraseRect( dctx, &r ); + dctx->window->DrawText( buf, rInner->left, rInner->top, dctx->scoreFnt ); +} /* frank_draw_drawTimer */ + +static XP_UCHAR* +frank_draw_getMiniWText( DrawCtx* p_dctx, XWMiniTextType whichText ) +{ + char* str = (char*)NULL; + + switch( whichText ) { + case BONUS_DOUBLE_LETTER: + str = "Double letter"; break; + case BONUS_DOUBLE_WORD: + str = "Double word"; break; + case BONUS_TRIPLE_LETTER: + str = "Triple letter"; break; + case BONUS_TRIPLE_WORD: + str = "Triple word"; break; + case INTRADE_MW_TEXT: + str = "Click D when done"; break; + default: + XP_ASSERT( XP_FALSE ); + } + return (XP_UCHAR*)str; +} /* frank_draw_getMiniWText */ + +static void +frank_draw_measureMiniWText( DrawCtx* p_dctx, const XP_UCHAR* str, + XP_U16* widthP, XP_U16* heightP ) +{ + FrankDrawCtx* dctx = (FrankDrawCtx*)p_dctx; + *widthP = 6 + GUI_TextWidth( dctx->scoreFnt, (const char*)str, + strlen((const char*)str) ); + *heightP = 6 + GUI_FontHeight( dctx->scoreFnt ); +} /* frank_draw_measureMiniWText */ + +static void +frank_draw_drawMiniWindow( DrawCtx* p_dctx, const XP_UCHAR* text, + const XP_Rect* rect, void** closureP ) +{ + FrankDrawCtx* dctx = (FrankDrawCtx*)p_dctx; + RECT r; + + XP_RectToRECT( &r, rect ); + + eraseRect( dctx, &r ); + + --r.width; + --r.height; + ++r.x; + ++r.y; + dctx->window->DrawRect( &r, COLOR_BLACK ); + --r.x; + --r.y; + eraseRect( dctx, &r ); + dctx->window->DrawRect( &r, COLOR_BLACK ); + + dctx->window->DrawText( (const char*)text, r.x+2, r.y+2, dctx->scoreFnt ); +} /* frank_draw_drawMiniWindow */ + +static void +frank_draw_eraseMiniWindow( DrawCtx* p_dctx, const XP_Rect* rect, + XP_Bool lastTime, + void** closure, XP_Bool* invalUnder ) +{ +/* FrankDrawCtx* dctx = (FrankDrawCtx*)p_dctx; */ + *invalUnder = XP_TRUE; +} /* frank_draw_eraseMiniWindow */ + +#define SET_GDK_COLOR( c, r, g, b ) { \ + c.red = (r); \ + c.green = (g); \ + c.blue = (b); \ +} +static void +draw_doNothing( DrawCtx* dctx, ... ) +{ +} /* draw_doNothing */ + + +const unsigned char downcursor_bits[] = { + 0x00, 0x00, + 0x10, 0x00, + 0x10, 0x00, + 0x10, 0x00, + 0xfe, 0x00, + 0x7c, 0x00, + 0x38, 0x00, + 0x10, 0x00, + 0x00, 0x00, +}; + +const unsigned char rightcursor_bits[] = { + 0x00, 0x00, 0x10, 0x00, 0x18, 0x00, 0x1c, 0x00, 0xfe, 0x00, 0x1c, 0x00, + 0x18, 0x00, 0x10, 0x00, 0x00, 0x00 +}; +const unsigned char startMark_bits[] = { + 0xc1, 0x80, /* ##-- ---# # */ + 0xe3, 0x80, /* ###- --## # */ + 0x77, 0x00, /* -### -### - */ + 0x3e, 0x00, /* --## ###- - */ + 0x1c, 0x00, /* ---# ##-- - */ + 0x3e, 0x00, /* --## ###- - */ + 0x77, 0x00, /* -### -### - */ + 0xe3, 0x80, /* ###- --## # */ + 0xc1, 0x80, /* ##-- ---# # */ +}; + +#ifdef USE_PATTERNS +const unsigned char bonus_bits[][4] = { + { 0x88, 0x44, 0x22, 0x11 }, + { 0xaa, 0x55, 0xaa, 0x55 }, + { 0xCC, 0x66, 0x33, 0x99 }, + { 0xCC, 0xCC, 0x33, 0x33 } +}; +#endif + +DrawCtx* +frank_drawctxt_make( MPFORMAL CWindow* window ) +{ + FrankDrawCtx* dctx = (FrankDrawCtx*)XP_MALLOC( mpool, + sizeof(FrankDrawCtx) ); + U16 i; + + dctx->vtable = (DrawCtxVTable*) + XP_MALLOC( mpool, sizeof(*(((FrankDrawCtx*)dctx)->vtable)) ); + + for ( i = 0; i < sizeof(*dctx->vtable)/4; ++i ) { + ((void**)(dctx->vtable))[i] = draw_doNothing; + } + +/* SET_VTABLE_ENTRY( dctx, draw_destroyCtxt, frank_ ); */ + SET_VTABLE_ENTRY( dctx->vtable, draw_boardBegin, frank ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawCell, frank ); + SET_VTABLE_ENTRY( dctx->vtable, draw_invertCell, frank ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_boardFinished, frank ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_trayBegin, frank ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTile, frank ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTileBack, frank ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTrayDivider, frank ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_clearRect, frank ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawBoardArrow, frank ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_scoreBegin, frank ); + SET_VTABLE_ENTRY( dctx->vtable, draw_measureRemText, frank ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawRemText, frank ); + SET_VTABLE_ENTRY( dctx->vtable, draw_measureScoreText, frank ); + SET_VTABLE_ENTRY( dctx->vtable, draw_score_drawPlayer, frank ); + SET_VTABLE_ENTRY( dctx->vtable, draw_score_pendingScore, frank ); + SET_VTABLE_ENTRY( dctx->vtable, draw_scoreFinished, frank ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTimer, frank ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_getMiniWText, frank ); + SET_VTABLE_ENTRY( dctx->vtable, draw_measureMiniWText, frank ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawMiniWindow, frank ); + SET_VTABLE_ENTRY( dctx->vtable, draw_eraseMiniWindow, frank ); + + dctx->window = window; + + dctx->valFont = GUI_GetFont( 9, CTRL_NORMAL ); + dctx->scoreFnt = GUI_GetFont( 12, CTRL_NORMAL ); + dctx->scoreFntBold = GUI_GetFont( 12, CTRL_BOLD ); + dctx->trayFont = GUI_GetFont( 16, CTRL_NORMAL ); + + IMAGE downcursor = { 9, 9, 2, + COLOR_MODE_MONO, 0, (const COLOR *) 0, + (U8*)downcursor_bits }; + XP_MEMCPY( (IMAGE*)&dctx->downcursor, &downcursor, + sizeof(dctx->downcursor) ); + + IMAGE rightcursor = { 9, 9, 2, + COLOR_MODE_MONO, 0, (const COLOR *) 0, + (U8*)rightcursor_bits }; + XP_MEMCPY( (IMAGE*)&dctx->rightcursor, &rightcursor, + sizeof(dctx->rightcursor) ); + + IMAGE startMark = { 9, 9, 2, + COLOR_MODE_MONO, 0, (const COLOR *) 0, + (U8*)startMark_bits }; + XP_MEMCPY( (IMAGE*)&dctx->startMark, &startMark, + sizeof(dctx->startMark) ); + +#ifdef USE_PATTERNS + for ( i = 0; i < BONUS_LAST; ++i ) { + IMAGE bonus = { 8, 4, 1, + COLOR_MODE_MONO, 0, (const COLOR *) 0, + (U8*)bonus_bits[i] }; + XP_MEMCPY( (IMAGE*)&dctx->bonusImages[i], &bonus, + sizeof(dctx->bonusImages[i]) ); + } +#endif + return (DrawCtx*)dctx; +} /* frank_drawctxt_make */ diff --git a/xwords4/franklin/frankdraw.h b/xwords4/franklin/frankdraw.h new file mode 100644 index 000000000..19b26a177 --- /dev/null +++ b/xwords4/franklin/frankdraw.h @@ -0,0 +1,31 @@ +// -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- +/* + * Copyright 1999-2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include + +extern "C" { + +#include "frankmain.h" +#include "draw.h" +#include "board.h" + +} /* extern "C" */ + +DrawCtx* frank_drawctxt_make( MPFORMAL CWindow* win ); + diff --git a/xwords4/franklin/frankgamesdb.cpp b/xwords4/franklin/frankgamesdb.cpp new file mode 100644 index 000000000..b76f90cb5 --- /dev/null +++ b/xwords4/franklin/frankgamesdb.cpp @@ -0,0 +1,313 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4;-*- */ +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include "frankgamesdb.h" +#include "frankids.h" + +extern "C" { +# include "xptypes.h" +} + +CGamesDB::CGamesDB(MPFORMAL const XP_UCHAR* fileName) +{ + this->nRecordsUsed = 0; + this->nRecordsAllocated = 0; + this->lockedIndex = -1; + MPASSIGN( this->mpool, mpool ); + + fRecords = (DBRecord*)NULL; + + // create or load file + memset( &this->xwfile, 0, sizeof(xwfile) ); + strcpy( (char*)this->xwfile.name, (char*)fileName ); + strcpy( this->xwfile.publisher, PUB_ERICHOUSE ); + strcpy( this->xwfile.extension, EXT_XWORDSGAMES ); + + size_t size = EBO_BLK_SIZE; + int result = ebo_new( &this->xwfile, size, FALSE ); + BOOL exists = result < 0; + + XP_DEBUGF( "exists=%d", exists ); + + if ( exists ) { + readFromFile( &xwfile ); + } +} // CGamesDBn + +CGamesDB::~CGamesDB() +{ + this->writeToFile(); + + U16 nRecords = this->nRecordsUsed; + for ( U16 i = 0; i < nRecords; ++i ) { + DBRecord* record = &fRecords[i]; + XP_FREE( mpool, record->datum ); + if ( !!record->name ) { + XP_FREE( mpool, record->name ); + } + } + + XP_FREE( mpool, fRecords ); +} // ~CGamesDB + +void* +CGamesDB::getNthRecord( U16 index, U16* recordLen ) +{ + XP_ASSERT( index < this->nRecordsUsed ); + XP_ASSERT( this->lockedIndex < 0 ); + this->lockedIndex = index; + *recordLen = fRecords[index].length; + + XP_DEBUGF( "getNthRecord(%d) returning len=%d", index, *recordLen ); + + return fRecords[index].datum; +} // getNthRecord + +XP_UCHAR* +CGamesDB::getNthName( U16 index ) +{ + XP_ASSERT( index < this->nRecordsUsed ); + XP_DEBUGF( "returning %dth name: %s", index, fRecords[index].name ); + return fRecords[index].name; +} /* getNthName */ + +void +CGamesDB::recordRelease( U16 index ) +{ + XP_ASSERT( this->lockedIndex == index ); + this->lockedIndex = -1; +} // recordRelease + +void +CGamesDB::putNthRecord( U16 index, void* ptr, U16 len ) +{ + XP_DEBUGF( "putNthRecord(%d,ptr,%d)", index, len ); + XP_ASSERT( index <= nRecordsUsed ); + + if ( index == nRecordsAllocated ) { /* need a new one */ + ensureSpace( nRecordsUsed+1 ); + } else if ( !!fRecords[index].datum ) { + XP_FREE(mpool, fRecords[index].datum ); + } + + void* datap = XP_MALLOC( mpool, len ); + XP_MEMCPY( datap, ptr, len ); + fRecords[index].datum = datap; + fRecords[index].length = len; + + if ( index == this->nRecordsUsed ) { + ++this->nRecordsUsed; + } +} // putNthRecord + +void +CGamesDB::putNthName( U16 index, XP_UCHAR* name ) +{ + XP_ASSERT( index <= this->nRecordsUsed ); + + if ( index == nRecordsAllocated ) { /* need a new one */ + ensureSpace( nRecordsUsed+1 ); + } else if ( !!fRecords[index].name ) { + XP_FREE( mpool, fRecords[index].name ); + } + + fRecords[index].name = frankCopyStr( MPPARM(mpool) name); + if ( index == this->nRecordsUsed ) { + ++this->nRecordsUsed; + } +} /* putNthName */ + +void +CGamesDB::removeNthRecord( U16 index ) +{ + XP_ASSERT( index < this->nRecordsUsed ); + + if ( !!fRecords[index].datum ) { + XP_FREE( mpool, fRecords[index].datum ); + if ( !!fRecords[index].name ) { + XP_FREE( mpool, fRecords[index].name ); + } + } + + U16 nRecords = --this->nRecordsUsed; + for ( U16 i = index; i < nRecords; ++i ) { + fRecords[i] = fRecords[i+1]; + } + fRecords[nRecords].datum = NULL; + fRecords[nRecords].length = 0; +} // removeNthRecord + +U16 +CGamesDB::duplicateNthRecord( U16 index ) +{ + XP_ASSERT( index < this->nRecordsUsed ); + + ensureSpace( nRecordsUsed + 1 ); + + U16 newIndex = index + 1; + for ( U16 i = this->nRecordsUsed; i > newIndex; --i ) { + fRecords[i] = fRecords[i-1]; + } + ++this->nRecordsUsed; + + U16 len = fRecords[newIndex].length = fRecords[index].length; + void* data = XP_MALLOC( mpool, len ); + XP_MEMCPY( data, fRecords[index].datum, len ); + fRecords[newIndex].datum = data; + fRecords[newIndex].name = + frankCopyStr( MPPARM(mpool) fRecords[index].name ); + + XP_DEBUGF( "done with duplicateNthRecord; returning %d", newIndex ); + + return newIndex; +} /* duplicateNthRecord */ + +U16 +CGamesDB::countRecords() +{ + return this->nRecordsUsed; +} // countRecords + +void +CGamesDB::readFromFile( const ebo_name_t* xwfile ) +{ + size_t size = EBO_BLK_SIZE; + void* vmbase = (void*)ebo_roundup(OS_availaddr+GAMES_DB_OFFSET); +#ifdef DEBUG + int result = ebo_mapin( xwfile, 0, vmbase, &size, 1 ); + XP_ASSERT( result >= 0 ); +#else + (void)ebo_mapin( xwfile, 0, vmbase, &size, 1 ); +#endif + XP_ASSERT( ((unsigned long)vmbase & 0x01) == 0 ); + + U16 nRecords; + unsigned char* ptr = (unsigned char*)vmbase; + nRecords = *((U16*)ptr)++; + XP_DEBUGF( "nRecords = %d", nRecords ); + this->nRecordsUsed = this->nRecordsAllocated = nRecords; + + fRecords = (DBRecord*)XP_MALLOC( mpool, nRecords * sizeof(fRecords[0]) ); + + for ( U16 i = 0; i < nRecords; ++i ) { + DBRecord* record = &fRecords[i]; + U8 nameLen = *ptr++; + XP_UCHAR* name = (XP_UCHAR*)NULL; + + if ( nameLen > 0 ) { + name = (XP_UCHAR*)XP_MALLOC(mpool, nameLen+1); + XP_MEMCPY( name, ptr, nameLen ); + name[nameLen] = '\0'; + ptr += nameLen; + } + record->name = name; + + /* fix alignment */ + while ( ((unsigned long)ptr & 0x00000001) != 0 ) { + ++ptr; + } + + U16 len = record->length = *((U16*)ptr)++; + XP_DEBUGF( "read len %d from offset %ld", len, + (unsigned long)ptr - (unsigned long)vmbase - 2 ); + + record->datum = XP_MALLOC( mpool, len ); + XP_MEMCPY( record->datum, ptr, len ); + ptr += len; + + XP_DEBUGF( "Read %dth record", i ); + XP_ASSERT( ((unsigned char*)vmbase) + size >= ptr ); + } + + (void)ebo_unmap( vmbase, size ); + + XP_DEBUGF( "read %d records from file", nRecords ); +} // readFromFile + +void +CGamesDB::writeToFile() +{ + size_t size = EBO_BLK_SIZE; + void* vmbase = (void*)ebo_roundup(OS_availaddr+GAMES_DB_OFFSET); +#ifdef DEBUG + int result = ebo_mapin( &this->xwfile, 0, vmbase, &size, 1 ); + XP_ASSERT( result >= 0 ); +#else + (void)ebo_mapin( &this->xwfile, 0, vmbase, &size, 1 ); +#endif + XP_ASSERT( ((unsigned long)vmbase & 0x01) == 0 ); + + U16 nRecords = this->nRecordsUsed; + unsigned char* ptr = (unsigned char*)vmbase; + + *(U16*)ptr = nRecords; + ptr += sizeof(U16); + + for ( U16 i = 0; i < nRecords; ++i ) { + + XP_UCHAR* name = fRecords[i].name; + U16 slen = !!name? XP_STRLEN( name ): 0; + *ptr++ = slen; + if ( slen > 0 ) { + XP_ASSERT( slen < 0x00FF ); + XP_MEMCPY( ptr, name, slen ); + ptr += slen; + } + + /* fix alignment */ + while ( ((unsigned long)ptr & 0x00000001) != 0 ) { + ++ptr; + } + + U16 len = fRecords[i].length; + XP_DEBUGF( "writing len %d at offset %ld", len, + (unsigned long)ptr - (unsigned long)vmbase ); + *((U16*)ptr)++ = len; + + XP_MEMCPY( ptr, fRecords[i].datum, len ); + ptr += len; + } + + ebo_unmap( vmbase, size ); + + XP_WARNF( "finished writing %d recs to file", nRecords ); +} // writeToFile + +void +CGamesDB::ensureSpace( U16 nNeeded ) +{ + if ( this->nRecordsAllocated < nNeeded ) { + U16 newLen = nNeeded * sizeof(fRecords[0]); + if ( !!fRecords ) { + fRecords = (DBRecord*)XP_REALLOC( mpool, fRecords, newLen ); + } else { + fRecords = (DBRecord*)XP_MALLOC( mpool, newLen ); + + } + + U16 sizeAdded = (nNeeded - this->nRecordsAllocated) * sizeof(*fRecords); + XP_MEMSET( fRecords + this->nRecordsAllocated, 0x00, sizeAdded ); + + this->nRecordsAllocated = nNeeded; + XP_DEBUGF( "ensureSpace: increasing nRecordsAllocated to %d (len=%d)", + nRecordsAllocated, newLen ); + } +} /* ensureSpace */ diff --git a/xwords4/franklin/frankgamesdb.h b/xwords4/franklin/frankgamesdb.h new file mode 100644 index 000000000..81d178964 --- /dev/null +++ b/xwords4/franklin/frankgamesdb.h @@ -0,0 +1,81 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _FRANKGAMESDB_H_ +#define _FRANKGAMESDB_H_ + +#include +#include +#include "sys.h" +#include "gui.h" +#include "comtypes.h" +#include "mempool.h" + +/* #include "ebm_object.h" */ + +struct DBRecord { + U16 length; + void* datum; + XP_UCHAR* name; +}; + +class CGamesDB { + + private: + U16 nRecordsUsed; + U16 nRecordsAllocated; + DBRecord* fRecords; + S16 lockedIndex; + ebo_name_t xwfile; + + void readFromFile( const ebo_name_t* xwfile ); + void writeToFile(); + void ensureSpace( U16 nNeeded ); + + MPSLOT + + public: + /* create or open if already exists */ + CGamesDB(MPFORMAL const XP_UCHAR* fileName); + + /* commit optimized in-memory representation to a flat "file" */ + ~CGamesDB(); + + /* Return a ptr (read-only by convention!!) to the beginning of the + record's data. The ptr's valid until recordRelease is called. */ + void* getNthRecord( U16 index, U16* recordLen ); + void recordRelease( U16 index ); + XP_UCHAR* getNthName( U16 index ); + + /* put data into a record, adding a new one, replacing or appending to an + existing one */ + void putNthRecord( U16 index, void* ptr, U16 len ); + void appendNthRecord( U16 index, void* ptr, U16 len ); + void putNthName( U16 index, XP_UCHAR* name ); + + U16 duplicateNthRecord( U16 index ); + void removeNthRecord( U16 index ); + + /* how many records do I have. indices passed by getter methods that are + >= this number are illegal. setters can pass a number = to this, in + which case we create a new record. */ + U16 countRecords(); +}; + +#endif diff --git a/xwords4/franklin/frankids.h b/xwords4/franklin/frankids.h new file mode 100644 index 000000000..6f9cd972b --- /dev/null +++ b/xwords4/franklin/frankids.h @@ -0,0 +1,91 @@ +// -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- +/* + * Copyright 1999-2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "comtypes.h" +#include "mempool.h" + +#ifndef _FRANKIDS_H_ +#define _FRANKIDS_H_ + +#define MAIN_FLIP_BUTTON_ID 101 +#define MAIN_VALUE_BUTTON_ID 102 +#define MAIN_HINT_BUTTON_ID 103 +#define MAIN_UNDO_BUTTON_ID 104 +#define MAIN_COMMIT_BUTTON_ID 105 +#define MAIN_TRADE_BUTTON_ID 106 +#define MAIN_JUGGLE_BUTTON_ID 107 +#define MAIN_HIDE_BUTTON_ID 108 + + +#define MAIN_WINDOW_ID 1000 +#define ASK_WINDOW_ID 1001 +#define ASKLETTER_WINDOW_ID 1002 +#define PLAYERS_WINDOW_ID 1003 +#define PASSWORD_WINDOW_ID 1004 +#define SAVEDGAMES_WINDOW_ID 1005 + +#define FILEMENU_BUTTON_ID 1020 +#define FILEMENU_NEWGAME 1021 +#define FILEMENU_SAVEDGAMES 1022 +#define FILEMENU_PREFS 1023 +#define FILEMENU_ABOUT 1024 + +#define GAMEMENU_BUTTON_ID 1025 +#define GAMEMENU_TVALUES 1026 +#define GAMEMENU_FINALSCORES 1041 +#define GAMEMENU_GAMEINFO 1027 +#define GAMEMENU_HISTORY 1028 + +#define MOVEMENU_BUTTON_ID 1029 +#define MOVEMENU_HINT 1030 +#define MOVEMENU_NEXTHINT 1031 +#define MOVEMENU_REVERT 1032 +#define MOVEMENU_UNDO 1033 +#define MOVEMENU_DONE 1034 +#define MOVEMENU_JUGGLE 1035 +#define MOVEMENU_TRADE 1036 +#define MOVEMENU_HIDETRAY 1037 + +#ifdef MEM_DEBUG +# define DEBUGMENU_BUTTON_ID 1050 +# define DEBUGMENU_HEAPDUMP 1051 +#endif + +#define FILEMENU_WINDOW_ID 1038 +#define GAMEMENU_WINDOW_ID 1039 +#define MOVEMENU_WINDOW_ID 1040 +#define MENUBAR_WINDOW_ID 1041 + +const char* PUB_ERICHOUSE = "Eric_House"; + +const char* EXT_XWORDSDICT = "xwd"; +const char* EXT_XWORDSGAMES = "xwg"; + +#define DICT_OFFSET 0x00000000 +#define GAMES_DB_OFFSET 0x00800000 +#define FLAGS_CHECK_OFFSET 0x00900000 + +#define MAX_NAME_LENGTH 31 /* not including null byte */ + +extern "C" { + XP_UCHAR* frankCopyStr( MPFORMAL const XP_UCHAR* ); +} + +#endif + diff --git a/xwords4/franklin/frankletter.cpp b/xwords4/franklin/frankletter.cpp new file mode 100644 index 000000000..dc8de6a7a --- /dev/null +++ b/xwords4/franklin/frankletter.cpp @@ -0,0 +1,119 @@ +// -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- +/* + * Copyright 1999-2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include "sys.h" +#include "gui.h" +#include "ebm_object.h" + +extern "C" { +#include "xptypes.h" +} + +#include "frankletter.h" + +#include "frankids.h" +#include "frankask.h" + +#define LETTER_HEIGHT 12 +#define LETTERS_ROW_HEIGHT LETTER_HEIGHT +#define LETTERS_ROW_WIDTH 30 +#define LETTERS_NUM_VISROWS 16 + +class LettersList : public CList { +private: + const XP_UCHAR4* fTexts; + + public: + LettersList( const XP_UCHAR4* texts, U16 numRows ); + + U16 GetRowHeight( S32 row ) { return LETTER_HEIGHT; } + void DrawRow( RECT *rect, S32 row ); +}; + +LettersList::LettersList( const XP_UCHAR4* texts, U16 numRows ) + : CList( 1001, LETTERS_ROW_WIDTH, + LETTERS_ROW_HEIGHT * LETTERS_NUM_VISROWS, + numRows, LISTOPTION_ALWAYS_HIGHLIGHT ) +{ + fTexts = texts; + + this->SetCurrentRow(0); // Select the first item so there's a default +} + +void LettersList::DrawRow( RECT *rect, S32 row ) +{ + CWindow* window = this->GetWindow(); + window->DrawText( (char*)fTexts[row], rect->x, rect->y ); +} /* LettersList::DrawRow */ + +CAskLetterWindow::CAskLetterWindow( const PickInfo* pi, XP_U16 playerNum, + const XP_UCHAR4* texts, XP_U16 nTiles, + XP_S16* resultP ) + : CWindow( ASKLETTER_WINDOW_ID, 55, 15, 80, 220, "Blank", TRUE ) +{ + fTexts = texts; + fNTiles = nTiles; + fResultP = resultP; + + this->list = new LettersList( texts, nTiles ); + this->AddChild( this->list, 5, 5 ); + + CButton* okbutton = new CButton( 1000, 0, 0, "Ok" ); + this->AddChild( okbutton, 40, 70 ); +} // CAskWindow + +S32 +CAskLetterWindow::MsgHandler( MSG_TYPE type, CViewable *object, S32 data ) +{ + S32 result = 0; + + switch (type) { + case MSG_BUTTON_SELECT: // there's only one button.... + *fResultP = this->list->GetCurrentRow(); + + this->Close(); + result = 1; + break; + + case MSG_KEY: // allow keys to select the matching letter in the list + if ( isalpha( data ) ) { + XP_UCHAR ch = toupper(data); + for ( U16 i = 0; i < fNTiles; ++i ) { + if ( ch == fTexts[i][0] ) { + this->list->SetCurrentRow( i ); + result = 1; + break; + } + } + } + + default: + break; + } + + if ( result == 0 ) { + result = CWindow::MsgHandler( type, object, data ); + } + return result; +} // CAskLetterWindow::MsgHandler + + + diff --git a/xwords4/franklin/frankletter.h b/xwords4/franklin/frankletter.h new file mode 100644 index 000000000..3f1dd0479 --- /dev/null +++ b/xwords4/franklin/frankletter.h @@ -0,0 +1,35 @@ +// -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +extern "C" { +#include "dictnry.h" +#include "util.h" +} + +class CAskLetterWindow : public CWindow { + private: + const XP_UCHAR4* fTexts; + XP_S16* fResultP; + XP_U16 fNTiles; + CList* list; /* my own subclass, of course */ + public: + CAskLetterWindow( const PickInfo* pi, XP_U16 playerNum, + const XP_UCHAR4* texts, XP_U16 nTiles, XP_S16* result ); + S32 MsgHandler( MSG_TYPE type, CViewable *object, S32 data ); +}; diff --git a/xwords4/franklin/frankmain.cpp b/xwords4/franklin/frankmain.cpp new file mode 100644 index 000000000..220eb0498 --- /dev/null +++ b/xwords4/franklin/frankmain.cpp @@ -0,0 +1,1673 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1999-2002 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#define TEST_CPP 1 + +#include +#include +#include +#include +#include +#include +#include /* for time_get_onOS */ +#include + +#include "sys.h" +#include "gui.h" +#include "OpenDatabases.h" +#include "comms.h" /* for CHANNEL_NONE */ +#include "LocalizedStrIncludes.h" +// #include "FieldMgr.h" + +#include "ebm_object.h" + +extern "C" { +#include /* jonathan_yavner@franklin.com says put in extern "C" */ +#include "xptypes.h" +#include "game.h" +#include "vtabmgr.h" +#include "dictnry.h" +#include "util.h" +#include "memstream.h" +#include "strutils.h" +} + +#include "frankmain.h" +#include "frankdraw.h" +#include "frankdict.h" +#include "frankpasswd.h" +#include "frankdlist.h" +#include "frankshowtext.h" +/* #include "frankask.h" */ +#include "frankletter.h" +#include "frankplayer.h" +#include "frankgamesdb.h" +#include "franksavedgames.h" +#include "bmps_includes.h" +/* #include "browser.h" */ + +extern "C" { +#include "lcd.h" +#include "ereader_hostio.h" +} + +#include "frankids.h" +class CXWordsWindow; + +enum { HINT_REQUEST, SERVER_TIME_REQUEST, NEWGAME_REQUEST, + FINALSCORE_REQUEST }; + +CXWordsWindow *MainWindow; +/* CLabel *Repeat_Label; */ +CMenuBar MainMenuBar( MENUBAR_WINDOW_ID, 23 ); +/* CPushButton *Edit_Button; */ + +/* Addrtest variables */ +/* class CRecordWindow; */ +/* COpenDB DBase; */ +// CFMDatabase FMgr; +/* CRecordWindow *AddrWindow; */ + + +/* callbacks */ +static VTableMgr* frank_util_getVTManager( XW_UtilCtxt* uc ); +static DictionaryCtxt* frank_util_makeEmptyDict( XW_UtilCtxt* uc ); +static void frank_util_userError( XW_UtilCtxt* uc, UtilErrID id ); +static XP_Bool frank_util_userQuery( XW_UtilCtxt* uc, UtilQueryID id, + XWStreamCtxt* stream ); +static XP_S16 frank_util_userPickTile( XW_UtilCtxt* uc, const PickInfo* pi, + XP_U16 playerNum, + const XP_UCHAR4* texts, XP_U16 nTiles ); +static XP_Bool frank_util_askPassword( XW_UtilCtxt* uc, const XP_UCHAR* name, + XP_UCHAR* buf, XP_U16* len ); +static void frank_util_trayHiddenChange( XW_UtilCtxt* uc, + XW_TrayVisState newState ); +static void frank_util_notifyGameOver( XW_UtilCtxt* uc ); +static XP_Bool frank_util_hiliteCell( XW_UtilCtxt* uc, + XP_U16 col, XP_U16 row ); +static XP_Bool frank_util_engineProgressCallback( XW_UtilCtxt* uc ); +static void frank_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why, + XP_U16 when, + TimerProc proc, void* closure ); +static void frank_util_requestTime( XW_UtilCtxt* uc ); +static XP_U32 frank_util_getCurSeconds( XW_UtilCtxt* uc ); +static XWBonusType frank_util_getSquareBonus( XW_UtilCtxt* uc, + ModelCtxt* model, + XP_U16 col, XP_U16 row ); +static XP_UCHAR* frank_util_getUserString( XW_UtilCtxt* uc, XP_U16 stringCode ); +static XP_Bool frank_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi, + XP_U16 turn, XP_Bool turnLost ); +static void frank_util_engineStarting( XW_UtilCtxt* uc, XP_U16 nBlanks ); +static void frank_util_engineStopping( XW_UtilCtxt* uc ); + + +typedef struct FrankSavedState { + U32 magic; + U16 curGameIndex; + XP_Bool showProgress; +} FrankSavedState; + + +CXWordsWindow +class CXWordsWindow : public CWindow { + public: /* so util functions can access */ + XWGame fGame; + CurGameInfo fGameInfo; + VTableMgr* fVTableMgr; + + private: + FrankDrawCtx* draw; + DictionaryCtxt* dict; + XW_UtilCtxt util; + FrankDictList* fDictList; + + FrankSavedState fState; + + CGamesDB* gamesDB; + + CommonPrefs cp; + + RECT fProgressRect; + U16 fProgressCurLine; + + /* There's a wasted slot here, but it simplifies indexing */ + TimerProc fTimerProcs[NUM_TIMERS_PLUS_ONE]; + void* fTimerClosures[NUM_TIMERS_PLUS_ONE]; + + XP_U8 phoniesAction; + BOOL penDown; + BOOL drawInProgress; + BOOL userEventPending; + XP_Bool fRobotHalted; + XP_Bool fAskTrayLimits; + +public: + CXWordsWindow( MPFORMAL FrankDictList* dlist ); + ~CXWordsWindow(); +// void init(); + void Draw(); + S32 MsgHandler( MSG_TYPE type, CViewable *from, S32 data ); + + void setUserEventPending() { this->userEventPending = TRUE; } + void clearUserEventPending() { this->userEventPending = FALSE; } + BOOL getUserEventPending() { return this->userEventPending; } + void setTimerImpl( XWTimerReason why, + TimerProc proc, void* closure ); + void setTimerIfNeeded(); + void fireTimer( XWTimerReason why ); + XP_Bool robotIsHalted() { return fRobotHalted; } + void updateCtrlsForTray( XW_TrayVisState newState ); + void startProgressBar(); + void advanceProgressBar(); + void finishProgressBar(); + + private: + CButton* addButtonAt( short id, short x, short y, char* str ); + CButton* addButtonAtBitmap( short id, short x, short y, const char* c, + IMAGE* img ); + void initUtil(); + void initPrefs(); + void loadPrefs(); + void loadGameFromStream( XWStreamCtxt* inStream ); + void makeNewGame( U16 newIndex ); + void loadCurrentGame(); + void saveCurrentGame(); + void resetCurrentGame(); + void positionBoard(); + void disOrEnableFrank( U16 id, XP_Bool enable ); + + void writeGameToStream( XWStreamCtxt* stream, U16 index ); + XWStreamCtxt* makeMemStream(); + + public: + void doCommit(); + void doHint( XP_Bool reset ); + void doUndo(); + void doHideTray(); + void doHeapDump(); + + BOOL newGame( XP_Bool allowCancel ); + void gameInfo(); + void doNewGameMenu(); +#ifndef HASBRO_EBM + void doSavedGames(); +#endif + void doAbout(); + void doEndGame(); + void doTileValues(); + void doGameHistory(); + + void fni(); + void displayFinalScores(); + XP_U16 displayTextFromStream( XWStreamCtxt* stream, const char* title, + BOOL killStream = TRUE, + BOOL includeCancel = FALSE ); + void wrappedEventLoop( CWindow* window ); + + MPSLOT +}; /* class CXWordsWindow */ + +#ifdef MEM_DEBUG +#define MEMPOOL(t) (t)->mpool, +#define MEMPOOL_NOCOMMA(t) (t)->mpool +#else +#define MEMPOOL_NOCOMMA(t) +#define MEMPOOL(t) +#endif + +#define V_BUTTON_SPACING 17 +#define BUTTON_LEFT 183 + +CXWordsWindow::CXWordsWindow(MPFORMAL FrankDictList* dlist ) + : CWindow(MAIN_WINDOW_ID, 0, 0, 200, 240, + "Crosswords 4" +) +{ + short buttonTop = BOARD_TOP; + MPASSIGN( this->mpool, mpool ); + fDictList = dlist; + + fVTableMgr = make_vtablemgr(MPPARM_NOCOMMA(mpool)); + + this->gamesDB = new CGamesDB( MEMPOOL(this) (XP_UCHAR*)"xwords_games" ); + + this->penDown = FALSE; + this->drawInProgress = FALSE; + fRobotHalted = XP_FALSE; + + this->cp.showBoardArrow = XP_TRUE; + this->cp.showRobotScores = XP_FALSE; /* No ui to turn on/off yet!! */ + + CButton* button; + button = addButtonAtBitmap( MAIN_FLIP_BUTTON_ID, BUTTON_LEFT, buttonTop, + "F", (IMAGE*)&flip ); + buttonTop += button->GetHeight() + 2; + button = addButtonAtBitmap( MAIN_VALUE_BUTTON_ID, BUTTON_LEFT, buttonTop, + "V", (IMAGE*)&valuebutton ); + buttonTop += button->GetHeight() + 2; + button = addButtonAtBitmap( MAIN_HINT_BUTTON_ID, BUTTON_LEFT, + buttonTop, "?", (IMAGE*)&lightbulb ); + buttonTop += button->GetHeight() + 2; + (void)addButtonAtBitmap( MAIN_UNDO_BUTTON_ID, BUTTON_LEFT, buttonTop, + "U", (IMAGE*)&undo ); + + fProgressRect.y = buttonTop + V_BUTTON_SPACING + 2; + + /* now start drawing from the bottom */ + buttonTop = 205; + button = addButtonAt( MAIN_COMMIT_BUTTON_ID, + BUTTON_LEFT, buttonTop, "D" ); + (void)addButtonAt( MAIN_HIDE_BUTTON_ID, + BUTTON_LEFT - button->GetWidth(), buttonTop, "H" ); + buttonTop -= V_BUTTON_SPACING; + (void)addButtonAt( MAIN_TRADE_BUTTON_ID, BUTTON_LEFT, buttonTop, "T" ); + buttonTop -= V_BUTTON_SPACING; + (void)addButtonAt( MAIN_JUGGLE_BUTTON_ID, BUTTON_LEFT, buttonTop, "J" ); + + this->draw = (FrankDrawCtx*)frank_drawctxt_make( MEMPOOL(this) this ); + + fProgressRect.x = BUTTON_LEFT + 2; + fProgressRect.width = 10; + fProgressRect.height = buttonTop - fProgressRect.y - 2; + + this->initUtil(); + + fGame.model = (ModelCtxt*)NULL; + fGame.server = (ServerCtxt*)NULL; + fGame.board = (BoardCtxt*)NULL; + + fAskTrayLimits = XP_FALSE; + + gi_initPlayerInfo( MEMPOOL(this) &fGameInfo, (XP_UCHAR*)"Player %d" ); + + U16 nRecords = gamesDB->countRecords(); + if ( nRecords == 0 ) { /* 1 for prefs, 1 for first game */ + initPrefs(); + + fGameInfo.serverRole = SERVER_STANDALONE; + /* fGameInfo.timerEnabled = XP_TRUE; */ + makeNewGame( fState.curGameIndex ); + + GUI_EventMessage( MSG_USER, this, NEWGAME_REQUEST ); + } else { + XP_ASSERT( nRecords >= 2 ); + loadPrefs(); + + /* there needs to be a "game" for the saved one to be loaded into. */ + game_makeNewGame( MPPARM(mpool) &fGame, &fGameInfo, &this->util, + (DrawCtx*)this->draw, 0, &this->cp, + (TransportSend)NULL, NULL); + loadCurrentGame(); + + positionBoard(); + server_do( fGame.server ); /* in case there's a robot */ + board_invalAll( fGame.board ); + } +} /* CXWordsWindow::CXWordsWindow */ + +XWStreamCtxt* +CXWordsWindow::makeMemStream() +{ + XWStreamCtxt* stream = mem_stream_make( MEMPOOL(this) + fVTableMgr, + this, + CHANNEL_NONE, + (MemStreamCloseCallback)NULL ); + return stream; +} /* makeMemStream */ + +void +CXWordsWindow::saveCurrentGame() +{ + XWStreamCtxt* stream = makeMemStream(); + writeGameToStream( stream, fState.curGameIndex ); + + U16 len = stream_getSize( stream ); + void* ptr = XP_MALLOC( this->mpool, len ); + stream_getBytes( stream, ptr, len ); + this->gamesDB->putNthRecord( fState.curGameIndex, ptr, len ); + XP_FREE( this->mpool, ptr ); + + stream_destroy( stream ); +} /* saveCurrentGame */ + +void +CXWordsWindow::positionBoard() +{ + board_setPos( fGame.board, BOARD_LEFT, BOARD_TOP, + XP_FALSE ); + board_setScale( fGame.board, BOARD_SCALE, BOARD_SCALE ); + + board_setScoreboardLoc( fGame.board, SCORE_LEFT, SCORE_TOP, + this->GetWidth()-SCORE_LEFT-TIMER_WIDTH, + SCORE_HEIGHT, XP_TRUE ); + + U16 trayTop = BOARD_TOP + (BOARD_SCALE * 15) + 2; + board_setTrayLoc( fGame.board, TRAY_LEFT, trayTop, + MIN_TRAY_SCALE, MIN_TRAY_SCALE, + FRANK_DIVIDER_WIDTH ); + + board_setTimerLoc( fGame.board, + this->GetWidth() - TIMER_WIDTH, + SCORE_TOP, TIMER_WIDTH, TIMER_HEIGHT ); +} /* positionBoard */ + +void +CXWordsWindow::makeNewGame( U16 newIndex ) +{ + XP_U32 gameID = frank_util_getCurSeconds( &this->util ); + if ( !!fGame.model ) { + saveCurrentGame(); + game_reset( MEMPOOL(this) &fGame, &fGameInfo, &this->util, + gameID, &this->cp, (TransportSend)NULL, NULL ); + } else { + game_makeNewGame( MPPARM(mpool) &fGame, &fGameInfo, &this->util, + (DrawCtx*)this->draw, gameID, &this->cp, + (TransportSend)NULL, NULL); + positionBoard(); + } + this->gamesDB->putNthName( newIndex, (XP_UCHAR*)"untitled game" ); + fState.curGameIndex = newIndex; +} /* makeNewGame */ + +CButton* +CXWordsWindow::addButtonAt( short id, short x, short y, char* str ) +{ + CButton* button = new CButton( id, 0, 0, str ); + this->AddChild( button, x, y ); + return button; +} /* addButtonAt */ + +CButton* +CXWordsWindow::addButtonAtBitmap( short id, short x, short y, const char* c, + IMAGE* img ) +{ + CButton* button = new CButton( id, 0, 0, (const char*)NULL, + img, (IMAGE*)NULL, (IMAGE*)NULL ); + this->AddChild( button, x+1, y ); + + return button; +} /* addButtonAtBitmap */ + +void +CXWordsWindow::initUtil() +{ + UtilVtable* vtable = this->util.vtable = new UtilVtable; + this->util.closure = (void*)this; + this->util.gameInfo = &fGameInfo; + MPASSIGN( this->util.mpool, mpool ); + +/* vtable->m_util_makeStreamFromAddr = NULL; */ + vtable->m_util_getVTManager = frank_util_getVTManager; + vtable->m_util_makeEmptyDict = frank_util_makeEmptyDict; +/* vtable->m_util_yOffsetChange = NULL <--no scrolling */ + vtable->m_util_userError = frank_util_userError; + vtable->m_util_userQuery = frank_util_userQuery; + vtable->m_util_userPickTile = frank_util_userPickTile; + vtable->m_util_askPassword = frank_util_askPassword; + vtable->m_util_trayHiddenChange = frank_util_trayHiddenChange; + vtable->m_util_notifyGameOver = frank_util_notifyGameOver; + vtable->m_util_hiliteCell = frank_util_hiliteCell; + vtable->m_util_engineProgressCallback = frank_util_engineProgressCallback; + vtable->m_util_setTimer = frank_util_setTimer; + vtable->m_util_requestTime = frank_util_requestTime; + vtable->m_util_getCurSeconds = frank_util_getCurSeconds; + + vtable->m_util_getSquareBonus = frank_util_getSquareBonus; + vtable->m_util_getUserString = frank_util_getUserString; + vtable->m_util_warnIllegalWord = frank_util_warnIllegalWord; +#ifdef SHOW_PROGRESS + vtable->m_util_engineStarting = frank_util_engineStarting; + vtable->m_util_engineStopping = frank_util_engineStopping; +#endif +} /* initUtil */ + +CXWordsWindow::~CXWordsWindow() +{ + XP_WARNF( "~CXWordsWindow(this=%p) called", this ); + + if ( !!this->gamesDB ) { + this->gamesDB->putNthRecord( 0, &fState, sizeof(fState) ); + saveCurrentGame(); + + delete this->gamesDB; + this->gamesDB = (CGamesDB*)NULL; + } + delete fDictList; +} + +void CXWordsWindow::Draw() +{ + if ( !this->drawInProgress ) { + this->drawInProgress = TRUE; + // don't call CWindow::Draw(); It erases the entire board + board_draw( fGame.board ); + + this->DrawChildren(); + this->drawInProgress = FALSE; + } +} // CXWordsWindow::Draw + +S32 +CXWordsWindow::MsgHandler( MSG_TYPE type, CViewable *from, S32 data ) +{ + S32 result = 0; + XP_Key xpkey; + S16 drag_x; + S16 drag_y; + XP_Bool handled; + + drag_x = (S16) (data >> 16); + drag_y = (S16) data; + + GUI_DisableTimers(); + switch (type) { + + case MSG_USER: + switch ( data ) { + case HINT_REQUEST: + doHint( XP_FALSE ); /* will reset if fails */ + break; + case SERVER_TIME_REQUEST: + this->clearUserEventPending(); /* clear before calling server! */ + if ( server_do( fGame.server ) ) { + GUI_NeedUpdate(); + } + break; + case NEWGAME_REQUEST: + this->newGame( XP_FALSE ); + break; + case FINALSCORE_REQUEST: + this->displayFinalScores(); + break; + } + break; + + case MSG_PEN_DOWN: + this->penDown = TRUE; + if ( board_handlePenDown( fGame.board, drag_x, drag_y, &handled ) ) { + GUI_NeedUpdate(); + result = 1; + } + board_pushTimerSave( fGame.board ); + break; + + case MSG_PEN_TRACK: + if ( this->penDown ) { + if ( board_handlePenMove( fGame.board, drag_x, drag_y ) ) { + GUI_NeedUpdate(); + result = 1; + } + } + break; + + case MSG_PEN_UP: + if ( this->penDown ) { + board_popTimerSave( fGame.board ); + if ( board_handlePenUp( fGame.board, drag_x, drag_y, 0 ) ) { + GUI_NeedUpdate(); + result = 1; + } + this->penDown = FALSE; + } + break; + + case MSG_TIMER: + fireTimer( (XWTimerReason)data ); + setTimerIfNeeded(); + GUI_NeedUpdate(); /* Needed off-emulator? PENDING */ + break; + + case MSG_BUTTON_SELECT: + result = 1; + switch (from->GetID()) { + case MAIN_FLIP_BUTTON_ID: + if ( board_flip( fGame.board ) ) { + GUI_NeedUpdate(); + } + break; + + case MAIN_VALUE_BUTTON_ID: + if ( board_toggle_showValues( fGame.board ) ) { + GUI_NeedUpdate(); + } + break; + + case MAIN_HINT_BUTTON_ID: + this->doHint( XP_FALSE ); + break; + + case MAIN_UNDO_BUTTON_ID: + this->doUndo(); + break; + + case MAIN_COMMIT_BUTTON_ID: + this->doCommit(); + break; + + case MAIN_TRADE_BUTTON_ID: + if ( board_beginTrade( fGame.board ) ) { + GUI_NeedUpdate(); + } + break; + + case MAIN_JUGGLE_BUTTON_ID: + if ( board_juggleTray( fGame.board ) ) { + GUI_NeedUpdate(); + } + break; + case MAIN_HIDE_BUTTON_ID: + this->doHideTray(); + break; + default: + result = 0; + } + break; + + case MSG_KEY: + xpkey = XP_KEY_NONE; + switch( data ) { + + case K_JOG_ENTER: + xpkey = XP_RETURN_KEY; + break; + + case K_JOG_DOWN: + xpkey = XP_CURSOR_KEY_RIGHT; + break; + + case K_JOG_UP: + xpkey = XP_CURSOR_KEY_LEFT; + break; + + case K_DELETE: + case K_BACKSPACE: + xpkey = XP_CURSOR_KEY_DEL; + break; + + default: + if ( isalpha( data ) ) { + xpkey = (XP_Key)toupper(data); + } + break; + + } + + if ( xpkey != XP_KEY_NONE ) { + if ( board_handleKey( fGame.board, xpkey ) ) { + GUI_NeedUpdate(); + result = 1; + } + } + + break; /* MSG_KEY */ + + default: + break; + } + GUI_EnableTimers(); + + if ( result == 0 ) { + result = CWindow::MsgHandler( type, from, data ); + } + return result; +} // CXWordsWindow::MsgHandler + +void +CXWordsWindow::setTimerIfNeeded() +{ + U32 mSeconds; + XWTimerReason why; + + if ( fTimerProcs[TIMER_PENDOWN] != NULL ) { /* faster, so higher priority */ + mSeconds = (U32)450; + why = TIMER_PENDOWN; + } else if ( fTimerProcs[TIMER_TIMERTICK] != NULL ) { + mSeconds = (U32)1000; + why = TIMER_TIMERTICK; + } else { + return; + } + + SetTimer( mSeconds, XP_FALSE, why ); +} /* setTimerIfNeeded */ + +void +CXWordsWindow::fireTimer( XWTimerReason why ) +{ + TimerProc proc = fTimerProcs[why]; + fTimerProcs[why] = (TimerProc)NULL; /* clear now; board may set it again */ + + (*proc)( fTimerClosures[why], why ); +} + +void +CXWordsWindow::setTimerImpl( XWTimerReason why, TimerProc proc, void* closure ) +{ + XP_ASSERT( why == TIMER_PENDOWN || + why == TIMER_TIMERTICK ); + + fTimerProcs[why] = proc; + fTimerClosures[why] = closure; + setTimerIfNeeded(); +} + +void +CXWordsWindow::disOrEnableFrank( U16 id, XP_Bool enable ) +{ + CButton* button = (CButton*)GetChildID( id ); + if ( enable ) { + button->Enable(); + } else { + button->Disable(); + } +} /* disOrEnableFrank */ + +void +CXWordsWindow::updateCtrlsForTray( XW_TrayVisState newState ) +{ + XP_ASSERT( newState != TRAY_HIDDEN ); + XP_Bool isRevealed = newState == TRAY_REVEALED; + + disOrEnableFrank( MAIN_HINT_BUTTON_ID, isRevealed ); + disOrEnableFrank( MAIN_UNDO_BUTTON_ID, isRevealed ); + disOrEnableFrank( MAIN_COMMIT_BUTTON_ID, isRevealed ); + disOrEnableFrank( MAIN_TRADE_BUTTON_ID, isRevealed ); + disOrEnableFrank( MAIN_JUGGLE_BUTTON_ID, isRevealed ); + disOrEnableFrank( MAIN_HIDE_BUTTON_ID, isRevealed ); +} /* updateCtrlsForTray */ + +void +CXWordsWindow::startProgressBar() +{ + if ( fState.showProgress ) { + + DrawRectFilled( &fProgressRect, COLOR_WHITE ); + DrawRect( &fProgressRect, COLOR_BLACK ); + + fProgressCurLine = 0; + } +} /* startProgressBar */ + +void +CXWordsWindow::finishProgressBar() +{ + if ( fState.showProgress ) { + DrawRectFilled( &fProgressRect, COLOR_WHITE ); + } +} /* finishProgressBar */ + +void +CXWordsWindow::advanceProgressBar() +{ + if ( fState.showProgress ) { + U16 line; + U16 height = fProgressRect.height - 2; /* don't overwrite top and + bottom */ + XP_Bool draw; + COLOR color; + + fProgressCurLine %= height * 2; + draw = fProgressCurLine < height; + + line = fProgressCurLine % (height) + 1; + line = fProgressRect.y + height - line + 1; + if ( draw ) { + color = COLOR_BLACK; + } else { + color = COLOR_WHITE; + } + + DrawLine( fProgressRect.x+1, line, + fProgressRect.x + fProgressRect.width - 1, line, color ); + + ++fProgressCurLine; + } +} /* advanceProgressBar */ + +void +CXWordsWindow::wrappedEventLoop( CWindow* window ) +{ + XP_Bool robotHalted = fRobotHalted; + fRobotHalted = XP_TRUE; + board_pushTimerSave( fGame.board ); + + GUI_EventLoop( window ); + + board_popTimerSave( fGame.board ); + fRobotHalted = robotHalted; +} /* wrappedEventLoop */ + +void +CXWordsWindow::gameInfo() +{ + BOOL ignore; + wrappedEventLoop( new CPlayersWindow( MEMPOOL(this) &fGameInfo, fDictList, + FALSE, FALSE, &ignore ) ); +} /* gameInfo */ + +BOOL +CXWordsWindow::newGame( XP_Bool allowCancel ) +{ + BOOL cancelled; + wrappedEventLoop( new CPlayersWindow( MEMPOOL(this) &fGameInfo, fDictList, + TRUE, allowCancel, &cancelled ) ); + + XP_ASSERT( allowCancel || !cancelled ); /* can't clear cancelled if not + allowed to */ + if ( !cancelled ) { + XP_U32 gameID = frank_util_getCurSeconds( &this->util ); + game_reset( MPPARM(mpool) &fGame, &fGameInfo, &this->util, gameID, + &this->cp, (TransportSend)NULL, NULL ); + if ( !!fGameInfo.dictName ) { + DictionaryCtxt* dict = model_getDictionary( fGame.model ); + if ( !!dict && 0==strcmp( (char*)dict_getName(dict), + (char*)fGameInfo.dictName)){ + /* do nothing; this dict's fine */ + } else { + if ( !!dict ) { + dict_destroy( dict ); + } + dict = frank_dictionary_make( MPPARM(mpool) + fGameInfo.dictName); + model_setDictionary( fGame.model, dict ); + } + } + server_do( fGame.server ); + + GUI_NeedUpdate(); + } + + return !cancelled; +} /* newGame */ + +/* ======================================================================== */ +void +Init_Window( MPFORMAL FrankDictList* dlist ) +{ + MainWindow = new CXWordsWindow( MPPARM(mpool) dlist ); +} + +void +Init_Menu() +{ + short row; + CMenu* menu; + + menu = new CMenu( FILEMENU_WINDOW_ID ); + menu->SetNumRows( 4 ); /* 4 with preferences */ + row = 0; + menu->SetRow( row++, FILEMENU_NEWGAME, "New game", 'n' ); + menu->SetRow( row++, FILEMENU_SAVEDGAMES, "Saved games..." ); + menu->SetSeparatorRow( row++ ); +/* menu->SetRow( row++, FILEMENU_PREFS, "Preferences..." ); */ +#ifdef HASBRO_EBM + menu->SetRow( row++, FILEMENU_ABOUT, "About Franklin Scrabble(tm)..." ); +#else + menu->SetRow( row++, FILEMENU_ABOUT, "About Crosswords..." ); +#endif + MainMenuBar.AddButton( new CPushButton(FILEMENU_BUTTON_ID,0,0,"File"), + menu ); + + menu = new CMenu( GAMEMENU_WINDOW_ID ); + menu->SetNumRows( 4 ); + row = 0; + menu->SetRow( row++, GAMEMENU_TVALUES, "Tile values", 'v' ); + menu->SetRow( row++, GAMEMENU_GAMEINFO, "Current game info", 'i' ); + menu->SetRow( row++, GAMEMENU_HISTORY, "Game history", 's' ); + menu->SetRow( row++, GAMEMENU_FINALSCORES, "Final scores", 'f' ); + MainMenuBar.AddButton( new CPushButton(GAMEMENU_BUTTON_ID,0,0,"Game"), + menu ); + + menu = new CMenu( MOVEMENU_WINDOW_ID ); + menu->SetNumRows( 9 ); + row = 0; + menu->SetRow( row++, MOVEMENU_HINT, "Hint", 'h' ); + menu->SetRow( row++, MOVEMENU_NEXTHINT, "Next hint", 'n' ); + menu->SetRow( row++, MOVEMENU_REVERT, "Revert move", 'r' ); + menu->SetRow( row++, MOVEMENU_UNDO, "Undo prev. move", 'u' ); + menu->SetSeparatorRow( row++ ); + menu->SetRow( row++, MOVEMENU_DONE, "Done", 'd' ); + menu->SetRow( row++, MOVEMENU_JUGGLE, "Juggle", 'j' ); + menu->SetRow( row++, MOVEMENU_TRADE, "Trade", 't' ); + menu->SetRow( row++, MOVEMENU_HIDETRAY, "Hide tray", 'h' ); + + MainMenuBar.AddButton( new CPushButton(MOVEMENU_BUTTON_ID,0,0,"Move"), + menu ); + +#ifdef MEM_DEBUG + menu = new CMenu( MOVEMENU_WINDOW_ID ); + menu->SetNumRows( 1 ); + row = 0; + menu->SetRow( row++, DEBUGMENU_HEAPDUMP, "Heap dump" ); + + MainMenuBar.AddButton( new CPushButton(DEBUGMENU_BUTTON_ID,0,0,"Debug"), + menu ); +#endif + +} + +void +MyErrorHandler( const char *filename, int lineno, const char *failexpr ) +{ + if (lineno != -1 || strcmp( failexpr, "Out of memory" )) { + return; + } + + GUI_Alert( ALERT_WARNING, + "Operation cancelled - insufficient memory" ); + GUI_SetMallocReserve( 1536 ); + GUI_ClearStack(); +} + +S32 +GUI_main( MSG_TYPE type, CViewable *object, S32 data ) +{ + switch (type) { + case MSG_APP_START: { + FrankDictList* dlist; + if (OS_is_present && hostIO_am_I_the_current_task()) { + HOSTIO_INLINE_BREAKPOINT(); + } + + struct timeval tv; + gettimeofday( &tv, (struct timezone *)NULL ); + srand( tv.tv_sec /*20*/ /*TIMER_GetTickCountUSecs()*/ ); + +#ifdef MEM_DEBUG + MemPoolCtx* mpool = mpool_make(); +#endif + + dlist = new FrankDictList( MPPARM_NOCOMMA(mpool) ); + if ( dlist->GetDictCount() > 0 ) { + Init_Window( MPPARM(mpool) dlist ); + Init_Menu(); + GUI_SetErrorHandler( MyErrorHandler ); + } else { + delete dlist; +#ifdef MEM_DEBUG + mpool_destroy( mpool ); +#endif + GUI_Alert( ALERT_WARNING, + "Crosswords requires at least one dictionary." ); + GUI_Exit(); + } + return 1; + } + + case MSG_APP_STOP: + delete MainWindow; /* trigger save */ + return 1; + + case MSG_KEY: + if ( data == K_MENU ) { + MainMenuBar.Show(); + return 1; + } + break; + + case MSG_MENU_SELECT: + if (data == -1) { + /* We don't care about menu cancellations */ + return 1; + } + switch ((U16) data) { + + case FILEMENU_NEWGAME: + MainWindow->doNewGameMenu(); + return 1; + + case FILEMENU_SAVEDGAMES: +#ifdef HASBRO_EBM + GUI_Alert( ALERT_WARNING, "Feature not available in demo version." ); +#else + MainWindow->doSavedGames(); +#endif + return 1; + + /* case FILEMENU_PREFS: */ + case FILEMENU_ABOUT: + MainWindow->doAbout(); + return 1; + + case GAMEMENU_TVALUES: + MainWindow->doTileValues(); + return 1; + + case GAMEMENU_FINALSCORES: + MainWindow->doEndGame(); + return 1; + break; + + case GAMEMENU_GAMEINFO: + MainWindow->gameInfo(); + return 1; + + case GAMEMENU_HISTORY: + MainWindow->doGameHistory(); + return 1; + + case MOVEMENU_HINT: + case MOVEMENU_NEXTHINT: + MainWindow->doHint( (U16)data == MOVEMENU_HINT ); + break; + + case MOVEMENU_UNDO: + MainWindow->doUndo(); + break; + + case MOVEMENU_REVERT: + break; + case MOVEMENU_DONE: + MainWindow->doCommit(); + break; + case MOVEMENU_JUGGLE: + case MOVEMENU_TRADE: + break; + case MOVEMENU_HIDETRAY: + MainWindow->doHideTray(); + break; +#ifdef MEM_DEBUG + case DEBUGMENU_HEAPDUMP: + MainWindow->doHeapDump(); + break; +#endif + } + break; + default: + fallthru; + } + return 0; +} // GUI_main + +void +CXWordsWindow::doHint( XP_Bool reset ) +{ + XP_Bool workRemains = XP_FALSE; + XP_Bool done; + + if ( reset ) { + board_resetEngine( fGame.board ); + } + done = board_requestHint( fGame.board, + fAskTrayLimits, &workRemains ); + if ( done ) { + GUI_NeedUpdate(); + } + if ( workRemains ) { + GUI_EventMessage( MSG_USER, this, HINT_REQUEST ); + } +} /* handleHintMenu */ + +void +CXWordsWindow::doUndo() +{ + if ( server_handleUndo( fGame.server ) ) { + GUI_NeedUpdate(); + } +} /* doUndo */ + +void +CXWordsWindow::doHideTray() +{ + if ( board_hideTray( fGame.board ) ) { + GUI_NeedUpdate(); + } +} /* doHideTray */ + +#ifdef MEM_DEBUG +void +CXWordsWindow::doHeapDump() +{ + XWStreamCtxt* stream = makeMemStream(); + mpool_stats( mpool, stream ); + + XP_U16 size = stream_getSize( stream ); + char* buf = (char*)malloc(size+1); + stream_getBytes( stream, buf, size ); + buf[size] = '\0'; + perror(buf); /* XP_DEBUGF has 256 byte limit */ + free( buf ); +} /* CXWordsWindow::doHeapDump */ +#endif + +void +CXWordsWindow::doCommit() +{ + if ( board_commitTurn( fGame.board ) ) { + GUI_NeedUpdate(); + } +} /* doCommit */ + +/* If there's a game in progress (and there always is, I think), ask user if + * wants to save. If does, save that game and make a new one, with a new + * index, to call the gui on. Else reset the existing one and call the gui.*/ +void +CXWordsWindow::doNewGameMenu() +{ + XP_Bool doit = XP_TRUE; + /* OK returns 1 */ + if ( 0 == GUI_Alert( ALERT_OK, + "Click \"OK\" to replace the current game with a new " + "one, or \"Cancel\" to add without deleting the current " + "game." ) ) { + makeNewGame( this->gamesDB->countRecords() ); + } else if ( 0 == GUI_Alert( ALERT_OK, + "Are you sure you want to replace" + " the existing game?" ) ) { + doit = XP_FALSE; + } + + if ( doit && newGame( XP_FALSE ) ) { /* don't let user cancel; too late! */ + positionBoard(); + } + + board_invalAll( fGame.board ); + GUI_NeedUpdate(); +} /* doNewGameMenu */ + +#ifndef HASBRO_EBM +void +CXWordsWindow::doSavedGames() +{ + U16 openIndex; /* what index am I to open? */ + U16 curIndex = fState.curGameIndex; /* may change if lower-index + game deleted */ + saveCurrentGame(); /* so can be duped */ + wrappedEventLoop( new CSavedGamesWindow( this->gamesDB, &openIndex, + &curIndex) ); + + fState.curGameIndex = curIndex; + if ( curIndex != openIndex ) { + fState.curGameIndex = openIndex; + loadCurrentGame(); + positionBoard(); + server_do( fGame.server ); /* in case there's a robot */ + board_invalAll( fGame.board ); + GUI_NeedUpdate(); + } +} /* doSavedGames */ +#endif + +void +CXWordsWindow::doAbout() +{ + XP_U16 ignore; + XWStreamCtxt* stream; + + stream = makeMemStream(); + char* txt = "Crosswords " VERSION_STRING "\n" + "Copyright 2000-2004 by Eric House (xwords@eehouse.org).\n" + "All rights reserved.\n" + "For further information see www.peak.org/~fixin/xwords/ebm.html."; + stream_putString( stream, txt ); + stream_putU8( stream, '\0' ); + + wrappedEventLoop( new CShowTextWindow( MEMPOOL(this) stream, + "About Crosswords", + true, false, + &ignore ) ); +} /* doAbout */ + +void +CXWordsWindow::doEndGame() +{ + if ( server_getGameIsOver( fGame.server ) ) { + this->displayFinalScores(); + } else if ( GUI_Alert( ALERT_OK, + "Are you sure you want to end the game now?" ) + != 0 ) { + server_endGame( fGame.server ); + } + GUI_NeedUpdate(); +} /* doEndGame */ + +void +CXWordsWindow::doTileValues() +{ + XWStreamCtxt* stream; + + stream = makeMemStream(); + server_formatDictCounts( fGame.server, stream, 2 /* cols */ ); + + displayTextFromStream( stream, "Tile counts and values" ); +} /* doTileValues */ + +void +CXWordsWindow::doGameHistory() +{ + XP_Bool gameOver = server_getGameIsOver( fGame.server ); + XWStreamCtxt* stream = makeMemStream(); + model_writeGameHistory( fGame.model, stream, fGame.server, gameOver ); + + displayTextFromStream( stream, "Game history" ); +} /* doGameHistory */ + +void +CXWordsWindow::initPrefs() +{ + fState.magic = 0x12345678; + fState.curGameIndex = 1; /* 0 is prefs record */ + fState.showProgress = XP_TRUE; + + /* save 'em now so we can save 1st game at expected index. */ + this->gamesDB->putNthRecord( 0, &fState, sizeof(fState) ); +} /* initPrefs */ + +void +CXWordsWindow::loadPrefs() +{ + CGamesDB* gamesDB = this->gamesDB; + XP_ASSERT( gamesDB->countRecords() > 0 ); + + U16 len; + void* recordP = gamesDB->getNthRecord( 0, &len ); + + XP_ASSERT( len == sizeof(fState) ); + XP_ASSERT( !!recordP ); + XP_MEMCPY( &fState, recordP, len ); + gamesDB->recordRelease(0); + + XP_ASSERT( fState.magic == 0x12345678 ); +} /* loadPrefs */ + +void +CXWordsWindow::loadCurrentGame() +{ + U16 len; + void* recordP = gamesDB->getNthRecord( fState.curGameIndex, &len ); + + XWStreamCtxt* inStream = makeMemStream(); + stream_putBytes( inStream, recordP, len ); + gamesDB->recordRelease( fState.curGameIndex ); + + loadGameFromStream( inStream ); + stream_destroy( inStream ); +} /* loadCurrentGame */ + +void +CXWordsWindow::writeGameToStream( XWStreamCtxt* stream, U16 index ) +{ + /* the dictionary */ + DictionaryCtxt* dict = model_getDictionary( fGame.model ); + XP_UCHAR* dictName = dict_getName( dict ); + stream_putU8( stream, !!dictName ); + if ( !!dictName ) { + stringToStream( stream, dictName ); + } + + game_saveToStream( &fGame, &fGameInfo, stream ); +} /* writeGameToStream */ + +void +CXWordsWindow::loadGameFromStream( XWStreamCtxt* inStream ) +{ + /* the dictionary */ + XP_U8 hasDictName = stream_getU8( inStream ); + DictionaryCtxt* dict = (DictionaryCtxt*)NULL; + if ( hasDictName ) { + XP_UCHAR* name = stringFromStream(MEMPOOL(this) inStream); + dict = frank_dictionary_make( MPPARM(mpool) name ); + } + + game_makeFromStream( MPPARM(mpool) inStream, &fGame, &fGameInfo, + dict, &this->util, (DrawCtx*)this->draw, &this->cp, + (TransportSend)NULL, NULL ); +} /* loadGameFromStream */ + +void +CXWordsWindow::fni() +{ + GUI_Alert( ALERT_WARNING, "Feature pending" ); + board_invalAll( fGame.board ); + GUI_NeedUpdate(); +} /* fni */ + +void +CXWordsWindow::displayFinalScores() +{ + XWStreamCtxt* stream; + + stream = makeMemStream(); + server_writeFinalScores( fGame.server, stream ); + + displayTextFromStream( stream, "Final scores" ); +} /* displayFinalScores */ + +XP_U16 +CXWordsWindow::displayTextFromStream( XWStreamCtxt* stream, + const char* title, /* should be ID!!! */ + BOOL killStream, + BOOL includeCancel ) +{ + XP_U16 result = 0; + wrappedEventLoop( new CShowTextWindow( MEMPOOL(this) stream, title, + killStream, includeCancel, + &result ) ); + return result; +} /* displayTextFromStream */ + +extern "C" { + + int + frank_snprintf( XP_UCHAR* buf, XP_U16 len, XP_UCHAR* format, ... ) + { + va_list ap; + + va_start(ap, format); + + vsnprintf((char*)buf, len, (char*)format, ap); + + va_end(ap); + + return strlen((char*)buf); + } /* frank_snprintf */ + + void + frank_debugf( char* format, ... ) + { + char buf[256]; + va_list ap; + + va_start(ap, format); + + vsprintf(buf, format, ap); + + va_end(ap); + + perror(buf); + } // debugf + + XP_UCHAR* + frankCopyStr( MPFORMAL const XP_UCHAR* buf ) + { + XP_U16 len = XP_STRLEN(buf) + 1; + XP_UCHAR* result = (XP_UCHAR*)XP_MALLOC( mpool, len ); + XP_MEMCPY( result, buf, len ); + return result; + } /* frankCopyStr */ + + unsigned long + frank_flipLong( unsigned long l ) + { + unsigned long result = + ((l & 0x000000FF) << 24) | + ((l & 0x0000FF00) << 8) | + ((l & 0x00FF0000) >> 8) | + ((l & 0xFF000000) >> 24); + return result; + } /* frank_flipLong */ + + unsigned short + frank_flipShort(unsigned short s) + { + unsigned short result = + ((s & 0x00FF) << 8) | + ((s & 0xFF00) >> 8); + + return result; + } /* frank_flipShort */ + +} +/***************************************************************************** + * These are the callbacks intstalled in the util vtable + ****************************************************************************/ +static VTableMgr* +frank_util_getVTManager( XW_UtilCtxt* uc ) +{ + CXWordsWindow* self = (CXWordsWindow*)uc->closure; + return self->fVTableMgr; +} /* frank_util_getVTManager */ + +static DictionaryCtxt* +frank_util_makeEmptyDict( XW_UtilCtxt* uc ) +{ + return frank_dictionary_make( MPPARM(uc->mpool) (XP_UCHAR*)NULL ); +} /* frank_util_makeEmptyDict */ + +static void +frank_util_userError( XW_UtilCtxt* uc, UtilErrID id ) +{ + const char *message; +/* + BOOL GUI_Alert( ALERT type, const char *text ); + Puts up an alert window, which is a small window containing an icon, some text, and one or more buttons. + These types of alerts are offered: + ALERT_BUG: Insect icon and "Abort" button (click terminates application). Does not return. + ALERT_FATAL: Octagonal icon and "Stop" button (click terminates application). Does not return. + ALERT_ERROR: Exclamation-point icon and "Cancel" button (click returns 0). + ALERT_WARNING: Info icon and "OK" button (click returns 0). + ALERT_OK: Question-mark icon, buttons "OK" (click returns 1) and "Cancel" (click returns 0). + ALERT_RETRY: Exclamation-point icon and buttons "Try again" (returns 1) and "Exit" (returns 0). +*/ + switch( id ) { + case ERR_TILES_NOT_IN_LINE: + message = "All tiles played must be in a line."; + break; + case ERR_NO_EMPTIES_IN_TURN: + message = "Empty squares cannot separate tiles played."; + break; + + case ERR_TWO_TILES_FIRST_MOVE: + message = "Must play two or more pieces on the first move."; + break; + case ERR_TILES_MUST_CONTACT: + message = "New pieces must contact others already in place (or " + "the middle square on the first move)."; + break; + case ERR_NOT_YOUR_TURN: + message = "You can't do that; it's not your turn!"; + break; + case ERR_NO_PEEK_ROBOT_TILES: + message = "No peeking at the robot's tiles!"; + break; + case ERR_CANT_TRADE_MID_MOVE: + message = "Remove played tiles before trading."; + break; + case ERR_TOO_FEW_TILES_LEFT_TO_TRADE: + message = "Too few tiles left to trade."; + break; + default: + message = "unknown errorcode ID!!!"; + break; + } + + (void)GUI_Alert( ALERT_ERROR, message ); + CXWordsWindow* self = (CXWordsWindow*)uc->closure; + board_invalAll( self->fGame.board ); + GUI_NeedUpdate(); +} /* frank_util_userError */ + +static XP_Bool +frank_util_userQuery( XW_UtilCtxt* uc, UtilQueryID id, XWStreamCtxt* stream ) +{ + char* question; + XP_U16 askResult; + CXWordsWindow* self = (CXWordsWindow*)uc->closure; + + switch( id ) { + case QUERY_COMMIT_TURN: + askResult = self->displayTextFromStream( stream, "Query", + FALSE, TRUE ); + return askResult; + case QUERY_COMMIT_TRADE: + question = "Really trade the selected tiles?"; + break; + case QUERY_ROBOT_MOVE: + case QUERY_ROBOT_TRADE: + XP_LOGF( "handling robot info" ); + askResult = self->displayTextFromStream( stream, "Robot move", + FALSE, FALSE ); + return askResult; + break; + default: + question = "Unimplemented query code!!!"; + break; + } + + askResult = GUI_Alert( ALERT_OK, question ); + board_invalAll( self->fGame.board ); + GUI_NeedUpdate(); + return askResult != 0; +} /* frank_util_userQuery */ + +static XP_S16 +frank_util_userPickTile( XW_UtilCtxt* uc, const PickInfo* pi, + XP_U16 playerNum, + const XP_UCHAR4* texts, XP_U16 nTiles ) +{ + CXWordsWindow* self = (CXWordsWindow*)uc->closure; + XP_S16 result; + self->wrappedEventLoop( new CAskLetterWindow( pi, playerNum, + texts, nTiles, &result ) ); + return result; + /* doesn't need to inval because CAskLetterWindow saves bits behind */ +} /* frank_util_askBlankFace */ + +static XP_Bool +frank_util_askPassword( XW_UtilCtxt* uc, const XP_UCHAR* name, XP_UCHAR* buf, + XP_U16* lenp ) +{ + XP_Bool ok; + CXWordsWindow* self = (CXWordsWindow*)uc->closure; + self->wrappedEventLoop( new CAskPasswdWindow( name, buf, lenp, &ok ) ); + return ok; +} /* frank_util_askPassword */ + +static void +frank_util_trayHiddenChange( XW_UtilCtxt* uc, XW_TrayVisState newState ) +{ + CXWordsWindow* self = (CXWordsWindow*)uc->closure; + self->updateCtrlsForTray( newState ); +} /* frank_util_trayHiddenChange */ + +static void +frank_util_notifyGameOver( XW_UtilCtxt* uc ) +{ + CXWordsWindow* self = (CXWordsWindow*)uc->closure; + board_invalAll( self->fGame.board ); + GUI_NeedUpdate(); + GUI_EventMessage( MSG_USER, self, FINALSCORE_REQUEST ); +} /* frank_util_notifyGameOver */ + +static XP_Bool +frank_util_hiliteCell( XW_UtilCtxt* uc, XP_U16 col, XP_U16 row ) +{ + CXWordsWindow* self = (CXWordsWindow*)uc->closure; + XP_Bool halted = self->robotIsHalted(); + if ( !halted ) { + board_hiliteCellAt( self->fGame.board, col, row ); + } + BOOL waiting = EVNT_IsWaiting(); + return !waiting && !halted; +} /* frank_util_hiliteCell */ + +/* Return false to get engine to abort search. + */ +static XP_Bool +frank_util_engineProgressCallback( XW_UtilCtxt* uc ) +{ + CXWordsWindow* self = (CXWordsWindow*)uc->closure; + + self->advanceProgressBar(); + + BOOL waiting = EVNT_IsWaiting(); + return !waiting && !self->robotIsHalted(); +} /* frank_util_engineProgressCallback */ + +static void +frank_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why, XP_U16 when, + TimerProc proc, void* closure ) +{ + CXWordsWindow* self = (CXWordsWindow*)uc->closure; + self->setTimerImpl( why, proc, closure ); +} /* frank_util_setTimer */ + +static void +frank_util_requestTime( XW_UtilCtxt* uc ) +{ + CXWordsWindow* self = (CXWordsWindow*)uc->closure; + if ( !self->getUserEventPending() ) { + GUI_EventMessage( MSG_USER, self, SERVER_TIME_REQUEST ); + self->setUserEventPending(); + } +} /* frank_util_requestTime */ + +static XP_U32 +frank_util_getCurSeconds( XW_UtilCtxt* uc ) +{ + struct timeval tv; + gettimeofday( &tv, (struct timezone *)NULL ); + return tv.tv_sec; +} /* frank_util_getCurSeconds */ + +#define EM BONUS_NONE +#define DL BONUS_DOUBLE_LETTER +#define DW BONUS_DOUBLE_WORD +#define TL BONUS_TRIPLE_LETTER +#define TW BONUS_TRIPLE_WORD + +static XWBonusType +frank_util_getSquareBonus( XW_UtilCtxt* uc, ModelCtxt* model, + XP_U16 col, XP_U16 row ) +{ + XP_U16 index; + + const char scrabbleBoard[8*8] = { + TW,EM,EM,DL,EM,EM,EM,TW, + EM,DW,EM,EM,EM,TL,EM,EM, + + EM,EM,DW,EM,EM,EM,DL,EM, + DL,EM,EM,DW,EM,EM,EM,DL, + + EM,EM,EM,EM,DW,EM,EM,EM, + EM,TL,EM,EM,EM,TL,EM,EM, + + EM,EM,DL,EM,EM,EM,DL,EM, + TW,EM,EM,DL,EM,EM,EM,DW, + }; /* scrabbleBoard */ + + if ( col > 7 ) col = 14 - col; + if ( row > 7 ) row = 14 - row; + index = (row*8) + col; + if ( index >= 8*8 ) { + return (XWBonusType)EM; + } else { + return (XWBonusType)scrabbleBoard[index]; + } +} /* frank_util_getSquareBonus */ + +static XP_UCHAR* +frank_util_getUserString( XW_UtilCtxt* uc, XP_U16 stringCode ) +{ + switch( stringCode ) { + case STRD_REMAINING_TILES_ADD: + return (XP_UCHAR*)"+ %d [all remaining tiles]"; + case STRD_UNUSED_TILES_SUB: + return (XP_UCHAR*)"- %d [unused tiles]"; + case STR_BONUS_ALL: + return (XP_UCHAR*)"Bonus for using all tiles: 50\n"; + case STRD_TURN_SCORE: + return (XP_UCHAR*)"Score for turn: %d\n"; + case STR_COMMIT_CONFIRM: + return (XP_UCHAR*)"Commit the current move?\n"; + case STR_NONLOCAL_NAME: + return (XP_UCHAR*)"%s (remote)"; + case STR_LOCAL_NAME: + return (XP_UCHAR*)"%s"; + case STRD_TIME_PENALTY_SUB: + return (XP_UCHAR*)" - %d [time]"; + + case STRD_CUMULATIVE_SCORE: + return (XP_UCHAR*)"Cumulative score: %d\n"; + case STRS_MOVE_ACROSS: + return (XP_UCHAR*)"move (from %s across)\n"; + case STRS_MOVE_DOWN: + return (XP_UCHAR*)"move (from %s down)\n"; + case STRS_TRAY_AT_START: + return (XP_UCHAR*)"Tray at start: %s\n"; + + case STRS_NEW_TILES: + return (XP_UCHAR*)"New tiles: %s\n"; + case STRSS_TRADED_FOR: + return (XP_UCHAR*)"Traded %s for %s."; + case STR_PASS: + return (XP_UCHAR*)"pass\n"; + case STR_PHONY_REJECTED: + return (XP_UCHAR*)"Illegal word in move; turn lost!\n"; + case STRD_ROBOT_TRADED: + return (XP_UCHAR*)"Robot traded %d tiles this turn."; + case STR_ROBOT_MOVED: + return (XP_UCHAR*)"The robot made this move:\n"; + + case STR_PASSED: + return (XP_UCHAR*)"Passed"; + case STRSD_SUMMARYSCORED: + return (XP_UCHAR*)"%s:%d"; + case STRD_TRADED: + return (XP_UCHAR*)"Traded %d"; + case STR_LOSTTURN: + return (XP_UCHAR*)"Lost turn"; + + case STRS_VALUES_HEADER: + return (XP_UCHAR*)"%s counts/values:\n"; + + default: + return (XP_UCHAR*)"unknown code "; + } +} /* frank_util_getUserString */ + +static void +formatBadWords( BadWordInfo* bwi, char buf[] ) +{ + XP_U16 i; + + for ( i = 0, buf[0] = '\0'; ; ) { + char wordBuf[18]; + sprintf( wordBuf, "\"%s\"", bwi->words[i] ); + strcat( buf, wordBuf ); + if ( ++i == bwi->nWords ) { + break; + } + strcat( buf, ", " ); + } +} /* formatBadWords */ + +static XP_Bool +frank_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi, + XP_U16 turn, XP_Bool turnLost ) +{ + char buf[200]; + char wordsBuf[150]; + XP_Bool result; + CXWordsWindow* self = (CXWordsWindow*)uc->closure; + + formatBadWords( bwi, wordsBuf ); + + if ( turnLost ) { + XP_UCHAR* name = self->fGameInfo.players[turn].name; + XP_ASSERT( !!name ); + sprintf( buf, "Player %d (%s) played illegal word[s] " + "%s; loses turn", + turn+1, name, wordsBuf ); + (void)GUI_Alert( ALERT_ERROR, buf ); + result = XP_TRUE; + } else { + sprintf( buf, "Word %s not in the current dictionary. " + "Use it anyway?", wordsBuf ); + result = GUI_Alert( ALERT_OK, buf ); + } + return result; +} /* frank_util_warnIllegalWord */ + +#ifdef SHOW_PROGRESS +static void +frank_util_engineStarting( XW_UtilCtxt* uc, XP_U16 nBlanks ) +{ + CXWordsWindow* self = (CXWordsWindow*)uc->closure; + self->startProgressBar(); +} /* frank_util_engineStarting */ + +static void +frank_util_engineStopping( XW_UtilCtxt* uc ) +{ + CXWordsWindow* self = (CXWordsWindow*)uc->closure; + self->finishProgressBar(); +} /* frank_util_engineStopping */ +#endif /* SHOW_PROGRESS */ diff --git a/xwords4/franklin/frankmain.h b/xwords4/franklin/frankmain.h new file mode 100644 index 000000000..0b16f612d --- /dev/null +++ b/xwords4/franklin/frankmain.h @@ -0,0 +1,77 @@ +// -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- +/* + * Copyright 1999-2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef _FRANKMAIN_H_ +#define _FRANKMAIN_H_ + +#include +#include +#include +#include +#include "sys.h" +#include "gui.h" +#include "OpenDatabases.h" +/* #include "FieldMgr.h" */ + +#include "ebm_object.h" + +#define SCREEN_WIDTH 200 + +#define BOARD_LEFT 2 + +#define SCORE_LEFT BOARD_LEFT +#define SCORE_TOP 3 +#define SCORE_WIDTH SCREEN_WIDTH +#define SCORE_HEIGHT 13 + +#define TIMER_WIDTH 36 +#define TIMER_HEIGHT SCORE_HEIGHT + +#define BOARD_TOP SCORE_HEIGHT+SCORE_TOP +#define BOARD_SCALE 12 + +#define TRAY_LEFT BOARD_LEFT +#define MIN_TRAY_SCALE 23 +#define FRANK_DIVIDER_WIDTH 5 + +#define VERSION_STRING "4.0.7a1" + +extern "C" { + + typedef struct FrankDrawCtx { + DrawCtxVTable* vtable; + CWindow* window; + const FONT* scoreFnt; + const FONT* scoreFntBold; + const FONT* trayFont; + const FONT* valFont; + const IMAGE rightcursor; + const IMAGE downcursor; + const IMAGE startMark; +#ifdef USE_PATTERNS + const IMAGE bonusImages[BONUS_LAST]; +#endif + } FrankDrawCtx; + + void debugf( char* format, ... ); + +} /* extern "C" */ + +#endif diff --git a/xwords4/franklin/frankpasswd.cpp b/xwords4/franklin/frankpasswd.cpp new file mode 100644 index 000000000..677c9966a --- /dev/null +++ b/xwords4/franklin/frankpasswd.cpp @@ -0,0 +1,112 @@ +// -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include "sys.h" +#include "gui.h" +#include "ebm_object.h" + +extern "C" { +#include "xptypes.h" +} + +#include "frankpasswd.h" + +#include "frankids.h" + +#define TEXT_ID 2000 +#define PASSWD_WIDTH 105 +#define PASSWD_HEIGHT 12 + +#define LABEL_X 10 +#define LABEL_Y 10 +#define PASSWD_X LABEL_X +#define PASSWD_Y (LABEL_Y+15) + +#define BUTTON_Y (PASSWD_Y+20) +#define OK_BUTTON_X 30 +#define CANCEL_BUTTON_X 100 +#define PASS_CANCEL_ID 2001 +#define PASS_OK_ID 2002 +#define LABEL_ID 2003 + +CAskPasswdWindow::CAskPasswdWindow( const XP_UCHAR* name, XP_UCHAR* buf, + XP_U16* len, XP_Bool* result ) + : CWindow( PASSWORD_WINDOW_ID, 10, 120, 180, 85, "Password", TRUE ) +{ + fName = name; + fBuf = buf; + this->lenp = len; + this->okP = result; + + snprintf( fLabelBuf, sizeof(fLabelBuf), "Password for %s:", name ); + CLabel* label = new CLabel( LABEL_ID, fLabelBuf ); + this->AddChild( label, LABEL_X, LABEL_Y ); + + this->entry = new CTextEdit( TEXT_ID, PASSWD_WIDTH, PASSWD_HEIGHT, + TEXTOPTION_PASSWORD + | TEXTOPTION_ONELINE + | TEXTOPTION_HAS_FOCUS); + this->AddChild( this->entry, PASSWD_X, PASSWD_Y ); + this->SetFocus( this->entry ); + + CButton* button = new CButton( PASS_CANCEL_ID, 0, 0, "Cancel" ); + this->AddChild( button, CANCEL_BUTTON_X, BUTTON_Y ); + button = new CButton( PASS_OK_ID, 0, 0, "Ok" ); + this->AddChild( button, OK_BUTTON_X, BUTTON_Y ); +} // CAskWindow + +S32 +CAskPasswdWindow::MsgHandler( MSG_TYPE type, CViewable *object, S32 data ) +{ + S32 result = 0; + char* text; + XP_U16 len; + + switch (type) { + case MSG_BUTTON_SELECT: // there's only one button.... + switch ( object->GetID() ) { + case PASS_OK_ID: + text = this->entry->GetText(); + len = this->entry->TextLength(); + strncpy( (char*)fBuf, text, XP_MIN(len,*this->lenp) ); + fBuf[len] = '\0'; + *this->lenp = len; + *this->okP = XP_TRUE; + break; + case PASS_CANCEL_ID: + *this->okP = XP_FALSE; + break; + default: + return 0; + } + result = 1; + this->Close(); + break; + + default: + break; + } + + return result; +} // CAskLetterWindow::MsgHandler + + + diff --git a/xwords4/franklin/frankpasswd.h b/xwords4/franklin/frankpasswd.h new file mode 100644 index 000000000..540c0a76d --- /dev/null +++ b/xwords4/franklin/frankpasswd.h @@ -0,0 +1,40 @@ +// -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _FRANKPASSWD_H_ +#define _FRANKPASSWD_H_ + +#include "comtypes.h" + +class CAskPasswdWindow : public CWindow { + private: + const XP_UCHAR* fName; + XP_UCHAR* fBuf; + XP_U16* lenp; + XP_Bool* okP; + CTextEdit* entry; + char fLabelBuf[64]; + public: + CAskPasswdWindow( const XP_UCHAR* name, XP_UCHAR* buf, XP_U16* len, + XP_Bool* result ); + S32 MsgHandler( MSG_TYPE type, CViewable *object, S32 data ); +}; + + +#endif /* _FRANKPASSWD_H_ */ diff --git a/xwords4/franklin/frankplayer.cpp b/xwords4/franklin/frankplayer.cpp new file mode 100644 index 000000000..444e54d1e --- /dev/null +++ b/xwords4/franklin/frankplayer.cpp @@ -0,0 +1,466 @@ +// -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- +/* + * Copyright 1999-2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include "sys.h" +#include "gui.h" +#include "ebm_object.h" + +extern "C" { +#include "xptypes.h" +#include "strutils.h" +#include "mempool.h" +} + +#include "frankids.h" +#include "frankplayer.h" +#include "frankdict.h" + + +#define PLAYERCOUNT_MENU_ID 2000 +#define NUMPLAYERS_POPUP_ID 2001 +#define NAME_LABEL_ID 2002 +#define ROBOT_LABEL_ID 2003 +#define PASSWORD_LABEL_ID 2004 +#define OK_BUTTON_ID 2005 +#define CANCEL_BUTTON_ID 2006 +#define REVERT_BUTTON_ID 2007 +#define COUNT_LABEL_ID 2008 +#define PLAYERDICT_MENU_ID 2009 +#define DICTNAMES_POPUP_ID 2010 +#define DICT_LABEL_ID 2011 +#define SIZE_LABEL_ID 2012 +#define BOARDSIZE_MENU_ID 2013 +#define BOARDSIZE_POPUP_ID 2014 +#define TIMER_CHECKBOX_ID 2015 +#define TIMER_FIELD_ID 2016 +#define PHONIES_MENU_ID 2017 +#define PHONIES_POPUP_ID 2018 + +#define OK_BUTTON_COL 35 +#define CANCEL_BUTTON_COL 95 +#define REVERT_BUTTON_COL 125 +/* These must be far enough apart that derivitaves will remain unique; make it + 10 for now. Also, there can't be any overlap between their ranges and + other ids!!!!*/ +#define NAME_BASE 2060 +#define ROBOT_BASE 2070 +#define PASSWORD_BASE 2080 + +#define COUNTER_ROW 5 +#define LABEL_ROW 20 +#define FIRST_ROW (LABEL_ROW+20) +#define ROW_OFFSET 18 +#define DICTMENU_ROW (FIRST_ROW + (ROW_OFFSET * MAX_NUM_PLAYERS) + 2) +#define SIZEMENU_ROW (DICTMENU_ROW + ROW_OFFSET + 1) +#define PHONIESMENU_ROW (SIZEMENU_ROW + ROW_OFFSET + 1) +#define TIMER_ROW (PHONIESMENU_ROW + ROW_OFFSET + 1) + +#define BUTTON_ROW (TIMER_ROW + ROW_OFFSET + 3) +#define NAME_COL 5 +#define NAME_WIDTH 105 +#define NAME_HEIGHT 12 +#define ROBOT_COL (NAME_COL + NAME_WIDTH + 10) +#define PASSWD_WIDTH 20 +#define TIME_WIDTH 20 +#define PASSWD_HEIGHT NAME_HEIGHT +#define PASSWORD_COL (ROBOT_COL + PASSWD_WIDTH + 10) +#define TIMER_FIELD_COL 120 + +/* Put up a window with a list for each player giving name and robotness, + * and allowing for setting/changing a password. + */ +CPlayersWindow::CPlayersWindow( MPFORMAL CurGameInfo* gi, FrankDictList* dlist, + BOOL isNew, BOOL allowCancel, BOOL* cancelledP ) + : CWindow( PLAYERS_WINDOW_ID, 2, 12, 196, 226, "Game setup", !isNew, + FALSE, FALSE ) +{ + fLocalGI = *gi; + fGIRef = gi; + fDList = dlist; + this->resultP = cancelledP; + fIsNew = isNew; + MPASSIGN( this->mpool, mpool ); + + /* numplayers counter */ + CLabel* label = new CLabel( COUNT_LABEL_ID, "Number of players:" ); + this->AddChild( label, NAME_COL, COUNTER_ROW ); + this->countMenu = new CMenu(PLAYERCOUNT_MENU_ID, 0, 0, 0, 0, 0 ); + this->countMenu->SetNumRows( MAX_NUM_PLAYERS ); + + char* base = (char*)fNumsBuf; + for ( U16 i = 0 ; i < MAX_NUM_PLAYERS; ++i ) { + snprintf( base, 2, "%d", i+1 ); + this->countMenu->SetRow( i, 2000+i, base ); + base += 2; + } + + CPopupTrigger *trigger = new CPopupTrigger( NUMPLAYERS_POPUP_ID, 0, 0, + this->countMenu, 0 ); + trigger->SetCurrentRow( fLocalGI.nPlayers-1 ); + this->AddChild( trigger, NAME_COL+130, COUNTER_ROW ); + if ( !isNew ) { + DisOrEnable( NUMPLAYERS_POPUP_ID, FALSE ); + } + + /* Column labels */ + label = new CLabel( NAME_LABEL_ID, "Name" ); + this->AddChild( label, NAME_COL, LABEL_ROW ); + label = new CLabel( ROBOT_LABEL_ID, "Rbt" ); + this->AddChild( label, ROBOT_COL, LABEL_ROW ); + label = new CLabel( PASSWORD_LABEL_ID, "Pwd" ); + this->AddChild( label, PASSWORD_COL, LABEL_ROW ); + + /* build a row of controls for each potential player. Disable those below + the point determined by the number of players we have. */ + for ( U16 i = 0; i < MAX_NUM_PLAYERS; ++i ) { + LocalPlayer* fp = &fLocalGI.players[i]; + + CTextEdit* name = new CTextEdit( NAME_BASE + i, NAME_WIDTH, + NAME_HEIGHT, TEXTOPTION_ONELINE ); + name->SetText( (char*)fp->name ); + this->AddChild( name, NAME_COL, FIRST_ROW + (ROW_OFFSET*i) ); + + CCheckbox *robot_check = new CCheckbox( ROBOT_BASE + i, 0, 0, "" ); + robot_check->SetDownStatus( fp->isRobot ); + this->AddChild( robot_check, ROBOT_COL, FIRST_ROW + (ROW_OFFSET*i) ); + + CTextEdit* passwd = new CTextEdit( PASSWORD_BASE + i, + PASSWD_WIDTH, PASSWD_HEIGHT, + TEXTOPTION_PASSWORD + | TEXTOPTION_ONELINE); + this->AddChild( passwd, PASSWORD_COL, FIRST_ROW + (ROW_OFFSET*i) ); + const char* password = (const char*)fp->password; + if ( !!password && !!password[0] ) { + passwd->SetText( password ); + } + } + + this->makeDictMenu(); + + this->makeSizeMenu(); + + this->makePhoniesMenu(); + + /* the timer checkbox */ + fTimerEnabled = new CCheckbox( TIMER_CHECKBOX_ID, 0, 0, + "Timer enabled" ); + fTimerEnabled->SetDownStatus( fLocalGI.timerEnabled ); + AddChild( fTimerEnabled, NAME_COL, TIMER_ROW ); + if ( !isNew ) { + fTimerEnabled->Disable(); + } + + /* the timer field (hidden if checkbox not checked) */ + fTimerField = new CTextEdit( TIMER_FIELD_ID, TIME_WIDTH, + PASSWD_HEIGHT, TEXTOPTION_ONELINE ); + char buf[10]; + sprintf( buf, "%d", fLocalGI.gameSeconds / 60 ); + fTimerField->SetText( buf ); + AddChild( fTimerField, TIMER_FIELD_COL, TIMER_ROW ); + if ( !fLocalGI.timerEnabled || !isNew ) { + fTimerField->Disable(); + } + + /* the buttons at the bottom */ + U16 okCol = OK_BUTTON_COL; + CButton* button = new CButton( OK_BUTTON_ID, 0, 0, "Ok" ); + if ( !(isNew && allowCancel) ) { + U16 buttonWidth = button->GetWidth(); + U16 windowWidth = this->GetWidth(); + okCol = (windowWidth - buttonWidth) / 2; + } + this->AddChild( button, okCol, BUTTON_ROW ); + + if ( isNew && allowCancel ) { + button = new CButton( CANCEL_BUTTON_ID, 0, 0, "Cancel" ); + this->AddChild( button, CANCEL_BUTTON_COL, BUTTON_ROW ); + } + + adjustVisibility(); + XP_DEBUGF( "CPlayersWindow done" ); +} // CPlayersWindow + +CPlayersWindow::~CPlayersWindow() +{ + delete( this->countMenu ); + delete( this->dictMenu ); + delete( this->sizeMenu ); +} /* ~CPlayersWindow */ + +void +CPlayersWindow::DisOrEnable( U16 id, BOOL enable ) +{ + CViewable* child = this->GetChildID( id ); + if ( enable ) { + XP_DEBUGF( "enabling child id=%d\n", id ); + child->Enable(); + } else { + XP_DEBUGF( "disabling child id=%d\n", id ); + child->Disable(); + } +} /* DisOrEnable */ + + +static BOOL +checkAllDigits( CTextEdit* te ) +{ + char* text = te->GetText(); + char ch; + while ( (ch=*text++) != '\0' ) { + if ( !isdigit(ch) ) { + return false; + } + } + return true; +} /* checkAllDigits */ + +S32 +CPlayersWindow::MsgHandler( MSG_TYPE type, CViewable *from, S32 data ) +{ + S32 result = 0; + S32 id; + U16 row; + + switch ( type ) { + case MSG_MENU_SELECT: /* the num-players trigger */ + XP_DEBUGF( "MSG_MENU_SELECT: data=%ld\n", data ); + switch ( from->GetID()) { + case NUMPLAYERS_POPUP_ID: + row = this->countMenu->GetCurrentRow(); + fLocalGI.nPlayers = row + 1; /* GetCurrentRow is 0-based */ + adjustVisibility(); + GUI_NeedUpdate(); + result = 1; + break; + /* case DICTNAMES_POPUP_ID: */ + /* row = this->dictMenu->GetCurrentRow(); */ + /* break; */ + } + break; + + case MSG_TEXT_CHANGED: + if ( (from->GetID() == TIMER_FIELD_ID) + && !checkAllDigits( (CTextEdit*)from ) ) { + result = TEXTEDIT_PLEASE_UNDO; + } + break; + + case MSG_BUTTON_SELECT: + result = 1; + id = from->GetID(); + switch ( id ) { + + case TIMER_CHECKBOX_ID: + DisOrEnable( TIMER_FIELD_ID, fTimerEnabled->GetDownStatus() ); + break; + + case OK_BUTTON_ID: + for ( U16 i = 0; i < fLocalGI.nPlayers; ++i ) { + copyIDString( NAME_BASE+i, &fLocalGI.players[i].name ); + copyIDString( PASSWORD_BASE+i, + &fLocalGI.players[i].password ); + } + if ( !!dictMenu ) { + fLocalGI.dictName = + copyString( MPPARM(mpool) + fDList->GetNthName(dictMenu->GetCurrentRow())); + } else { + fLocalGI.dictName = (XP_UCHAR*)NULL; + } + + if ( fIsNew ) { + fLocalGI.boardSize = 15 - this->sizeMenu->GetCurrentRow(); + fLocalGI.phoniesAction = fPhoniesMenu->GetCurrentRow(); + } + + fLocalGI.timerEnabled = fTimerEnabled->GetDownStatus(); + if ( fLocalGI.timerEnabled ) { + char* text = fTimerField->GetText(); + fLocalGI.gameSeconds = atoi(text) * 60; + } + + *fGIRef = fLocalGI; /* copy changes to caller */ + case CANCEL_BUTTON_ID: + *this->resultP = id == CANCEL_BUTTON_ID; + this->Close(); + result = 1; + break; + default: /* probably one of our synthetic IDs */ + if ( id >= ROBOT_BASE && id < ROBOT_BASE+MAX_NUM_PLAYERS ) { + U16 playerNum = id - ROBOT_BASE; + BOOL isRobot = ((CButton*)from)->GetDownStatus(); + fLocalGI.players[playerNum].isRobot = isRobot; + adjustVisibility(); + } else if (id >= NAME_BASE && id < NAME_BASE + MAX_NUM_PLAYERS ){ + } else { + result = 0; + } + } + default: + break; + } + + if ( result == 0 ) { + result = CWindow::MsgHandler( type, from, data ); + } + return result; +} // CPlayersWindow::MsgHandler + +/* This will create a dictionary of the dict listed first in the initial.mom + * file + */ +void +CPlayersWindow::makeDictMenu() +{ + XP_U16 nDicts = fDList->GetDictCount(); + + U16 startRow; + if ( !!fLocalGI.dictName ) { + startRow = fDList->IndexForName( fLocalGI.dictName); + } else { + startRow = 0; + } + + XP_ASSERT( nDicts > 0 ); + + CMenu* menu = new CMenu( PLAYERDICT_MENU_ID, 0, 0, 0, 0, 0 ); + menu->SetNumRows( nDicts ); + + for ( U16 i = 0; i < nDicts; ++i ) { + menu->SetRow( i, 3000+i, (char*)fDList->GetNthName(i) ); + } + + CPopupTrigger *trigger = new CPopupTrigger( DICTNAMES_POPUP_ID, 0, 0, + menu, 0 ); + trigger->SetCurrentRow(startRow); + menu->SetCurrentRow(startRow); + + CLabel* label = new CLabel( DICT_LABEL_ID, "Dictnry:" ); + this->AddChild( label, NAME_COL, DICTMENU_ROW ); + U16 labelWidth = label->GetWidth(); + this->AddChild( trigger, NAME_COL+labelWidth+10, DICTMENU_ROW ); + + if ( !fIsNew ) { + DisOrEnable( DICTNAMES_POPUP_ID, FALSE ); + } + + this->dictMenu = menu; +} /* CPlayersWindow::makeDictMenu */ + +void +CPlayersWindow::makeSizeMenu() +{ + CMenu* menu = new CMenu( BOARDSIZE_MENU_ID, 0, 0, 0, 0, 0 ); + menu->SetNumRows( NUM_SIZES ); + for ( U16 i = 0; i < NUM_SIZES; ++i ) { + U16 siz = 15-i; + snprintf( (char*)this->sizeNames[i], sizeof(this->sizeNames[i]), + "%dx%d", siz, siz ); + menu->SetRow( i, 4000+i, (char*)this->sizeNames[i] ); + } + CPopupTrigger* trigger = new CPopupTrigger( BOARDSIZE_POPUP_ID, 0, 0, + menu, 0 ); + U16 curSize = 15-fLocalGI.boardSize; + trigger->SetCurrentRow(curSize); + menu->SetCurrentRow(curSize); + + CLabel* label = new CLabel( SIZE_LABEL_ID, "Board size:" ); + this->AddChild( label, NAME_COL, SIZEMENU_ROW ); + U16 labelWidth = label->GetWidth(); + this->AddChild( trigger, NAME_COL+labelWidth+10, SIZEMENU_ROW ); + + if ( !fIsNew ) { + DisOrEnable( BOARDSIZE_POPUP_ID, FALSE ); + } + + this->sizeMenu = menu; +} /* CPlayersWindow::makeSizeMenu */ + +void +CPlayersWindow::adjustVisibility() +{ + /* disable everything greater than the number of players. Before that, + disable passwords if robot */ + + U16 nPlayers = fLocalGI.nPlayers; + for ( U16 i = 0; i < MAX_NUM_PLAYERS; ++i ) { + XP_Bool disableAll = i >= nPlayers; + BOOL enable; + + /* name */ + enable = !disableAll; + DisOrEnable( NAME_BASE + i, enable ); + + /* robot check */ + /* enable's the same as above */ + DisOrEnable( ROBOT_BASE + i, enable ); + + /* passwd */ + enable = !disableAll && !fLocalGI.players[i].isRobot; + DisOrEnable( PASSWORD_BASE + i, enable ); + } +} /* adjustVisibility */ + +void +CPlayersWindow::makePhoniesMenu() +{ + CMenu* menu = new CMenu( PHONIES_MENU_ID, 0, 0, 0, 0, 0 ); + menu->SetNumRows( 3 ); + menu->SetRow( 0, 5000, "Ignore" ); + menu->SetRow( 1, 5001, "Warn" ); + menu->SetRow( 2, 5002, "Disallow" ); + + CPopupTrigger* trigger = new CPopupTrigger( PHONIES_POPUP_ID, 0, 0, + menu, 0 ); + + XWPhoniesChoice phoniesAction = fLocalGI.phoniesAction; + trigger->SetCurrentRow(phoniesAction); + menu->SetCurrentRow(phoniesAction); + + CLabel* label = new CLabel( SIZE_LABEL_ID, "Phonies:" ); + this->AddChild( label, NAME_COL, PHONIESMENU_ROW ); + U16 labelWidth = label->GetWidth(); + this->AddChild( trigger, NAME_COL+labelWidth+10, PHONIESMENU_ROW ); + + fPhoniesMenu = menu; +} /* CPlayersWindow::makePhoniesMenu */ + +void +CPlayersWindow::copyIDString( U16 id, XP_UCHAR** where ) +{ + if ( *where ) { + XP_DEBUGF( "freeing string " ); + XP_DEBUGF( "%s\n", *where ); + XP_FREE( mpool, *where ); + XP_DEBUGF( "done freeing string\n" ); + } + + XP_UCHAR* str = (XP_UCHAR*)NULL; + CTextEdit* te = (CTextEdit*)this->GetChildID( id ); + XP_UCHAR* name = (XP_UCHAR*)te->GetText(); + U16 len = te->TextLength(); + if ( len > 0 ) { + str = (XP_UCHAR*)XP_MALLOC( mpool, len + 1 ); + memcpy( str, name, len ); + str[len] = '\0'; + } + *where = str; +} /* CPlayersWindow::copyIDString */ diff --git a/xwords4/franklin/frankplayer.h b/xwords4/franklin/frankplayer.h new file mode 100644 index 000000000..1bb977db9 --- /dev/null +++ b/xwords4/franklin/frankplayer.h @@ -0,0 +1,66 @@ +// -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- +/* + * Copyright 2001-2002 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _FRANKPLAYER_H_ +#define _FRANKPLAYER_H_ + +extern "C" { +#include "comtypes.h" +#include "game.h" +} + +#include "frankdict.h" +#include "frankdlist.h" + +#define NUM_SIZES 6 + +class CPlayersWindow : public CWindow { + private: + CurGameInfo fLocalGI; /* local copy; discard if cancel */ + BOOL* resultP; /* where to write ok-or-cancel */ + CurGameInfo* fGIRef; /* copy local to here if not cancel */ + CMenu* countMenu; /* need to preserve in order to delete */ + CMenu* dictMenu; /* need to preserve in order to delete */ + CMenu* sizeMenu; + CMenu* fPhoniesMenu; + CCheckbox* fTimerEnabled; + CTextEdit* fTimerField; + XP_UCHAR sizeNames[10][NUM_SIZES]; + FrankDictList* fDList; + BOOL fIsNew; + XP_UCHAR fNumsBuf[10]; /* saves allocs and frees for menu strings */ + + public: + MPSLOT + + public: + CPlayersWindow( MPFORMAL CurGameInfo* pi, FrankDictList* dlist, BOOL isNew, + BOOL allowCancel, BOOL* cancelledP ); + ~CPlayersWindow(); + S32 MsgHandler( MSG_TYPE type, CViewable *object, S32 data ); + private: + void DisOrEnable( U16 id, BOOL enable ); + void makeDictMenu(); + void makeSizeMenu(); + void makePhoniesMenu(); + void copyIDString( U16 id, XP_UCHAR** where ); + void adjustVisibility(); +}; + +#endif diff --git a/xwords4/franklin/franksavedgames.cpp b/xwords4/franklin/franksavedgames.cpp new file mode 100644 index 000000000..0f0a2b860 --- /dev/null +++ b/xwords4/franklin/franksavedgames.cpp @@ -0,0 +1,231 @@ +// -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- +/* + * Copyright 1999-2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include "sys.h" +#include "gui.h" +#include "ebm_object.h" + +extern "C" { +#include "comtypes.h" +} + +#include "franksavedgames.h" +#include "frankids.h" + +#define RENAME_BUTTON_ID 1000 +#define DUP_BUTTON_ID 1001 +#define DELETE_BUTTON_ID 1002 +#define DONE_BUTTON_ID 1003 +#define OPEN_BUTTON_ID 1004 +#define NAME_FIELD_ID 1005 + +#define ROW_HEIGHT 12 +#define GAMES_NUM_VISROWS 6 +#define GAMES_ROW_WIDTH 150 +#define LIST_TOP 5 +#define LIST_HEIGHT (GAMES_NUM_VISROWS*ROW_HEIGHT) +#define FIELD_TOP (LIST_TOP+LIST_HEIGHT+8) +#define LIST_LEFT 5 +#define FIELD_HEIGHT 15 +#define FIELD_WIDTH 100 +#define BUTTON_TOP (FIELD_TOP+FIELD_HEIGHT+8) + +class GamesList : public CList { +private: + CGamesDB* gamesDB; + + public: + GamesList( CGamesDB* gamesDB, U16 numRows, U16 startRow ); + + U16 GetRowHeight( S32 row ) { return ROW_HEIGHT; } + void DrawRow( RECT *rect, S32 row ); +}; + +GamesList::GamesList( CGamesDB* gamesDB, U16 numRows, U16 startRow ) + : CList( 1001, GAMES_ROW_WIDTH, LIST_HEIGHT, + numRows, LISTOPTION_ALWAYS_HIGHLIGHT ) +{ + this->gamesDB = gamesDB; + + this->SetCurrentRow(startRow); +} + +void GamesList::DrawRow( RECT *rect, S32 row ) +{ + XP_UCHAR* name = this->gamesDB->getNthName( row+1 ); + if ( !name ) { + name = (XP_UCHAR*)"untitled"; + } + + CWindow* window = this->GetWindow(); + window->DrawText( (char*)name, rect->x, rect->y ); +} /* GamesList::DrawRow */ + +/***************************************************************************** + * The class itself + ****************************************************************************/ +CSavedGamesWindow::CSavedGamesWindow( CGamesDB* gamesDB, U16* toOpen, + U16* curIndex ) + : CWindow( SAVEDGAMES_WINDOW_ID, 2, 90, 196, 148, "Saved games", TRUE, + FALSE, FALSE /* no closebox */ ) +{ + this->gamesDB = gamesDB; + this->toOpenP = toOpen; /* what we'll say to open */ + this->curIndexP = curIndex; /* where we'll say current's moved to */ + this->curIndex = *curIndex; /* save current (move when delete/dup) */ + this->displayIndex = this->curIndex; /* start display at current */ + this->gamesList = (GamesList*)NULL; + + CTextEdit* field = new CTextEdit( NAME_FIELD_ID, FIELD_WIDTH, + FIELD_HEIGHT, TEXTOPTION_HAS_FOCUS + | TEXTOPTION_ONELINE ); + this->nameField = field; + field->SetText( (char*)gamesDB->getNthName( this->displayIndex ) ); + this->AddChild( field, LIST_LEFT, FIELD_TOP ); + + CButton* button = new CButton( RENAME_BUTTON_ID, 0, 0, "Rename" ); + U16 result = this->AddChild( button, 130, FIELD_TOP ); + + button = new CButton( DUP_BUTTON_ID, 0, 0, "Dup" ); + result = this->AddChild( button, 5, BUTTON_TOP ); + + button = new CButton( DELETE_BUTTON_ID, 0, 0, "Delete" ); + result = this->AddChild( button, 40, BUTTON_TOP ); + this->deleteButton = button; + checkDisableDelete(); + + button = new CButton( OPEN_BUTTON_ID, 0, 0, "Open" ); + result = this->AddChild( button, 90, BUTTON_TOP ); + + button = new CButton( DONE_BUTTON_ID, 0, 0, "Done" ); + result = this->AddChild( button, 130, BUTTON_TOP ); + + reBuildGamesList(); +} // CSavedGamesWindow + +void +CSavedGamesWindow::reBuildGamesList() +{ + if ( !!this->gamesList ) { + this->DeleteChild( this->gamesList ); + delete this->gamesList; + } + + U16 numRows = this->gamesDB->countRecords() - 1; /* skip prefs */ + GamesList* list = new GamesList( gamesDB, numRows, this->curIndex-1 ); + this->gamesList = list; + this->AddChild( list, LIST_LEFT, LIST_TOP ); + list->SetCurrentRow( this->displayIndex-1 ); +} /* reBuildGamesList */ + +void +CSavedGamesWindow::checkDisableDelete() +{ + BOOL disable = this->displayIndex == this->curIndex; + CButton* button = this->deleteButton; + if ( disable != button->IsDisabled() ) { + if ( disable ) { + button->Disable(); + } else { + button->Enable(); + } + } +} /* checkDisableDelete */ + +S32 +CSavedGamesWindow::MsgHandler( MSG_TYPE type, CViewable *object, S32 data ) +{ + S32 result = 0; + XP_UCHAR* name; + U16 newID; + + switch (type) { + case MSG_BUTTON_SELECT: // there's only one button.... + switch (object->GetID()) { + + case RENAME_BUTTON_ID: + name = (XP_UCHAR*)this->nameField->GetText(); + this->gamesDB->putNthName( this->displayIndex, name ); + this->gamesList->Draw(); + break; + + case DUP_BUTTON_ID: + newID = this->gamesDB->duplicateNthRecord( this->displayIndex ); + this->displayIndex = newID; + reBuildGamesList(); + this->gamesList->Draw(); + checkDisableDelete(); + break; + + case DELETE_BUTTON_ID: + /* disable button instead of checking here */ + XP_ASSERT( this->displayIndex != this->curIndex ); + if ( 1 == GUI_Alert( ALERT_OK, + "Are you sure you want to delete" + " the selected game?" ) ) { + this->gamesDB->removeNthRecord( this->displayIndex ); + + if ( this->displayIndex < this->curIndex ) { + --this->curIndex; + } + + if ( this->displayIndex == this->gamesDB->countRecords() ) { + --this->displayIndex; + } + + reBuildGamesList(); + this->gamesList->Draw(); + checkDisableDelete(); + } + break; + + case DONE_BUTTON_ID: + this->displayIndex = this->curIndex; /* restore to saved so next + line's does nothing */ + /* FALLTHRU */ + case OPEN_BUTTON_ID: + *this->curIndexP = this->curIndex; + *this->toOpenP = this->displayIndex; + this->Close(); + break; + } + result = 1; + break; + + case MSG_ROW_SELECT: + this->displayIndex = (U16)data + 1; + nameField->SetText( (char*)gamesDB->getNthName( this->displayIndex ) ); + checkDisableDelete(); + result = 1; + break; + + default: + break; + } + + if ( result == 0 ) { + result = CWindow::MsgHandler( type, object, data ); + } + return result; +} // MsgHandler + + + diff --git a/xwords4/franklin/franksavedgames.h b/xwords4/franklin/franksavedgames.h new file mode 100644 index 000000000..2f476c608 --- /dev/null +++ b/xwords4/franklin/franksavedgames.h @@ -0,0 +1,47 @@ +// -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _FRANKSAVEDGAMES_H_ +#define _FRANKSAVEDGAMES_H_ + +#include "frankgamesdb.h" + +extern "C" { +} + +class CSavedGamesWindow : public CWindow { + private: + CGamesDB* gamesDB; + class GamesList* gamesList; + CTextEdit* nameField; + CButton* deleteButton; + U16* toOpenP; + U16* curIndexP; + U16 curIndex; + U16 displayIndex; + + void reBuildGamesList(); + void checkDisableDelete(); + + public: + CSavedGamesWindow( CGamesDB* gamesDB, U16* toOpen, U16* curIndex ); + S32 MsgHandler( MSG_TYPE type, CViewable *object, S32 data ); +}; + +#endif diff --git a/xwords4/franklin/frankshowtext.cpp b/xwords4/franklin/frankshowtext.cpp new file mode 100644 index 000000000..faac30e39 --- /dev/null +++ b/xwords4/franklin/frankshowtext.cpp @@ -0,0 +1,154 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include "sys.h" +#include "gui.h" +#include "ebm_object.h" + +extern "C" { +#include "xptypes.h" +#include "xwstream.h" +} + +#include "frankshowtext.h" +#include "frankids.h" + +#define MAX_DLG_HT 200 +#define SHOWTEXT_WINDOW_ID 2000 +#define SHOW_TEXT_ID 2001 +#define TEXT_X 5 +#define TEXT_PADDING_ABOVE 5 +#define TEXT_PADDING_BELOW 5 +#define TEXT_Y TEXT_PADDING_ABOVE +#define TEXT_WIDTH 180 +#define TEXT_HEIGHT 40 + +#define TEXT_PADDING (TEXT_PADDING_ABOVE+TEXT_PADDING_BELOW) +#define TITLE_BAR_HT 15 + + +#define OK_BUTTON_ID 1000 +#define CANCEL_BUTTON_ID 1001 +#define OK_BUTTON_X 40 +#define CANCEL_BUTTON_X 100 +#define BUTTON_HEIGHT 12 +#define BUTTON_PADDING 3 /* below buttons */ + +CShowTextWindow::CShowTextWindow( MPFORMAL XWStreamCtxt* stream, + const char* title, + XP_Bool killStream, XP_Bool showCancel, + XP_U16* resultLoc ) + : CWindow( SHOWTEXT_WINDOW_ID, 5, 170, 190, + TEXT_HEIGHT + TEXT_PADDING + TITLE_BAR_HT, + title, TRUE, FALSE, !showCancel ) +{ + MPASSIGN( this->mpool, mpool ); + + CButton* okButton = (CButton*)NULL; + CButton* cancelButton = (CButton*)NULL; + + fResultLoc = resultLoc; + + CTextEdit* entry = new CTextEdit( SHOW_TEXT_ID, TEXT_WIDTH, TEXT_HEIGHT, + TEXTOPTION_NOEDIT | + TEXTOPTION_NOUNDERLINE); + + /* copy the stream's text into the texteditor */ + stream_putU8( stream, '\0' ); + XP_U16 len = stream_getSize( stream ); + char* textPtr = (char*)XP_MALLOC( mpool, len ); + stream_getBytes( stream, textPtr, len ); + XP_ASSERT( textPtr[len-1] == '\0' ); + entry->SetText(textPtr); + XP_FREE( mpool, textPtr ); + + if ( killStream ) { + stream_destroy( stream ); + } + + RECT rect; + GetUsableRect( &rect ); + U16 titleBarHt = rect.y - GetY(); + + U16 maxTextHeight = MAX_DLG_HT - TEXT_PADDING - titleBarHt; + U16 buttonHeight; + if ( showCancel ) { + okButton = new CButton( OK_BUTTON_ID, 0, 0, "Ok" ); + cancelButton = new CButton( CANCEL_BUTTON_ID, 0, 0, "Cancel" ); + buttonHeight = okButton->GetHeight() + BUTTON_PADDING; + maxTextHeight -= buttonHeight; + } else { + buttonHeight = 0; + } + + /* FIND out how big the text wants to be. Make the window as big as + necessary, growing it upward. */ + + U16 curTextHeight = entry->GetMaxHeight(); + if ( curTextHeight > maxTextHeight ) { + curTextHeight = maxTextHeight; + } + entry->SetHeight( curTextHeight ); + + U16 newDlgHeight = curTextHeight + buttonHeight + TEXT_PADDING + + titleBarHt; + S16 diff = newDlgHeight - GetHeight(); + SetY( GetY() - diff ); + SetHeight( newDlgHeight ); + + this->AddChild( entry, TEXT_X, TEXT_Y ); + + if ( showCancel ) { + U16 buttonY = TEXT_Y + curTextHeight + TEXT_PADDING_BELOW; + AddChild( okButton, OK_BUTTON_X, buttonY ); + AddChild( cancelButton, CANCEL_BUTTON_X, buttonY ); + } + +} /* CShowTextWindow */ + +S32 +CShowTextWindow::MsgHandler( MSG_TYPE type, CViewable *object, S32 data ) +{ + S32 result = 0; + + switch ( type ) { + case MSG_BUTTON_SELECT: /* there's only one button */ + result = 1; + switch ( object->GetID() ) { + case OK_BUTTON_ID: + *fResultLoc = 1; + break; + case CANCEL_BUTTON_ID: + *fResultLoc = 0; + break; + } + break; + + default: + break; + } + + if ( result == 1 ) { + this->Close(); + } + + return result; +} /* MsgHandler */ diff --git a/xwords4/franklin/frankshowtext.h b/xwords4/franklin/frankshowtext.h new file mode 100644 index 000000000..47454f979 --- /dev/null +++ b/xwords4/franklin/frankshowtext.h @@ -0,0 +1,42 @@ +// -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _SHOWTEXT_H_ +#define _SHOWTEXT_H_ + +extern "C" { +#include "comtypes.h" +#include "mempool.h" +} + +class CShowTextWindow : public CWindow { + private: + XP_U16* fResultLoc; + + public: + MPSLOT + + public: + CShowTextWindow( MPFORMAL XWStreamCtxt* stream, const char* title, + XP_Bool killStream, XP_Bool showCancel, + XP_U16* resultLoc ); + S32 MsgHandler( MSG_TYPE type, CViewable *object, S32 data ); +}; + +#endif /* _SHOWTEXT_H_ */ diff --git a/xwords4/franklin/initial.mom b/xwords4/franklin/initial.mom new file mode 100644 index 000000000..aa222375e --- /dev/null +++ b/xwords4/franklin/initial.mom @@ -0,0 +1,10 @@ +object Franklin _GUI pkg _GUI.pkg +object Franklin _Default fnt _Default.fnt +object Franklin _System fnt _System.fnt +object Franklin _jingles piezo _jingles.dat +sobject Franklin _alarms timer _alarms.dat +sobject Franklin _SysSet_ dat _settings.dat +object Eric_House xwords4 fxe xwords4.fxe + icon xwords4.icn Cross- words 4 + attribute _LFLAGS S +object Eric_House BasEnglish2to8 xwd BasEnglish2to8.xwd diff --git a/xwords4/franklin/pbitm2frank.pl b/xwords4/franklin/pbitm2frank.pl new file mode 100755 index 000000000..77289a81d --- /dev/null +++ b/xwords4/franklin/pbitm2frank.pl @@ -0,0 +1,124 @@ +#!/usr/bin/perl +# Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# write a pbitm out in franklin "format" +# +# usage: pbitm2frank.pl infile outName + +use strict; + +my $infile = $ARGV[0]; +my $outname = $ARGV[1]; + +my @lines; +my $width = -1; +my $height = 0; + +open (INFILE, $infile) or die "can't find file $infile\n"; + +print qq ( +/* This file generated from $infile; do not edit!!! */ + +); + +while ( ) { + ++$height; + + s/\s//; # get rid of whitespace + push( @lines, $_ ); + + my $len = length($_); + if ( $width == -1 ) { + $width = $len; + } elsif ( $len != $width ) { + die "line $height width differs"; + } +} + +my $rowbytes = ($width + 7) >> 3; +my $structName = "${outname}_struct" ; + +print qq( typedef struct $structName { + IMAGE img; + U8 data[$height * $rowbytes]; +} $structName; + +); + +printStruct(0); + +print "#ifdef USE_INVERTED\n"; +printStruct(1); +print "#endif /* USE_INVERTED */\n"; + + +sub printStruct() { + my ($invert) = @_; + + my $thisName = $outname; + + if ( $invert ) { + $thisName .= "_inverted"; + } + + print qq( +$structName $thisName = { + { $width, $height, $rowbytes, + COLOR_MODE_MONO, 0, (const COLOR *) 0, (U8*)NULL }, + { + ); + + foreach my $line (@lines) { + printLine( $width, $line, $invert ); + } + + print " }\n};\n"; +} # printStruct + +sub printLine() { + my ($len, $line, $invert) = @_; + + $line .= '-------'; # pad with 7 0s + + if ( $invert ) { + $line =~ s/#/h/g; + $line =~ s/\-/#/g; + $line =~ s/h/\-/g; + } + + for ( my $i = 0; $len > 0; ++$i ) { + my $byte = 0; + my $subline = substr($line, $i*8, 8 ); + + for ( my $j = 0; $j < 8; ++$j ) { + my $ch = substr( $subline, $j, 1 ); + if ( $ch eq '-' ) { + } elsif ( $ch eq '#' ) { + $byte |= 1 << (7-$j); + } else { + print STDERR ("unexpected char $ch at offset ", + ($i*8)+$j, " in line $height\n"); + die; + } + } + + printf( "\t0x%x, ", $byte ); + $len -= 8; + } + + print "\t/* ", substr($line, 0, -7), " */\n"; +} # printLine diff --git a/xwords4/franklin/xptypes.h b/xwords4/franklin/xptypes.h new file mode 100644 index 000000000..7dd59bab6 --- /dev/null +++ b/xwords4/franklin/xptypes.h @@ -0,0 +1,112 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1999-2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _XPTYPES_H_ +#define _XPTYPES_H_ + +#include +#include +#include +#include +#include +#include + +#ifdef CPLUS +extern "C" { +#endif + +#define XP_TRUE ((XP_Bool)(1==1)) +#define XP_FALSE ((XP_Bool)(1==0)) + +typedef U8 XP_U8; +typedef S8 XP_S8; +typedef unsigned char XP_UCHAR; + +typedef U16 XP_U16; +typedef S16 XP_S16; + +typedef U32 XP_U32; +/* typedef S32 XP_A32; */ +typedef S32 XP_S32; + +typedef signed short XP_FontCode; /* not sure how I'm using this yet */ +typedef BOOL XP_Bool; +typedef U32 XP_Time; + +#define XP_CR "\n" + +void frank_insetRect( RECT* r, short byWhat ); +void frank_debugf(char*, ...); +void p_ignore(char*, ...); +int frank_snprintf( XP_UCHAR* buf, XP_U16 len, XP_UCHAR* format, ... ); +unsigned long frank_flipLong( unsigned long l ); +unsigned short frank_flipShort(unsigned short s); + +#define XP_RANDOM() rand() + +#ifdef MEM_DEBUG +# define XP_PLATMALLOC(nbytes) malloc(nbytes) +# define XP_PLATREALLOC(p,s) realloc((p), (s)) +# define XP_PLATFREE(p) free(p) +#else +# define XP_MALLOC(pool, nbytes) malloc(nbytes) +# define XP_REALLOC(pool, p, bytes) realloc((p), (bytes)) +# define XP_FREE(pool, p) free(p) +#endif + +#define XP_MEMSET(src, val, nbytes) memset( (src), (val), (nbytes) ) +#define XP_MEMCPY(d,s,l) memcpy((d),(s),(l)) +#define XP_MEMCMP( a1, a2, l ) memcmp((a1),(a2),(l)) +#define XP_STRLEN(s) strlen((char*)(s)) +#define XP_STRCMP(s1,s2) strcmp((char*)(s1),(char*)(s2)) +#define XP_STRNCMP(s1,s2,l) strncmp((char*)(s1),(char*)(s2),(l)) +#define XP_STRCAT(d,s) strcat((d),(s)) + +#define XP_SNPRINTF frank_snprintf + +#define XP_MIN(a,b) ((a)<(b)?(a):(b)) +#define XP_MAX(a,b) ((a)>(b)?(a):(b)) + +#ifdef DEBUG +#define XP_ASSERT(b) assert(b) +#else +#define XP_ASSERT(b) +#endif + +#define XP_STATUSF XP_DEBUGF +#define XP_WARNF XP_DEBUGF + +#ifdef DEBUG +#define XP_LOGF frank_debugf +#define XP_DEBUGF frank_debugf +#else +#define XP_LOGF if(0)p_ignore +#define XP_DEBUGF if(0)p_ignore +#endif + +#define XP_NTOHL(l) frank_flipLong(l) +#define XP_NTOHS(s) frank_flipShort(s) +#define XP_HTONL(l) frank_flipLong(l) +#define XP_HTONS(s) frank_flipShort(s) + +#ifdef CPLUS +} +#endif + +#endif diff --git a/xwords4/franklin/xwords4.atts b/xwords4/franklin/xwords4.atts new file mode 100644 index 000000000..e07a9fc5c --- /dev/null +++ b/xwords4/franklin/xwords4.atts @@ -0,0 +1,36 @@ +# +# This file contains attributes common to all things. +# +# Format is: NAME|permissions|VALUE + +# Set these attributes to your desired MOM object name +# +_PUB|global+read-only|"Eric_House" +_NAME|global+read-only|"xwords4" + +# If your object has an icon, this attribute sets the icon label text +# Restrictions: two rows of 9 characters, spaces break to the next line. +# This example "GUI Test App" will appear as two lines: "GUI Test" and "App" +_ITXT|global|"Cross- words 4" + +# Launcher flag characters: +# S = launch after sync +# B = launch on boot +# If this attribute is omitted, the app is never launched automatically. +#_LFLAGS|global+read-only|"SB" + + +# +# The attributes below should rarely have to change +# + +# Launcher category setting +_LCAT|nosign+global|"PROGRAMS" + +# file extension +_EXT|global+read-only|"fxe" + +# system permissions, apps are read-only, take off the "read-only" +# for read/write permission +_PERM|global+read-only|x"00000000" + diff --git a/xwords4/franklin/xwords4_icon.bmp b/xwords4/franklin/xwords4_icon.bmp new file mode 100644 index 0000000000000000000000000000000000000000..6c72e524879088ddc56a89e92fb430e16fd882da GIT binary patch literal 798 zcmcJM%?-jZ4250X0a}SOLU7?2kp&Rq$~p{|opQ|#yu@ym5?4h?xQZ2h_1|;u&jVAj z7yJe1q^G04@HLlGqH!E4gg`OIwJ?)E#D?i-hl?`yYOZD6GNP8T5$8S`dwk1W2lIZ( zO4wi6O~gZQmdvGxivNuv3*&1E(ZRyb^-fJDiPzmxhRQF`bFp5YCNr!|YHcy6b8N*K a^{l=Y6Kxo$>hUd`7EwN=PW^R=L literal 0 HcmV?d00001 diff --git a/xwords4/linux/.cvsignore b/xwords4/linux/.cvsignore new file mode 100644 index 000000000..5275f8c49 --- /dev/null +++ b/xwords4/linux/.cvsignore @@ -0,0 +1 @@ +obj_linux_memdbg diff --git a/xwords4/linux/LocalizedStrIncludes.h b/xwords4/linux/LocalizedStrIncludes.h new file mode 100644 index 000000000..8e7968e0a --- /dev/null +++ b/xwords4/linux/LocalizedStrIncludes.h @@ -0,0 +1,63 @@ +/* Copyright 2001 by Eric House (xwords@eehouse.org) (fixin@peak.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +/* This is the linux version of what's always been a palm file. There's + * probably a better way of doing this, but this is it for now. + */ + +#ifndef _LOCALIZEDSTRINCLUDES_H_ +#define _LOCALIZEDSTRINCLUDES_H_ + +enum { + STRD_REMAINING_TILES_ADD, + STRD_UNUSED_TILES_SUB, + STR_COMMIT_CONFIRM, + STRD_TURN_SCORE, + STR_BONUS_ALL, + STR_NONLOCAL_NAME, + STR_LOCAL_NAME, + STRD_TIME_PENALTY_SUB, + + STRD_CUMULATIVE_SCORE, + STRS_TRAY_AT_START, + STRS_MOVE_DOWN, + STRS_MOVE_ACROSS, + STRS_NEW_TILES, + STRSS_TRADED_FOR, + STR_PASS, + STR_PHONY_REJECTED, + STRD_ROBOT_TRADED, + STR_ROBOT_MOVED, + STR_REMOTE_MOVED, + + STR_PASSED, + STRSD_SUMMARYSCORED, + STRD_TRADED, + STR_LOSTTURN, + + STR_LOCALPLAYERS, + STR_TOTALPLAYERS, + STR_REMOTE, + + STRS_VALUES_HEADER, + + STR_LAST +}; + + +#endif diff --git a/xwords4/linux/Makefile b/xwords4/linux/Makefile new file mode 100644 index 000000000..6c989e39a --- /dev/null +++ b/xwords4/linux/Makefile @@ -0,0 +1,213 @@ +# -*- mode: makefile; compile-command: "make -j MEMDEBUG=TRUE"; -*- +# Copyright 2002-2007 by Eric House (xwords@eehouse.org). All rights +# reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +ifeq ($(MEMDEBUG),TRUE) +DEFINES = -DMEM_DEBUG -DDEBUG -DENABLE_LOGGING -DNUMBER_KEY_AS_INDEX +CFLAGS += -g $(GPROFFLAG) -Wall -Wunused-parameter -Wcast-align -Werror +CFLAGS += -DDEBUG_TS +PLATFORM = obj_linux_memdbg +else +DEFINES = +PLATFORM = obj_linux_rel +CFLAGS += -Os -Werror -Wunused +endif + +DO_CURSES = -DPLATFORM_NCURSES +ifdef CURSES_SMALL_SCREEN +DO_CURSES += -DCURSES_SMALL_SCREEN +endif +DO_GTK = -DPLATFORM_GTK + +SVN_REV ?= "$(shell svnversion -n .)" +SVNDEF = -D'SVN_REV=$(SVN_REV)' + +ifdef CURSES_ONLY +DO_GTK = +# := avoids recursion +PLATFORM := $(PLATFORM)_curses +endif +ifdef GTK_ONLY +DO_CURSES = +PLATFORM := $(PLATFORM)_gtk +endif +ifdef LIB_NO_UI +DO_CURSES = +DO_GTK = +endif + +DEFINES += $(DO_CURSES) $(DO_GTK) $(SVNDEF) + +ifdef LIB_NO_UI +TARGET=$(PLATFORM)/libxwords.so.0 +POINTER_SUPPORT = -DPOINTER_SUPPORT +else +TARGET=$(PLATFORM)/xwords +endif + +include ../common/config.mk + +DEFINES += -DPLATFORM_LINUX -DKEY_SUPPORT -DKEYBOARD_NAV -DNODE_CAN_4 +# DEFINES += -DSTUBBED_DICT +ifdef DO_GTK +DEFINES += -DXWFEATURE_SEARCHLIMIT +endif +DEFINES += -DFEATURE_TRAY_EDIT +#DEFINES += -DDRAW_WITH_PRIMITIVES + +ifdef CURSES_CELL_HT +DEFINES += -DCURSES_CELL_HT=$(CURSES_CELL_HT) +endif +ifdef CURSES_CELL_WIDTH +DEFINES += -DCURSES_CELL_WIDTH=$(CURSES_CELL_WIDTH) +endif + +# Bluetooth support +ifndef NO_BLUETOOTH +BLUETOOTH = -DXWFEATURE_BLUETOOTH -DBT_USE_L2CAP +endif +#BLUETOOTH = -DXWFEATURE_BLUETOOTH -DBT_USE_RFCOMM +# DEFINES += -DXWFEATURE_IR +DEFINES += ${BLUETOOTH} +DEFINES += -DXWFEATURE_RELAY + +# Support device-to-device connection via UDP, e.g. using wifi on a +# LAN or where the host/server isn't behind a firewall. +DEFINES += -DXWFEATURE_IP_DIRECT + +# Choose one of these. RELAY_HEARTBEAT means relay (must be compiled +# with same -D) works with comms on heartbeat. Works only with relay. +# COMMS_HEARTBEAT should work on any comms transport (even IR, but +# user experience will be very bad!). Is particularly useful with BT. +# DEFINES += -DRELAY_HEARTBEAT +DEFINES += -DCOMMS_HEARTBEAT + +# Let users pick the tiles going into their trays +#DEFINES += -DFEATURE_TRAY_EDIT +DEFINES += -DDONT_ABORT_ENGINE + +DEFINES += -DPERIMETER_FOCUS + +#-DDEBUG -DEIGHT_TILES + +#GPROFFLAG = -pg + +# INCLUDES += -I/usr/lib/glib/include +INCLUDES += ${EXTRAINCS} + +ifdef DO_GTK +GTK_OBJS = \ + $(PLATFORM)/gtkmain.o \ + $(PLATFORM)/gtkdraw.o \ + $(PLATFORM)/gtkask.o \ + $(PLATFORM)/gtkletterask.o \ + $(PLATFORM)/gtkpasswdask.o \ + $(PLATFORM)/gtknewgame.o \ + $(PLATFORM)/gtkntilesask.o +endif +ifdef DO_CURSES +CURSES_OBJS = \ + $(PLATFORM)/cursesmain.o \ + $(PLATFORM)/cursesdraw.o \ + $(PLATFORM)/cursesask.o \ + $(PLATFORM)/cursesdlgutil.o \ + $(PLATFORM)/cursesletterask.o +endif +ifndef LIB_NO_UI +MAIN_OBJS = $(PLATFORM)/linuxmain.o +endif + + +OBJ = \ + $(PLATFORM)/filestream.o \ + $(PLATFORM)/linuxbt.o \ + $(PLATFORM)/linuxudp.o \ + $(PLATFORM)/linuxdict.o \ + $(PLATFORM)/linuxutl.o \ + $(CURSES_OBJS) $(GTK_OBJS) $(MAIN_OBJS) + +LIBS = -lm -lmcheck $(GPROFFLAG) +ifdef BLUETOOTH +LIBS += -lbluetooth +endif + +ifneq (,$(findstring DPLATFORM_GTK,$(DEFINES))) + LIBS += `pkg-config --libs gtk+-2.0` + CFLAGS += `pkg-config --cflags gtk+-2.0` \ + -DGDK_DISABLE_DEPRECATED + POINTER_SUPPORT = -DPOINTER_SUPPORT +endif + +CFLAGS += $(POINTER_SUPPORT) + +ifneq (,$(findstring DPLATFORM_NCURSES,$(DEFINES))) + LIBS += $(OE_LIBDIR) -lncurses +endif + +# provides an all: target +include ../common/rules.mk + +all: $(TARGET) + +help: + @echo "make [MEMDEBUG=TRUE] [CURSES_ONLY=TRUE] [GTK_ONLY=TRUE]" + +#test: +# $(MAKE) test1 DEFINES="$(DEFINES) -FOOBAR" + +#test1: +# echo $(findstring FOO,$(DEFINES)) +# echo $(DEFINES) + +curses: + $(MAKE) CURSES_ONLY=TRUE + +gtk: + $(MAKE) GTK_ONLY=TRUE + +memdebug: + $(MAKE) MEMDEBUG=TRUE + +gprof: + $(MAKE) GPROFFLAG=-pg MEMDEBUG=TRUE + + +$(PLATFORM)/xwords: $(COMMONOBJ) $(OBJ) *.h Makefile + mkdir -p $(PLATFORM) + $(CC) $(CFLAGS) $(DEFINES) $(COMMONOBJ) $(OBJ) $(LIBS) -o $@ + +$(PLATFORM)/libxwords.so.0: $(COMMONOBJ) $(OBJ) *.h Makefile + mkdir -p $(PLATFORM) + $(CC) $(CFLAGS) $(DEFINES) $(COMMONOBJ) $(OBJ) -shared -o $@ -Wl,-soname,libxwords.so.0 + +$(PLATFORM)/%.o: %.c + mkdir -p $(PLATFORM) + $(CC) -c $(INCLUDES) $(DEFINES) -DPLATFORM=$(PLATFORM) $(CFLAGS) $< -o $@ + +clean: + rm -rf $(PLATFORM)/*.o $(TARGET) $(DESTDIR)/usr/local/bin/xwords + cd ../common && $(MAKE) PLATFORM=$(PLATFORM) $@ + +install: $(TARGET) + cp $< $(DESTDIR)/usr/local/bin + +tarball: + tar cvfz xwords_$(shell svnversion ..).tgz \ + ../linux/Makefile ../linux/*.c ../linux/*.h \ + ../relay/*.h \ + ../common/*.c ../common/*.h ../common/rules.mk ../common/config.mk + md5sum xwords_$(shell svnversion ..).tgz > xwords_$(shell svnversion ..).tgz.md5 diff --git a/xwords4/linux/README.txt b/xwords4/linux/README.txt new file mode 100644 index 000000000..9107b51a3 --- /dev/null +++ b/xwords4/linux/README.txt @@ -0,0 +1,52 @@ +This directory contains the desktop Linux port of Crosswords. + +To build, run a shell in this directory and type + +# make +or +# make debug +or +# make memdebug + +Any will work as long as you have both libncurses and libgtk-1.2 and +the associated headers installed on your system. If you don't you can +play with the Makefile to build with only GTK or ncurses. + +Once you've built, go to the linux directory that will be created +within this one and type, at a minimum + +# ./xwords -s -n SomeName + +to get a GTK-based game with the built-in (English) tiles. (Add the +-u flag to run with ncurses instead of GTK.) There will be no robot +player, and the hint feature ('?' button) won't work. For that you +need a real dictionary, which you can build in the dawg directory. If +you build the BasEnglish2to8.xwd one in dawg/English, this command +will run a two person game between you and the machine: + +# ./xwords -s -r robot -n SomeName -d ../../dawg/English/BasEnglish2to8.xwd + +Here are the commands to launch two copies playing against each other +over the network. Do these in separate shells both in the same +directory as the above commands ran in. Launch the one with the -s +flag (the "server") first. + + s1# ./xwords -s -r Eric -N -p 4000 -l 4001 + s2# ./xwords -d ../../dawg/English/BasEnglish2to8.xwd -r Kati -p 4001 -l 6002 + +Both of these have "robot" players. Turn one or both -r flags to -n +for human players who make their own moves. + +If you want to run them on different machines, just add the -a flag to +the client telling it on what machine to find the server (since it +sends the first message, and the server will use the return address +from that message.) + + + +***** + +Please keep in mind that these Linux desktop clients are meant for +development only, as testbeds for code in ../common/ that will also be +used for the "real" products on PalmOS, PocketPC, eBookman, etc. +They're not supposed to be polished. diff --git a/xwords4/linux/cursesask.c b/xwords4/linux/cursesask.c new file mode 100644 index 000000000..463c622dc --- /dev/null +++ b/xwords4/linux/cursesask.c @@ -0,0 +1,128 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Put up a "dialog" with a question and tab-selectable buttons below. + * On , return the number of the button selected at the time. + */ + +#ifdef PLATFORM_NCURSES + +#include + +#include "cursesask.h" +#include "cursesdlgutil.h" + + +/* Figure out how many lines there are and how wide the widest is. + */ +short +cursesask( CursesAppGlobals* globals, char* question, short numButtons, + char* button1, ... ) +{ + WINDOW* confWin; + int x, y, rows, row, nLines; + short newSelButton = 0; + short curSelButton = 1; /* force draw by being different */ + short spacePerButton, num; + short maxWidth; + XP_Bool dismissed = XP_FALSE; + FormatInfo fi; + int len; + + getmaxyx(globals->boardWin, y, x); + + measureAskText( question, x-2, &fi ); + len = fi.maxLen; + if ( len < MIN_WIDTH ) { + len = MIN_WIDTH; + } + + rows = fi.nLines; + maxWidth = x - (PAD*2) - 2; /* 2 for two borders */ + + if ( len > x-2 ) { + rows = (len / maxWidth) + 1; + len = maxWidth; + } + + nLines = ASK_HEIGHT + rows - 1; + confWin = newwin( nLines, len+(PAD*2), + (y/2) - (nLines/2), (x-len-2)/2 ); + keypad( confWin, TRUE ); + wclear( confWin ); + box( confWin, '|', '-'); + + for ( row = 0; row < rows; ++row ) { + mvwaddnstr( confWin, row+1, PAD, + fi.line[row].substr, fi.line[row].len ); + } + spacePerButton = (len+(PAD*2)) / (numButtons + 1); + + while ( !dismissed ) { + int ch; + + if ( newSelButton != curSelButton ) { + drawButtons( confWin, rows+1, spacePerButton, numButtons, + curSelButton=newSelButton, &button1 ); + } + + ch = wgetch( confWin ); + switch ( ch ) { + case 'L': + case KEY_RIGHT: + case 525: + newSelButton = (curSelButton+1) % numButtons; + break; + case 'H': + case '\t': + case KEY_LEFT: + case 524: + newSelButton = (numButtons+curSelButton-1) % numButtons; + break; + case EOF: + case 4: /* C-d */ + case 27: /* ESC */ + curSelButton = 0; /* should be the cancel case */ + case KEY_B2: /* "center of keypad" */ + case '\r': + case '\n': + dismissed = XP_TRUE; + break; + case '1': + case '2': + case '3': + case '4': + num = ch - '1'; + if ( num < numButtons ) { + newSelButton = num; + } + break; + default: + beep(); + } + } + delwin( confWin ); + + /* this leaves a ghost line, but I can't figure out a better way. */ + wtouchln( globals->boardWin, (y/2)-(nLines/2), ASK_HEIGHT + rows - 1, 1 ); + wrefresh( globals->boardWin ); + return curSelButton; +} /* ask */ + +#endif /* PLATFORM_NCURSES */ diff --git a/xwords4/linux/cursesask.h b/xwords4/linux/cursesask.h new file mode 100644 index 000000000..f4930a682 --- /dev/null +++ b/xwords4/linux/cursesask.h @@ -0,0 +1,29 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CURSESASK_H_ +#define _CURSESASK_H_ + +#include "cursesmain.h" + +short cursesask( CursesAppGlobals* globals, char* question, + short numButtons, char* button1, ... ); + + +#endif diff --git a/xwords4/linux/cursesdlgutil.c b/xwords4/linux/cursesdlgutil.c new file mode 100644 index 000000000..b7484691d --- /dev/null +++ b/xwords4/linux/cursesdlgutil.c @@ -0,0 +1,92 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000-2003 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef PLATFORM_NCURSES + +#include "cursesdlgutil.h" + +void +measureAskText( const XP_UCHAR* question, int width, FormatInfo* fip ) +{ + int i; + XP_U16 maxLen = 0; + XP_Bool done = XP_FALSE; + + int len = strlen(question); + const char* end = question + len; + const char* cur = question; + for ( i = 0; i < MAX_LINES && !done; ++i ) { + len = strlen(cur); + + if ( len == 0 ) { + assert( i > 0 ); + fip->nLines = i; + fip->maxLen = maxLen; + break; + } + + fip->line[i].substr = cur; + + /* Now we need to break the line if 1) there's a ; or 2) it's too + long. */ + const char* cr = strstr( cur, "\n" ); + if ( NULL != cr && (cr - cur) < width ) { + len = cr - cur; + } else if ( len > width ) { + const char* s = cur + width; + while ( *s != ' ' && s > cur ) { + --s; + } + assert( s > cur ); /* deal with this!! */ + len = s - cur; + } + fip->line[i].len = len; + if ( maxLen < len ) { + maxLen = len; + } + + cur += len + 1; /* skip the /space */ + if ( cur > end ) { + cur = end; + } + } +} /* measureAskText */ + +void +drawButtons( WINDOW* win, XP_U16 line, short spacePerButton, + short numButtons, short curSelButton, char** button1 ) +{ + short i; + for ( i = 0; i < numButtons; ++i ) { + short len = strlen( *button1 ); + + if ( i == curSelButton ) { + wstandout( win ); + } + mvwprintw( win, line, ((i+1) * spacePerButton) - (len/2), + "[%s]", *button1 ); + if ( i == curSelButton ) { + wstandend( win ); + } + ++button1; + } + wrefresh( win ); +} /* drawButtons */ + +#endif diff --git a/xwords4/linux/cursesdlgutil.h b/xwords4/linux/cursesdlgutil.h new file mode 100644 index 000000000..1f9199c7e --- /dev/null +++ b/xwords4/linux/cursesdlgutil.h @@ -0,0 +1,49 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CURSESDLGUTIL_H_ +#define _CURSESDLGUTIL_H_ + +#include "comtypes.h" +#include "linuxmain.h" +#include "cursesmain.h" + +#define ASK_HEIGHT 5 +#define PAD 2 +#define MAX_LINES 15 +#define MIN_WIDTH 25 + + + +typedef struct FormatInfo { + XP_U16 nLines; + XP_U16 maxLen; + struct { + const XP_UCHAR* substr; + XP_U16 len; + } line[MAX_LINES]; +} FormatInfo; + + +void measureAskText( const XP_UCHAR* question, int maxWidth, FormatInfo* fip ); + +void drawButtons( WINDOW* confWin, XP_U16 line, short spacePerButton, + short numButtons, short curSelButton, char** button1 ); + +#endif diff --git a/xwords4/linux/cursesdraw.c b/xwords4/linux/cursesdraw.c new file mode 100644 index 000000000..da3d4985e --- /dev/null +++ b/xwords4/linux/cursesdraw.c @@ -0,0 +1,568 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE"; -*- */ +/* + * Copyright 1997-2008 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef PLATFORM_NCURSES + +#include +#include +#include + +#include "cursesmain.h" +#include "draw.h" +#include "board.h" +#include "dbgutil.h" + +typedef struct CursesDrawCtx { + DrawCtxVTable* vtable; + + WINDOW* boardWin; + +} CursesDrawCtx; + +static void curses_draw_clearRect( DrawCtx* p_dctx, const XP_Rect* rectP ); +static void getTops( const XP_Rect* rect, int* toptop, int* topbot ); + +static void +drawRect( WINDOW* win, const XP_Rect* rect, char vert, char hor ) +{ + wmove( win, rect->top-1, rect->left ); + whline( win, hor, rect->width ); + wmove( win, rect->top+rect->height, rect->left ); + whline( win, hor, rect->width ); + + wmove( win, rect->top, rect->left-1 ); + wvline( win, vert, rect->height ); + wmove( win, rect->top, rect->left+rect->width ); + wvline( win, vert, rect->height ); +} /* drawRect */ + +static void +eraseRect( CursesDrawCtx* dctx, const XP_Rect* rect ) +{ + int y, bottom = rect->top + rect->height; + for ( y = rect->top; y < bottom; ++y ) { + mvwhline( dctx->boardWin, y, rect->left, ' ', rect->width ); + } +} /* eraseRect */ + +static void +cursesHiliteRect( WINDOW* window, const XP_Rect* rect ) +{ + int right, width, x, y; + width = rect->width; + right = width + rect->left; + wstandout( window ); + for ( y = rect->top; y < rect->top + rect->height; ++y ) { + for ( x = rect->left; x < right; ++x ) { + chtype cht = mvwinch( window, y, x ); + char ch = cht & A_CHARTEXT; + mvwaddch( window, y, x, ch ); + } + } + wstandend( window ); +} + +static void +curses_draw_destroyCtxt( DrawCtx* XP_UNUSED(p_dctx) ) +{ + // CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; +} /* draw_setup */ + +static void +curses_draw_dictChanged( DrawCtx* XP_UNUSED(p_dctx), + const DictionaryCtxt* XP_UNUSED(dict) ) +{ +} + +static XP_Bool +curses_draw_boardBegin( DrawCtx* XP_UNUSED(p_dctx), + const XP_Rect* XP_UNUSED(rect), + DrawFocusState XP_UNUSED(dfs) ) +{ + return XP_TRUE; +} /* draw_finish */ + +static XP_Bool +curses_draw_trayBegin( DrawCtx* XP_UNUSED(p_dctx), + const XP_Rect* XP_UNUSED(rect), + XP_U16 XP_UNUSED(owner), + DrawFocusState XP_UNUSED(dfs) ) +{ + return XP_TRUE; +} /* draw_finish */ + +static void +curses_draw_scoreBegin( DrawCtx* p_dctx, const XP_Rect* rect, + XP_U16 XP_UNUSED(numPlayers), + DrawFocusState XP_UNUSED(dfs) ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + eraseRect( dctx, rect ); +} /* curses_draw_scoreBegin */ + +static void +formatRemText( char* buf, int bufLen, XP_S16 nTilesLeft, int width ) +{ + snprintf( buf, bufLen, "Tiles left in pool: %.3d", nTilesLeft ); + if ( strlen(buf)+1 >= width ) { + snprintf( buf, bufLen, "Rem: %.3d", nTilesLeft ); + } +} /* formatRemText */ + +static void +curses_draw_measureRemText( DrawCtx* XP_UNUSED(dctx), + const XP_Rect* r, + XP_S16 nTilesLeft, + XP_U16* width, XP_U16* height ) +{ + char buf[32]; + formatRemText( buf, sizeof(buf), nTilesLeft, r->width ); + + *width = strlen(buf); + *height = 1; +} /* curses_draw_measureRemText */ + +static void +curses_draw_drawRemText( DrawCtx* p_dctx, const XP_Rect* rInner, + const XP_Rect* XP_UNUSED(rOuter), XP_S16 nTilesLeft, + XP_Bool focussed ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + char buf[32]; + + formatRemText( buf, sizeof(buf), nTilesLeft, rInner->width ); + mvwprintw( dctx->boardWin, rInner->top, rInner->left, buf ); + if ( focussed ) { + cursesHiliteRect( dctx->boardWin, rInner ); + } +} /* curses_draw_drawRemText */ + +static int +fitIn( char* buf, int len, int* rem, char* str ) +{ + int slen = strlen(str); + if ( !!rem && (*rem != 0) ) { + ++len; + --*rem; + } + if ( slen > len ) { + slen = len; + } + + memcpy( buf, str, slen ); + return len; +} /* fitIn */ + +static void +formatScoreText( XP_UCHAR* out, int outLen, const DrawScoreInfo* dsi, + int width ) +{ + /* Long and short formats. We'll try long first. If it fits, cool. + Otherwise we use short. Either way, we fill the whole rect so it can + overwrite anything that was there before.*/ + char tmp[width+1]; + char buf[width+1]; + int scoreWidth = 4; + + XP_ASSERT( width < outLen ); + + XP_MEMSET( buf, ' ', width ); + buf[width] = '\0'; + + /* Status/role chars at start */ + if ( dsi->isTurn ) { + buf[0] = 'T'; + } + if ( dsi->selected ) { + buf[1] = 'S'; + } + if ( dsi->isRobot) { + buf[2] = 'r'; + } + + /* Score always goes at end. Will overwrite status if width is really small */ + snprintf( tmp, scoreWidth, "%.3d", dsi->totalScore ); + memcpy( &buf[width-scoreWidth+1], tmp, scoreWidth-1 ); + + /* Now we want to fit name, rem tiles, last score, and last move, if + there's space. Allocate to each so they're in columns. */ + + width -= 8; /* status chars plus space; score plus space */ + if ( width > 0 ) { + int pos = 4; + int nCols = 2; + int perCol = (width - ( nCols - 1)) / nCols; /* not counting spaces */ + if ( perCol > 0 ) { + int rem = (width - ( nCols - 1)) % nCols; + + pos += 1 + fitIn( &buf[pos], perCol, &rem, dsi->name ); + + XP_U16 len = sizeof(tmp); + if ( (*dsi->lsc)( dsi->lscClosure, dsi->playerNum, tmp, &len ) ) { + char* s = tmp; + if ( len > perCol ) { + /* We want to preserve the score first, then the first part of + word. That is, WORD:20 prints as W0:20, not WORD: or + RD:20 */ + char* colon = strstr( tmp, ":" ); + if ( colon ) { + s += len - perCol; + memmove( s, tmp, colon - tmp - (len - perCol) ); + } + } + pos += 1 + fitIn( &buf[pos], perCol, NULL, s ); + } + } else { + fitIn( &buf[pos], width, NULL, dsi->name ); + } + } + + snprintf( out, outLen, "%s", buf ); +} /* formatScoreText */ + +static void +curses_draw_measureScoreText( DrawCtx* XP_UNUSED(p_dctx), + const XP_Rect* r, + const DrawScoreInfo* dsi, + XP_U16* width, XP_U16* height ) +{ + XP_UCHAR buf[100]; + formatScoreText( buf, sizeof(buf), dsi, r->width ); + + *width = strlen( buf ); + XP_ASSERT( *width <= r->width ); + *height = 1; /* one line per player */ +} /* curses_draw_measureScoreText */ + +static void +curses_draw_score_pendingScore( DrawCtx* p_dctx, const XP_Rect* rect, + XP_S16 score, XP_U16 XP_UNUSED(playerNum), + CellFlags XP_UNUSED(flags) ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + char buf[4]; + + if ( score >= 0 ) { + sprintf( buf, "%.3d", score ); + } else { + strcpy( buf, "???" ); + } + + int toptop, topbot; + getTops( rect, &toptop, &topbot ); + + mvwprintw( dctx->boardWin, toptop, rect->left, "pt:" ); + mvwprintw( dctx->boardWin, topbot, rect->left, "%s", buf ); +} /* curses_draw_score_pendingScore */ + +static void +curses_draw_objFinished( DrawCtx* p_dctx, BoardObjectType XP_UNUSED(typ), + const XP_Rect* XP_UNUSED(rect), + DrawFocusState XP_UNUSED(dfs) ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + wrefresh( dctx->boardWin ); +} /* curses_draw_objFinished */ + +#define MY_PAIR 1 + +static void +curses_draw_score_drawPlayer( DrawCtx* p_dctx, const XP_Rect* rInner, + const XP_Rect* rOuter, const DrawScoreInfo* dsi ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + char buf[100]; + int y = rInner->top; + + curses_draw_clearRect( p_dctx, rOuter ); + + /* print the name and turn/remoteness indicator */ + formatScoreText( buf, sizeof(buf), dsi, rInner->width ); + mvwprintw( dctx->boardWin, y, rOuter->left, buf ); + + if ( (dsi->flags&CELL_ISCURSOR) != 0 ) { + cursesHiliteRect( dctx->boardWin, rOuter ); + } +} /* curses_draw_score_drawPlayer */ + +static XP_Bool +curses_draw_drawCell( DrawCtx* p_dctx, const XP_Rect* rect, + const XP_UCHAR* letter, XP_Bitmap XP_UNUSED(bitmap), + Tile XP_UNUSED(tile), XP_S16 XP_UNUSED(owner), + XWBonusType bonus, HintAtts XP_UNUSED(hintAtts), + CellFlags flags ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + XP_UCHAR loc[4] = { ' ', ' ', ' ', '\0' }; + XP_ASSERT( rect->width < sizeof(loc) ); + if ( !!letter ) { + XP_MEMCPY( loc, letter, strlen(letter) ); + } + + /* in case it's not 1x1 */ + eraseRect( dctx, rect ); + + if ( (flags & (CELL_DRAGSRC|CELL_ISEMPTY)) != 0 ) { + switch ( bonus ) { + case BONUS_DOUBLE_LETTER: + loc[0] = '+'; break; + case BONUS_DOUBLE_WORD: + loc[0] = '*'; break; + case BONUS_TRIPLE_LETTER: + loc[0] = '^'; break; + case BONUS_TRIPLE_WORD: + loc[0] = '#'; break; + default: + loc[0] = ' '; + } /* switch */ + } + + if ( (flags&CELL_HIGHLIGHT) != 0 ) { + wstandout( dctx->boardWin ); + } + + mvwaddnstr( dctx->boardWin, rect->top, rect->left, + loc, rect->width ); + + if ( (flags&CELL_HIGHLIGHT) != 0 ) { + wstandend( dctx->boardWin ); + } + + if ( (flags&CELL_ISCURSOR) != 0 ) { + cursesHiliteRect( dctx->boardWin, rect ); + } + + return XP_TRUE; +} /* curses_draw_drawCell */ + +static void +getTops( const XP_Rect* rect, int* toptop, int* topbot ) +{ + int top = rect->top; + if ( rect->height >= 4 ) { + ++top; + } + *toptop = top; + + top = rect->top + rect->height - 1; + if ( rect->height >= 3 ) { + --top; + } + *topbot = top; +} /* getTops */ + +static void +curses_stringInTile( CursesDrawCtx* dctx, const XP_Rect* rect, + XP_UCHAR* letter, XP_UCHAR* val ) +{ + eraseRect( dctx, rect ); + + int toptop, topbot; + getTops( rect, &toptop, &topbot ); + + if ( !!letter ) { + mvwaddnstr( dctx->boardWin, toptop, rect->left+(rect->width/2), + letter, strlen(letter) ); + } + + if ( !!val ) { + int len = strlen( val ); + mvwaddnstr( dctx->boardWin, topbot, rect->left + rect->width - len, + val, len ); + } +} /* curses_stringInTile */ + +static void +curses_draw_drawTile( DrawCtx* p_dctx, const XP_Rect* rect, + const XP_UCHAR* textP, XP_Bitmap XP_UNUSED(bitmap), + XP_S16 val, CellFlags flags ) +{ + char numbuf[5]; + char letterbuf[5]; + char* nump = NULL; + char* letterp = NULL; + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + + if ( (flags&CELL_ISEMPTY) == 0 ) { + letterbuf[0] = !!textP? *textP: '_'; /* BLANK or bitmap */ + letterbuf[1] = '\0'; + if ( val >= 0 ) { + sprintf( numbuf, "%.2d", val ); + if ( numbuf[0] == '0' ) { + numbuf[0] = ' '; + } + nump = numbuf; + } + letterp = letterbuf; + } + curses_stringInTile( dctx, rect, letterp, nump ); + + if ( (flags&CELL_HIGHLIGHT) != 0 ) { + mvwaddnstr( dctx->boardWin, rect->top+rect->height-1, + rect->left, "*-*", 3 ); + } + + if ( (flags&CELL_ISCURSOR) != 0 ) { + cursesHiliteRect( dctx->boardWin, rect ); + } +} /* curses_draw_drawTile */ + +static void +curses_draw_drawTileBack( DrawCtx* p_dctx, const XP_Rect* rect, + CellFlags flags ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + curses_stringInTile( dctx, rect, "?", "?" ); + if ( (flags&CELL_ISCURSOR) != 0 ) { + cursesHiliteRect( dctx->boardWin, rect ); + } +} /* curses_draw_drawTileBack */ + +static void +curses_draw_drawTrayDivider( DrawCtx* p_dctx, const XP_Rect* rect, + CellFlags XP_UNUSED(flags) ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + wmove( dctx->boardWin, rect->top, rect->left ); + wvline( dctx->boardWin, '#', rect->height ); + +} /* curses_draw_drawTrayDivider */ + +static void +curses_draw_drawBoardArrow( DrawCtx* p_dctx, const XP_Rect* rect, + XWBonusType XP_UNUSED(cursorBonus), + XP_Bool vertical, HintAtts XP_UNUSED(hintAtts), + CellFlags XP_UNUSED(flags) ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; +#if 1 + char ch = vertical?'|':'-'; + mvwaddch( dctx->boardWin, rect->top, rect->left, ch ); +#else + chtype curChar = mvwinch(dctx->boardWin, rect->top, rect->left ); + wstandout( dctx->boardWin ); + mvwaddch( dctx->boardWin, rect->top, rect->left, curChar); + wstandend( dctx->boardWin ); +#endif +} /* curses_draw_drawBoardArrow */ + +static void +curses_draw_clearRect( DrawCtx* p_dctx, const XP_Rect* rectP ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + XP_Rect rect = *rectP; + + eraseRect( dctx, &rect ); +} /* curses_draw_clearRect */ + +static const XP_UCHAR* +curses_draw_getMiniWText( DrawCtx* XP_UNUSED(p_dctx), + XWMiniTextType XP_UNUSED(textHint) ) +{ + return "Trading..."; +} /* curses_draw_getMiniWText */ + +static void +curses_draw_measureMiniWText( DrawCtx* XP_UNUSED(p_dctx), const XP_UCHAR* str, + XP_U16* widthP, XP_U16* heightP ) +{ + *widthP = strlen(str) + 4; + *heightP = 3; +} /* curses_draw_measureMiniWText */ + +static void +curses_draw_drawMiniWindow( DrawCtx* p_dctx, const XP_UCHAR* text, + const XP_Rect* rect, void** XP_UNUSED(closure) ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + XP_Rect smallerR; + + XP_ASSERT(0); /* does this really get called? */ + + smallerR.top = rect->top + 1; + smallerR.left = rect->left + 1; + smallerR.width = rect->width - 2; + smallerR.height = rect->height - 2; + + eraseRect( dctx, rect ); + drawRect( dctx->boardWin, &smallerR, '|', '-' ); + + mvwprintw( dctx->boardWin, smallerR.top, smallerR.left, text, + strlen(text) ); +} /* curses_draw_drawMiniWindow */ + +#if 0 +static void +curses_draw_frameTray( DrawCtx* p_dctx, XP_Rect* rect ) +{ + CursesDrawCtx* dctx = (CursesDrawCtx*)p_dctx; + box( dctx->boardWin, '*', '+'); +} /* curses_draw_frameTray */ +#endif + +static void +draw_doNothing( DrawCtx* XP_UNUSED(dctx), ... ) +{ +} /* draw_doNothing */ + +DrawCtx* +cursesDrawCtxtMake( WINDOW* boardWin ) +{ + CursesDrawCtx* dctx = malloc( sizeof(CursesDrawCtx) ); + short i; + + dctx->vtable = malloc( sizeof(*(((CursesDrawCtx*)dctx)->vtable)) ); + + for ( i = 0; i < sizeof(*dctx->vtable)/4; ++i ) { + ((void**)(dctx->vtable))[i] = draw_doNothing; + } + + SET_VTABLE_ENTRY( dctx->vtable, draw_destroyCtxt, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_dictChanged, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_boardBegin, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_objFinished, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_trayBegin, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_scoreBegin, curses ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_measureRemText, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawRemText, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_measureScoreText, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_score_pendingScore, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_score_drawPlayer, curses ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_drawCell, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTile, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTileBack, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTrayDivider, curses ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_drawBoardArrow, curses ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_clearRect, curses ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_drawMiniWindow, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_getMiniWText, curses ); + SET_VTABLE_ENTRY( dctx->vtable, draw_measureMiniWText, curses ); + + dctx->boardWin = boardWin; + + return (DrawCtx*)dctx; +} /* curses_drawctxt_init */ + +#endif /* PLATFORM_NCURSES */ diff --git a/xwords4/linux/cursesletterask.c b/xwords4/linux/cursesletterask.c new file mode 100644 index 000000000..44cfa992e --- /dev/null +++ b/xwords4/linux/cursesletterask.c @@ -0,0 +1,218 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE"; -*- */ + +/* + * Copyright 2003 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef PLATFORM_NCURSES + +#include "linuxutl.h" +#include "cursesletterask.h" +#include "cursesdlgutil.h" + +#define MAX_TILE_BUTTON_ROWS 10 +#define MAX_TILE_BUTTON_WIDTH (sizeof(XP_UCHAR4) + 2) + +static void +sizeTextsAsButtons( XP_U16 maxLen, XP_U16 nTiles, XP_U16* textsCols, + XP_U16* textsRows, XP_U16* textsOffsets ) +{ + XP_U16 nCols = maxLen / MAX_TILE_BUTTON_WIDTH; + XP_U16 nRows = (nTiles + nCols - 1) / nCols; + XP_U16 i; + + *textsCols = nCols; + *textsRows = nRows; + + for ( i = 0; i < nRows; ++i ) { + textsOffsets[i] = i * nCols; + } + + XP_DEBUGF( "broke %d letters into %d rows of %d cols", + nTiles, nRows, nCols ); +} /* sizeTextsAsButtons */ + +XP_S16 +curses_askLetter( CursesAppGlobals* globals, XP_UCHAR* query, + const XP_UCHAR4* texts, XP_U16 nTiles ) +{ + XP_S16 result; + WINDOW* confWin; + int x, y, rows, row, nLines, i; + short newSelButton = 0; + short curSelButton = 1; /* force draw by being different */ + short maxWidth; + short numCtlButtons; + char* ctlButtons[] = { "Ok", "Cancel" }; + XP_Bool dismissed = XP_FALSE; + FormatInfo fi; + int len; + XP_U16 textsCols, textsRows; + XP_U16 textsOffsets[MAX_TILE_BUTTON_ROWS]; + XP_U16 spacePerCtlButton; + char* textPtrs[MAX_UNIQUE_TILES]; + + for ( i = 0; i < nTiles; ++i ) { + textPtrs[i] = (char*)&texts[i]; + } + + getmaxyx( globals->boardWin, y, x ); + + numCtlButtons = VSIZE(ctlButtons); + + maxWidth = x - (PAD*2) - 2; /* 2 for two borders */ + measureAskText( query, maxWidth, &fi ); + + sizeTextsAsButtons( x, nTiles, &textsCols, &textsRows, textsOffsets ); + + len = XP_MAX( fi.maxLen, textsCols * MAX_TILE_BUTTON_WIDTH ); + if ( len < MIN_WIDTH ) { + len = MIN_WIDTH; + } + + rows = fi.nLines + textsRows + 1; + XP_DEBUGF( "set maxWidth=%d", maxWidth ); + + + if ( len > x-2 ) { + rows = (len / maxWidth) + 1; + len = maxWidth; + } + + nLines = ASK_HEIGHT + rows - 1; + XP_DEBUGF( "newwin( %d, %d, (%d/2) - (%d/2), (%d-%d-2)/2", + nLines, len,//+(PAD*2), + y, nLines, x, len ); + + XP_ASSERT( y >= nLines ); + confWin = newwin( nLines, len,//+(PAD*2), + (y/2) - (nLines/2), (x-len-2)/2 ); + keypad( confWin, TRUE ); + XP_ASSERT( !!confWin ); + + wclear( confWin ); + box( confWin, '|', '-'); + + for ( row = 0; row < fi.nLines; ++row ) { + mvwaddnstr( confWin, row+1, PAD, + fi.line[row].substr, fi.line[row].len ); + } + + spacePerCtlButton = (len+(PAD*2)) / (numCtlButtons + 1); + + result = newSelButton; + + while ( !dismissed ) { + int ch; + + if ( newSelButton != curSelButton ) { + + XP_U16 i; + for ( i = 0; i < textsRows; ++i ) { + XP_U16 nInRow = textsCols; + + if ( i + 1 == textsRows ) { + nInRow = nTiles % textsCols; + if ( nInRow == 0 ) { + nInRow = textsCols; + } + } + + XP_DEBUGF( "printing %d cols, row %d, first char %s", + nInRow, i, textPtrs[textsOffsets[i]] ); + drawButtons( confWin, rows + 2 - textsRows + i, + MAX_TILE_BUTTON_WIDTH-1, + nInRow, + newSelButton - textsOffsets[i], + (char**)&textPtrs[textsOffsets[i]] ); + } + + + drawButtons( confWin, rows+2, spacePerCtlButton, + numCtlButtons, + newSelButton - nTiles, ctlButtons ); + + curSelButton = newSelButton; + } + + ch = wgetch( confWin ); + int incr = 0; + switch ( ch ) { + case '\t': + case 'R': + case KEY_RIGHT: + case 525: + incr = 1; + break; + case 'L': + case KEY_LEFT: + case 524: + incr = -1; + break; + + case KEY_DOWN: + case 526: + incr = textsCols; + break; + case KEY_UP: + case 523: + incr = -textsCols; + break; + + case EOF: + case 4: /* C-d */ + case 27: /* ESC */ + curSelButton = 0; /* should be the cancel case */ + case KEY_B2: /* "center of keypad" */ + case '\r': + case '\n': + dismissed = XP_TRUE; + break; + default: + if ( ch >= 'a' && ch <= 'z' ) { + ch += 'A' - 'a'; + } + for ( i = 0; i < nTiles; ++i ) { + if ( ch == texts[i][0] ) { + XP_DEBUGF( "picking %c", (char)ch); + newSelButton = i; + result = i; + break; + } + } + } + + if ( incr != 0 ) { + newSelButton = curSelButton + incr; + if ( newSelButton < 0 ) { + newSelButton = 0; + } else if ( newSelButton >= numCtlButtons + nTiles ) { + newSelButton = numCtlButtons + nTiles - 1; + } + + } + } + delwin( confWin ); + + /* this leaves a ghost line, but I can't figure out a better way. */ + wtouchln( globals->boardWin, (y/2)-(nLines/2), ASK_HEIGHT + rows - 1, 1 ); + wrefresh( globals->boardWin ); + + return result; +} /* curses_askLetter */ + +#endif /* PLATFORM_NCURSES */ diff --git a/xwords4/linux/cursesletterask.h b/xwords4/linux/cursesletterask.h new file mode 100644 index 000000000..47ab6d083 --- /dev/null +++ b/xwords4/linux/cursesletterask.h @@ -0,0 +1,30 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2003 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CURSESLETTERASK_H_ +#define _CURSESLETTERASK_H_ + +#include "linuxmain.h" +#include "cursesmain.h" + +XP_S16 curses_askLetter( CursesAppGlobals* globals, XP_UCHAR* query, + const XP_UCHAR4* texts, XP_U16 nTiles ); + + +#endif diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c new file mode 100644 index 000000000..74ff2d0ca --- /dev/null +++ b/xwords4/linux/cursesmain.c @@ -0,0 +1,1473 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE"; -*- */ +/* + * Copyright 2000-2008 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifdef PLATFORM_NCURSES + +#include +#include +#include +#include + +#include /* gethostbyname */ +#include +//#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "linuxmain.h" +#include "linuxutl.h" +#include "cursesmain.h" +#include "cursesask.h" +#include "cursesletterask.h" +#include "linuxbt.h" +#include "model.h" +#include "draw.h" +#include "board.h" +#include "engine.h" +/* #include "compipe.h" */ +#include "xwproto.h" +#include "xwstream.h" +#include "xwstate.h" +#include "server.h" +#include "memstream.h" +#include "util.h" +#include "dbgutil.h" + +#ifdef CURSES_SMALL_SCREEN +# define MENU_WINDOW_HEIGHT 1 +# define BOARD_OFFSET 0 +#else +# define MENU_WINDOW_HEIGHT 5 /* three lines plus borders */ +# define BOARD_OFFSET 1 +#endif + +#ifndef CURSES_CELL_HT +# define CURSES_CELL_HT 1 +#endif +#ifndef CURSES_CELL_WIDTH +# define CURSES_CELL_WIDTH 2 +#endif + +#ifndef CURSES_MAX_HEIGHT +# define CURSES_MAX_HEIGHT 40 +#endif +#ifndef CURSES_MAX_WIDTH +//# define CURSES_MAX_WIDTH 50 +# define CURSES_MAX_WIDTH 70 +#endif + +#define INFINITE_TIMEOUT -1 +#define BOARD_SCORE_PADDING 3 + + +typedef XP_Bool (*CursesMenuHandler)(CursesAppGlobals* globals); +typedef struct MenuList { + CursesMenuHandler handler; + char* desc; + char* keyDesc; + char key; +} MenuList; + +static XP_Bool handleQuit( CursesAppGlobals* globals ); +static XP_Bool handleRight( CursesAppGlobals* globals ); +static XP_Bool handleSpace( CursesAppGlobals* globals ); +static XP_Bool handleRet( CursesAppGlobals* globals ); +static XP_Bool handleHint( CursesAppGlobals* globals ); +static XP_Bool handleLeft( CursesAppGlobals* globals ); +static XP_Bool handleRight( CursesAppGlobals* globals ); +static XP_Bool handleUp( CursesAppGlobals* globals ); +static XP_Bool handleDown( CursesAppGlobals* globals ); +static XP_Bool handleCommit( CursesAppGlobals* globals ); +static XP_Bool handleFlip( CursesAppGlobals* globals ); +static XP_Bool handleToggleValues( CursesAppGlobals* globals ); +static XP_Bool handleBackspace( CursesAppGlobals* globals ); +static XP_Bool handleUndo( CursesAppGlobals* globals ); +static XP_Bool handleReplace( CursesAppGlobals* globals ); +static XP_Bool handleJuggle( CursesAppGlobals* globals ); +static XP_Bool handleHide( CursesAppGlobals* globals ); +static XP_Bool handleAltLeft( CursesAppGlobals* globals ); +static XP_Bool handleAltRight( CursesAppGlobals* globals ); +static XP_Bool handleAltUp( CursesAppGlobals* globals ); +static XP_Bool handleAltDown( CursesAppGlobals* globals ); +#ifdef CURSES_SMALL_SCREEN +static XP_Bool handleRootKeyShow( CursesAppGlobals* globals ); +static XP_Bool handleRootKeyHide( CursesAppGlobals* globals ); +#endif + + +const MenuList g_sharedMenuList[] = { + { handleQuit, "Quit", "Q", 'Q' }, + { handleRight, "Tab right", "", '\t' }, + { handleSpace, "Raise focus", "", ' ' }, + { handleRet, "Click/tap", "", '\r' }, + { handleHint, "Hint", "?", '?' }, + +#ifdef KEYBOARD_NAV + { handleLeft, "Left", "H", 'H' }, + { handleRight, "Right", "L", 'L' }, + { handleUp, "Up", "J", 'J' }, + { handleDown, "Down", "K", 'K' }, +#endif + + { handleCommit, "Commit move", "C", 'C' }, + { handleFlip, "Flip", "F", 'F' }, + { handleToggleValues, "Show values", "V", 'V' }, + + { handleBackspace, "Remove from board", "", 8 }, + { handleUndo, "Undo prev", "U", 'U' }, + { handleReplace, "uNdo cur", "N", 'N' }, + + { NULL, NULL, NULL, '\0'} +}; + +const MenuList g_boardMenuList[] = { + { handleAltLeft, "Force left", "{", '{' }, + { handleAltRight, "Force right", "}", '}' }, + { handleAltUp, "Force up", "_", '_' }, + { handleAltDown, "Force down", "+", '+' }, + { NULL, NULL, NULL, '\0'} +}; + +const MenuList g_scoreMenuList[] = { +#ifdef KEYBOARD_NAV +#endif + { NULL, NULL, NULL, '\0'} +}; + +const MenuList g_trayMenuList[] = { + { handleJuggle, "Juggle", "G", 'G' }, + { handleHide, "[un]hIde", "I", 'I' }, + { handleAltLeft, "Divider left", "{", '{' }, + { handleAltRight, "Divider right", "}", '}' }, + + { NULL, NULL, NULL, '\0'} +}; + +#ifdef CURSES_SMALL_SCREEN +const MenuList g_rootMenuListShow[] = { + { handleRootKeyShow, "Press . for menu", "", '.' }, + { NULL, NULL, NULL, '\0'} +}; + +const MenuList g_rootMenuListHide[] = { + { handleRootKeyHide, "Clear menu", ".", '.' }, + { NULL, NULL, NULL, '\0'} +}; +#endif + + +static CursesAppGlobals g_globals; /* must be global b/c of SIGWINCH_handler */ + +static void changeMenuForFocus( CursesAppGlobals* globals, + BoardObjectType obj ); +static XP_Bool handleLeft( CursesAppGlobals* globals ); +static XP_Bool handleRight( CursesAppGlobals* globals ); +static XP_Bool handleUp( CursesAppGlobals* globals ); +static XP_Bool handleDown( CursesAppGlobals* globals ); +static XP_Bool handleFocusKey( CursesAppGlobals* globals, XP_Key key ); +static void countMenuLines( const MenuList** menuLists, int maxX, int padding, + int* nLinesP, int* nColsP ); +static void drawMenuFromList( WINDOW* win, const MenuList** menuLists, + int nLines, int padding ); +static CursesMenuHandler getHandlerForKey( const MenuList* list, char ch ); + + +#ifdef MEM_DEBUG +# define MEMPOOL params->util->mpool, +#else +# define MEMPOOL +#endif + +/* extern int errno; */ + +static void +cursesUserError( CursesAppGlobals* globals, const char* format, ... ) +{ + char buf[512]; + va_list ap; + + va_start( ap, format ); + + vsprintf( buf, format, ap ); + + (void)cursesask( globals, buf, 1, "OK" ); + + va_end(ap); +} /* cursesUserError */ + +static XP_S16 +curses_util_userPickTile( XW_UtilCtxt* uc, const PickInfo* XP_UNUSED(pi), + XP_U16 playerNum, const XP_UCHAR4* texts, + XP_U16 nTiles ) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; + char query[128]; + XP_S16 index; + char* playerName = globals->cGlobals.params->gi.players[playerNum].name; + + snprintf( query, sizeof(query), + "Pick tile for %s! (Tab or type letter to select " + "then hit .)", playerName ); + + index = curses_askLetter( globals, query, texts, nTiles ); + return index; +} /* util_userPickTile */ + +static void +curses_util_userError( XW_UtilCtxt* uc, UtilErrID id ) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; + XP_Bool silent; + const XP_UCHAR* message = linux_getErrString( id, &silent ); + + if ( silent ) { + XP_LOGF( "silent userError: %s", message ); + } else { + cursesUserError( globals, message ); + } +} /* curses_util_userError */ + +static XP_Bool +curses_util_userQuery( XW_UtilCtxt* uc, UtilQueryID id, XWStreamCtxt* stream ) +{ + CursesAppGlobals* globals; + char* question; + char* answers[3] = {NULL}; + short numAnswers = 0; + XP_Bool freeMe = XP_FALSE; + XP_Bool result; + XP_U16 okIndex = 1; + + switch( id ) { + case QUERY_COMMIT_TURN: + question = strFromStream( stream ); + freeMe = XP_TRUE; + answers[numAnswers++] = "Cancel"; + answers[numAnswers++] = "Ok"; + break; + case QUERY_COMMIT_TRADE: + question = "Commit trade?"; + answers[numAnswers++] = "Cancel"; + answers[numAnswers++] = "Ok"; + break; + case QUERY_ROBOT_MOVE: + case QUERY_ROBOT_TRADE: + question = strFromStream( stream ); + freeMe = XP_TRUE; + answers[numAnswers++] = "Ok"; + okIndex = 0; + break; + + default: + XP_ASSERT( 0 ); + return 0; + } + globals = (CursesAppGlobals*)uc->closure; + result = cursesask( globals, question, numAnswers, + answers[0], answers[1], answers[2] ) == okIndex; + + if ( freeMe ) { + free( question ); + } + + return result; +} /* curses_util_userQuery */ + +static void +curses_util_trayHiddenChange( XW_UtilCtxt* XP_UNUSED(uc), + XW_TrayVisState XP_UNUSED(state), + XP_U16 XP_UNUSED(nVisibleRows) ) +{ + /* nothing to do if we don't have a scrollbar */ +} /* curses_util_trayHiddenChange */ + +static void +cursesShowFinalScores( CursesAppGlobals* globals ) +{ + XWStreamCtxt* stream; + XP_UCHAR* text; + + stream = mem_stream_make( MPPARM(globals->cGlobals.params->util->mpool) + globals->cGlobals.params->vtMgr, + globals, CHANNEL_NONE, NULL ); + server_writeFinalScores( globals->cGlobals.game.server, stream ); + + text = strFromStream( stream ); + + (void)cursesask( globals, text, 1, "Ok" ); + + free( text ); +} /* cursesShowFinalScores */ + +static void +curses_util_notifyGameOver( XW_UtilCtxt* uc ) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; + board_draw( globals->cGlobals.game.board ); + + /* game belongs in cGlobals... */ + if ( globals->cGlobals.params->printHistory ) { + catGameHistory( &globals->cGlobals ); + } + + if ( globals->cGlobals.params->quitAfter >= 0 ) { + sleep( globals->cGlobals.params->quitAfter ); + globals->timeToExit = XP_TRUE; + } else if ( globals->cGlobals.params->undoWhenDone ) { + server_handleUndo( globals->cGlobals.game.server ); + } else { + /* This is modal. Don't show if quitting */ + cursesShowFinalScores( globals ); + } +} /* curses_util_notifyGameOver */ + +static XP_Bool +curses_util_hiliteCell( XW_UtilCtxt* XP_UNUSED(uc), + XP_U16 XP_UNUSED(col), XP_U16 XP_UNUSED(row) ) +{ + return XP_TRUE; +} /* curses_util_hiliteCell */ + +static XP_Bool +curses_util_engineProgressCallback( XW_UtilCtxt* XP_UNUSED(uc) ) +{ + return XP_TRUE; +} /* curses_util_engineProgressCallback */ + +static void +curses_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why, XP_U16 when, + XWTimerProc proc, void* closure ) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; + + globals->cGlobals.timerProcs[why] = proc; + globals->cGlobals.timerClosures[why] = closure; + globals->nextTimer = util_getCurSeconds(uc) + when; +} /* curses_util_setTimer */ + +static void +curses_util_requestTime( XW_UtilCtxt* uc ) +{ + /* I've created a pipe whose read-only end is plugged into the array of + fds that my event loop polls so that I can write to it to simulate + post-event on a more familiar system. It works, so no complaints! */ + CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; + (void)write( globals->timepipe[1], "!", 1 ); +} /* curses_util_requestTime */ + +static void +initCurses( CursesAppGlobals* globals ) +{ + WINDOW* mainWin; + WINDOW* menuWin; + WINDOW* boardWin; + + int width, height; + + /* ncurses man page says most apps want this sequence */ + mainWin = initscr(); + cbreak(); + noecho(); + nonl(); + intrflush(stdscr, FALSE); + keypad(stdscr, TRUE); /* effects wgetch only? */ + + getmaxyx(mainWin, height, width ); + XP_LOGF( "getmaxyx->w:%d; h:%d", width, height ); + if ( height > CURSES_MAX_HEIGHT ) { + height = CURSES_MAX_HEIGHT; + } + if ( width > CURSES_MAX_WIDTH ) { + width = CURSES_MAX_WIDTH; + } + + globals->statusLine = height - MENU_WINDOW_HEIGHT - 1; + menuWin = newwin( MENU_WINDOW_HEIGHT, width, + height-MENU_WINDOW_HEIGHT, 0 ); + nodelay(menuWin, 1); /* don't block on getch */ + boardWin = newwin( height-MENU_WINDOW_HEIGHT, width, 0, 0 ); + + globals->menuWin = menuWin; + globals->boardWin = boardWin; + globals->mainWin = mainWin; +} /* initCurses */ + +#if 0 +static void +showStatus( CursesAppGlobals* globals ) +{ + char* str; + + switch ( globals->state ) { + case XW_SERVER_WAITING_CLIENT_SIGNON: + str = "Waiting for client[s] to connnect"; + break; + case XW_SERVER_READY_TO_PLAY: + str = "It's somebody's move"; + break; + default: + str = "unknown state"; + } + + + standout(); + mvaddstr( globals->statusLine, 0, str ); +/* clrtoeol(); */ + standend(); + + refresh(); +} /* showStatus */ +#endif + +static XP_Bool +handleQuit( CursesAppGlobals* globals ) +{ + if ( !!globals->cGlobals.params->fileName ) { + XWStreamCtxt* outStream; + + outStream = mem_stream_make( + MPPARM(globals->cGlobals.params->util->mpool) + globals->cGlobals.params->vtMgr, + &globals->cGlobals, 0, writeToFile ); + stream_open( outStream ); + + game_saveToStream( &globals->cGlobals.game, + &globals->cGlobals.params->gi, + outStream ); + + stream_destroy( outStream ); + } + + globals->timeToExit = XP_TRUE; + return XP_TRUE; +} /* handleQuit */ + +static void +checkAssignFocus( BoardCtxt* board ) +{ + if ( OBJ_NONE == board_getFocusOwner(board) ) { + board_focusChanged( board, OBJ_BOARD, XP_TRUE ); + } +} + +static XP_Bool +handleSpace( CursesAppGlobals* globals ) +{ + XP_Bool handled; + checkAssignFocus( globals->cGlobals.game.board ); + + globals->doDraw = board_handleKey( globals->cGlobals.game.board, + XP_RAISEFOCUS_KEY, &handled ); + return XP_TRUE; +} /* handleSpace */ + +static XP_Bool +handleRet( CursesAppGlobals* globals ) +{ + XP_Bool handled; + globals->doDraw = board_handleKey( globals->cGlobals.game.board, + XP_RETURN_KEY, &handled ); + return XP_TRUE; +} /* handleRet */ + +static XP_Bool +handleHint( CursesAppGlobals* globals ) +{ + XP_Bool redo; + globals->doDraw = board_requestHint( globals->cGlobals.game.board, +#ifdef XWFEATURE_SEARCHLIMIT + XP_FALSE, +#endif + &redo ); + return XP_TRUE; +} /* handleHint */ + +static XP_Bool +handleCommit( CursesAppGlobals* globals ) +{ + globals->doDraw = board_commitTurn( globals->cGlobals.game.board ); + return XP_TRUE; +} /* handleCommit */ + +static XP_Bool +handleJuggle( CursesAppGlobals* globals ) +{ + globals->doDraw = board_juggleTray( globals->cGlobals.game.board ); + return XP_TRUE; +} /* handleJuggle */ + +static XP_Bool +handleHide( CursesAppGlobals* globals ) +{ + XW_TrayVisState curState = + board_getTrayVisState( globals->cGlobals.game.board ); + + if ( curState == TRAY_REVEALED ) { + globals->doDraw = board_hideTray( globals->cGlobals.game.board ); + } else { + globals->doDraw = board_showTray( globals->cGlobals.game.board ); + } + + return XP_TRUE; +} /* handleJuggle */ + +#ifdef CURSES_SMALL_SCREEN +static XP_Bool +handleRootKeyShow( CursesAppGlobals* globals ) +{ + WINDOW* win; + MenuList* lists[] = { g_sharedMenuList, globals->menuList, + g_rootMenuListHide, NULL }; + int winMaxY, winMaxX; + + wclear( globals->menuWin ); + wrefresh( globals->menuWin ); + + getmaxyx( globals->boardWin, winMaxY, winMaxX ); + + int border = 2; + int width = winMaxX - (border * 2); + int padding = 1; /* for the box */ + int nLines, nCols; + countMenuLines( lists, width, padding, &nLines, &nCols ); + + if ( width > nCols ) { + width = nCols; + } + + win = newwin( nLines+(padding*2), width+(padding*2), + ((winMaxY-nLines-padding-padding)/2), (winMaxX-width)/2 ); + wclear( win ); + box( win, '|', '-'); + + drawMenuFromList( win, lists, nLines, padding ); + wrefresh( win ); + + CursesMenuHandler handler = NULL; + while ( !handler ) { + int ch = fgetc( stdin ); + + int i; + for ( i = 0; !!lists[i]; ++i ) { + handler = getHandlerForKey( lists[i], ch ); + if ( !!handler ) { + break; + } + } + } + + delwin( win ); + + touchwin( globals->boardWin ); + wrefresh( globals->boardWin ); + MenuList* ml[] = { g_rootMenuListShow, NULL }; + drawMenuFromList( globals->menuWin, ml, 1, 0 ); + wrefresh( globals->menuWin ); + + return handler != NULL && (*handler)(globals); +} /* handleRootKeyShow */ + +static XP_Bool +handleRootKeyHide( CursesAppGlobals* globals ) +{ + globals->doDraw = XP_TRUE; + return XP_TRUE; +} +#endif + +static XP_Bool +handleAltLeft( CursesAppGlobals* globals ) +{ + return handleFocusKey( globals, XP_CURSOR_KEY_ALTLEFT ); +} + +static XP_Bool +handleAltRight( CursesAppGlobals* globals ) +{ + return handleFocusKey( globals, XP_CURSOR_KEY_ALTRIGHT ); +} + +static XP_Bool +handleAltUp( CursesAppGlobals* globals ) +{ + return handleFocusKey( globals, XP_CURSOR_KEY_ALTUP ); +} + +static XP_Bool +handleAltDown( CursesAppGlobals* globals ) +{ + return handleFocusKey( globals, XP_CURSOR_KEY_ALTDOWN ); +} + +static XP_Bool +handleFlip( CursesAppGlobals* globals ) +{ + globals->doDraw = board_flip( globals->cGlobals.game.board ); + return XP_TRUE; +} /* handleFlip */ + +static XP_Bool +handleToggleValues( CursesAppGlobals* globals ) +{ + globals->doDraw = board_toggle_showValues( globals->cGlobals.game.board ); + return XP_TRUE; +} /* handleToggleValues */ + +static XP_Bool +handleBackspace( CursesAppGlobals* globals ) +{ + XP_Bool handled; + globals->doDraw = board_handleKey( globals->cGlobals.game.board, + XP_CURSOR_KEY_DEL, &handled ); + return XP_TRUE; +} /* handleBackspace */ + +static XP_Bool +handleUndo( CursesAppGlobals* globals ) +{ + globals->doDraw = server_handleUndo( globals->cGlobals.game.server ); + return XP_TRUE; +} /* handleUndo */ + +static XP_Bool +handleReplace( CursesAppGlobals* globals ) +{ + globals->doDraw = board_replaceTiles( globals->cGlobals.game.board ); + return XP_TRUE; +} /* handleReplace */ + +#ifdef KEYBOARD_NAV +static XP_Bool +handleFocusKey( CursesAppGlobals* globals, XP_Key key ) +{ + XP_Bool handled; + XP_Bool draw; + + checkAssignFocus( globals->cGlobals.game.board ); + + draw = board_handleKey( globals->cGlobals.game.board, key, &handled ); + if ( !handled ) { + BoardObjectType nxt; + BoardObjectType order[] = { OBJ_BOARD, OBJ_SCORE, OBJ_TRAY }; + draw = linShiftFocus( &globals->cGlobals, key, order, &nxt ) || draw; + if ( nxt != OBJ_NONE ) { + changeMenuForFocus( globals, nxt ); + } + } + + globals->doDraw = draw || globals->doDraw; + return XP_TRUE; +} + +static XP_Bool +handleLeft( CursesAppGlobals* globals ) +{ + return handleFocusKey( globals, XP_CURSOR_KEY_LEFT ); +} /* handleLeft */ + +static XP_Bool +handleRight( CursesAppGlobals* globals ) +{ + return handleFocusKey( globals, XP_CURSOR_KEY_RIGHT ); +} /* handleRight */ + +static XP_Bool +handleUp( CursesAppGlobals* globals ) +{ + return handleFocusKey( globals, XP_CURSOR_KEY_UP ); +} /* handleUp */ + +static XP_Bool +handleDown( CursesAppGlobals* globals ) +{ + return handleFocusKey( globals, XP_CURSOR_KEY_DOWN ); +} /* handleDown */ +#endif + +static void +fmtMenuItem( const MenuList* item, char* buf, int maxLen ) +{ + snprintf( buf, maxLen, "%s %s", item->keyDesc, item->desc ); +} + + +static void +countMenuLines( const MenuList** menuLists, int maxX, int padding, + int* nLinesP, int* nColsP ) +{ + int nCols = 0; + /* The menu space should be wider rather than taller, but line up by + column. So we want to use as many columns as possible to minimize the + number of lines. So start with one line and lay out. If that doesn't + fit, try two. Given the number of lines, get the max width of each + column. + */ + + maxX -= padding * 2; /* on left and right side */ + + int nLines; + for ( nLines = 1; ; ++nLines ) { + short line = 0; + XP_Bool tooFewLines = XP_FALSE; + int maxThisCol = 0; + int i; + nCols = 0; + + for ( i = 0; !tooFewLines && (NULL != menuLists[i]); ++i ) { + const MenuList* entry; + for ( entry = menuLists[i]; !tooFewLines && !!entry->handler; + ++entry ) { + int width; + char buf[32]; + + /* time to switch to new column? */ + if ( line == nLines ) { + nCols += maxThisCol; + if ( nCols > maxX ) { + tooFewLines = XP_TRUE; + break; + } + maxThisCol = 0; + line = 0; + } + + fmtMenuItem( entry, buf, sizeof(buf) ); + width = strlen(buf) + 2; /* padding */ + + if ( maxThisCol < width ) { + maxThisCol = width; + } + + ++line; + } + } + /* If we get here without running out of space, we're done */ + nCols += maxThisCol; + if ( !tooFewLines && (nCols < maxX) ) { + break; + } + } + + *nColsP = nCols; + *nLinesP = nLines; +} /* countMenuLines */ + +static void +drawMenuFromList( WINDOW* win, const MenuList** menuLists, + int nLines, int padding ) +{ + short line = 0, col, i; + int winMaxY, winMaxX; + + getmaxyx( win, winMaxY, winMaxX ); + + int maxColWidth = 0; + if ( 0 == nLines ) { + int ignore; + countMenuLines( menuLists, winMaxX, padding, &nLines, &ignore ); + } + col = 0; + + for ( i = 0; NULL != menuLists[i]; ++i ) { + const MenuList* entry; + for ( entry = menuLists[i]; !!entry->handler; ++entry ) { + char buf[32]; + + fmtMenuItem( entry, buf, sizeof(buf) ); + + mvwaddstr( win, line+padding, col+padding, buf ); + + int width = strlen(buf) + 2; + if ( width > maxColWidth ) { + maxColWidth = width; + } + + if ( ++line == nLines ) { + line = 0; + col += maxColWidth; + maxColWidth = 0; + } + + } + } +} /* drawMenuFromList */ + +static void +SIGWINCH_handler( int signal ) +{ + int x, y; + + assert( signal == SIGWINCH ); + + endwin(); + +/* (*globals.drawMenu)( &globals ); */ + + getmaxyx( stdscr, y, x ); + wresize( g_globals.mainWin, y-MENU_WINDOW_HEIGHT, x ); + + board_draw( g_globals.cGlobals.game.board ); +} /* SIGWINCH_handler */ + +static void +cursesListenOnSocket( CursesAppGlobals* globals, int newSock ) +{ + XP_ASSERT( globals->fdCount+1 < FD_MAX ); + + XP_WARNF( "setting fd[%d] to %d", globals->fdCount, newSock ); + globals->fdArray[globals->fdCount].fd = newSock; + globals->fdArray[globals->fdCount].events = POLLIN; + + ++globals->fdCount; + XP_LOGF( "listenOnSocket: there are now %d sources to poll", + globals->fdCount ); +} /* cursesListenOnSocket */ + +static void +curses_stop_listening( CursesAppGlobals* globals, int sock ) +{ + int count = globals->fdCount; + int i; + bool found = false; + + for ( i = 0; i < count; ++i ) { + if ( globals->fdArray[i].fd == sock ) { + found = true; + } else if ( found ) { + globals->fdArray[i-1].fd = globals->fdArray[i].fd; + } + } + + assert( found ); + --globals->fdCount; +} /* curses_stop_listening */ + +static void +curses_socket_changed( void* closure, int oldSock, int newSock, + void** XP_UNUSED(storage) ) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)closure; + if ( oldSock != -1 ) { + curses_stop_listening( globals, oldSock ); + } + if ( newSock != -1 ) { + cursesListenOnSocket( globals, newSock ); + } +} /* curses_socket_changed */ + +static void +curses_socket_acceptor( int listener, Acceptor func, CommonGlobals* cGlobals, + void** XP_UNUSED(storage) ) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)cGlobals; + XP_ASSERT( !cGlobals->acceptor || (func == cGlobals->acceptor) ); + cGlobals->acceptor = func; + globals->csInfo.server.serverSocket = listener; + cursesListenOnSocket( globals, listener ); +} + +#ifdef RELAY_HEARTBEAT +static int +figureTimeout( CursesAppGlobals* globals ) +{ + int result = INFINITE_TIMEOUT; + if ( globals->cGlobals.timerProcs[TIMER_HEARTBEAT] != 0 ) { + XP_U32 now = util_getCurSeconds( globals->cGlobals.params->util ); + XP_U32 then = globals->nextTimer; + if ( now >= then ) { + result = 0; + } else { + result = (then - now) * 1000; + } + } + return result; +} /* figureTimeout */ +#else +# define figureTimeout(g) INFINITE_TIMEOUT +#endif + +/* + * Ok, so this doesn't block yet.... + */ +static XP_Bool +blocking_gotEvent( CursesAppGlobals* globals, int* ch ) +{ + XP_Bool result = XP_FALSE; + int numEvents; + short fdIndex; + XP_Bool redraw = XP_FALSE; + + int timeout = figureTimeout( globals ); + numEvents = poll( globals->fdArray, globals->fdCount, timeout ); + + if ( timeout != INFINITE_TIMEOUT && numEvents == 0 ) { +#ifdef RELAY_HEARTBEAT + if ( !globals->cGlobals.params->noHeartbeat ) { + linuxFireTimer( &globals->cGlobals, TIMER_HEARTBEAT ); + } +#endif + } else if ( numEvents > 0 ) { + + /* stdin first */ + if ( (globals->fdArray[FD_STDIN].revents & POLLIN) != 0 ) { + int evtCh = wgetch(globals->mainWin); + XP_LOGF( "%s: got key: %x", __func__, evtCh ); + *ch = evtCh; + result = XP_TRUE; + --numEvents; + } + if ( (globals->fdArray[FD_STDIN].revents & ~POLLIN ) ) { + XP_LOGF( "some other events set on stdin" ); + } + + if ( (globals->fdArray[FD_TIMEEVT].revents & POLLIN) != 0 ) { + char ch; + /* XP_DEBUGF( "curses got a USER EVENT\n" ); */ + (void)read(globals->fdArray[FD_TIMEEVT].fd, &ch, 1 ); + } + + fdIndex = FD_FIRSTSOCKET; + + if ( numEvents > 0 && + (globals->fdArray[fdIndex].revents & POLLIN) != 0 ) { + + --numEvents; + + if ( globals->fdArray[fdIndex].fd + == globals->csInfo.server.serverSocket ) { + /* It's the listening socket: call platform's accept() + wrapper */ + (*globals->cGlobals.acceptor)( globals->fdArray[fdIndex].fd, + globals ); + } else { +#ifndef XWFEATURE_STANDALONE_ONLY + unsigned char buf[256]; + int nBytes; + /* It's a normal data socket */ + if ( 0 ) { +#ifdef XWFEATURE_RELAY + } else if ( globals->cGlobals.params->conType + == COMMS_CONN_RELAY ) { + nBytes = linux_relay_receive( &globals->cGlobals, buf, + sizeof(buf) ); +#endif +#ifdef XWFEATURE_BLUETOOTH + } else if ( globals->cGlobals.params->conType + == COMMS_CONN_BT ) { + nBytes = linux_bt_receive( globals->fdArray[fdIndex].fd, + buf, sizeof(buf) ); +#endif + } else { + XP_ASSERT( 0 ); + } + + if ( nBytes != -1 ) { + XWStreamCtxt* inboundS; + struct sockaddr_in addr_sock = {0}; + redraw = XP_FALSE; + + XP_STATUSF( "linuxReceive=>%d", nBytes ); + inboundS = stream_from_msgbuf( &globals->cGlobals, + buf, nBytes ); + if ( !!inboundS ) { + CommsAddrRec addrRec; + + XP_MEMSET( &addrRec, 0, sizeof(addrRec) ); + addrRec.conType = COMMS_CONN_RELAY; + + addrRec.u.ip_relay.ipAddr = + ntohl(addr_sock.sin_addr.s_addr); + XP_LOGF( "captured incoming ip address: 0x%lx", + addrRec.u.ip_relay.ipAddr ); + + if ( comms_checkIncomingStream( + globals->cGlobals.game.comms, + inboundS, &addrRec ) ) { + XP_LOGF( "comms read port: %d", + addrRec.u.ip_relay.port ); + redraw = server_receiveMessage( + globals->cGlobals.game.server, inboundS ); + } + stream_destroy( inboundS ); + } + + /* if there's something to draw resulting from the + message, we need to give the main loop time to reflect + that on the screen before giving the server another + shot. So just call the idle proc. */ + if ( redraw ) { + curses_util_requestTime(globals->cGlobals.params->util); + } + } +#else + XP_ASSERT(0); /* no socket activity in standalone game! */ +#endif /* #ifndef XWFEATURE_STANDALONE_ONLY */ + } + ++fdIndex; + } + + redraw = server_do( globals->cGlobals.game.server ) || redraw; + if ( redraw ) { + /* messages change a lot */ + board_invalAll( globals->cGlobals.game.board ); + board_draw( globals->cGlobals.game.board ); + } + } + return result; +} /* blocking_gotEvent */ + +static void +remapKey( int* kp ) +{ + /* There's what the manual says I should get, and what I actually do from + * a funky M$ keyboard.... + */ + int key = *kp; + switch( key ) { + case KEY_B2: /* "center of keypad" */ + key = '\r'; + break; + case KEY_DOWN: + case 526: + key = 'K'; + break; + case KEY_UP: + case 523: + key = 'J'; + break; + case KEY_LEFT: + case 524: + key = 'H'; + break; + case KEY_RIGHT: + case 525: + key = 'L'; + break; + default: + if ( key > 0x7F ) { + XP_LOGF( "%s(%d): no mapping", __func__, key ); + } + break; + } + *kp = key; +} /* remapKey */ + +static void +drawMenuLargeOrSmall( CursesAppGlobals* globals, const MenuList* menuList ) +{ +#ifdef CURSES_SMALL_SCREEN + const MenuList* lists[] = { g_rootMenuListShow, NULL }; +#else + const MenuList* lists[] = { g_sharedMenuList, menuList, NULL }; +#endif + wclear( globals->menuWin ); + drawMenuFromList( globals->menuWin, lists, 0, 0 ); + wrefresh( globals->menuWin ); +} + +static void +changeMenuForFocus( CursesAppGlobals* globals, BoardObjectType focussed ) +{ +#ifdef KEYBOARD_NAV + if ( focussed == OBJ_TRAY ) { + globals->menuList = g_trayMenuList; + } else if ( focussed == OBJ_BOARD ) { + globals->menuList = g_boardMenuList; + } else if ( focussed == OBJ_SCORE ) { + globals->menuList = g_scoreMenuList; + } else { + XP_ASSERT(0); + } + drawMenuLargeOrSmall( globals, globals->menuList ); +#endif +} /* changeMenuForFocus */ + +#if 0 +static void +initClientSocket( CursesAppGlobals* globals, char* serverName ) +{ + struct hostent* hostinfo; + hostinfo = gethostbyname( serverName ); + if ( !hostinfo ) { + userError( globals, "unable to get host info for %s\n", serverName ); + } else { + char* hostName = inet_ntoa( *(struct in_addr*)hostinfo->h_addr ); + XP_LOGF( "gethostbyname returned %s", hostName ); + globals->csInfo.client.serverAddr = inet_addr(hostName); + XP_LOGF( "inet_addr returned %lu", + globals->csInfo.client.serverAddr ); + } +} /* initClientSocket */ +#endif + +static VTableMgr* +curses_util_getVTManager(XW_UtilCtxt* uc) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; + return globals->cGlobals.params->vtMgr; +} /* linux_util_getVTManager */ + +static XP_Bool +curses_util_askPassword( XW_UtilCtxt* XP_UNUSED(uc), + const XP_UCHAR* XP_UNUSED(name), + XP_UCHAR* XP_UNUSED(buf), XP_U16* XP_UNUSED(len) ) +{ + XP_WARNF( "curses_util_askPassword not implemented" ); + return XP_FALSE; +} /* curses_util_askPassword */ + +static void +curses_util_yOffsetChange( XW_UtilCtxt* XP_UNUSED(uc), XP_U16 oldOffset, + XP_U16 newOffset ) +{ + if ( oldOffset != newOffset ) { + XP_WARNF( "curses_util_yOffsetChange(%d,%d) not implemented", + oldOffset, newOffset ); + } +} /* curses_util_yOffsetChange */ + +static XP_Bool +curses_util_warnIllegalWord( XW_UtilCtxt* XP_UNUSED(uc), + BadWordInfo* XP_UNUSED(bwi), + XP_U16 XP_UNUSED(player), + XP_Bool XP_UNUSED(turnLost) ) +{ + XP_WARNF( "curses_util_warnIllegalWord not implemented" ); + return XP_FALSE; +} /* curses_util_warnIllegalWord */ + +static void +curses_util_remSelected( XW_UtilCtxt* uc ) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; + XWStreamCtxt* stream; + XP_UCHAR* text; + + stream = mem_stream_make( MPPARM(globals->cGlobals.params->util->mpool) + globals->cGlobals.params->vtMgr, + globals, CHANNEL_NONE, NULL ); + board_formatRemainingTiles( globals->cGlobals.game.board, stream ); + + text = strFromStream( stream ); + + (void)cursesask( globals, text, 1, "Ok" ); + + free( text ); +} + +#ifndef XWFEATURE_STANDALONE_ONLY +static void +cursesSendOnClose( XWStreamCtxt* stream, void* closure ) +{ + XP_S16 result; + CursesAppGlobals* globals = (CursesAppGlobals*)closure; + + XP_LOGF( "cursesSendOnClose called" ); + result = comms_send( globals->cGlobals.game.comms, stream ); +} /* cursesSendOnClose */ + +static XWStreamCtxt* +curses_util_makeStreamFromAddr(XW_UtilCtxt* uc, XP_PlayerAddr channelNo ) +{ + CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure; + LaunchParams* params = globals->cGlobals.params; + + XWStreamCtxt* stream = mem_stream_make( MPPARM(uc->mpool) + params->vtMgr, + uc->closure, channelNo, + cursesSendOnClose ); + return stream; +} /* curses_util_makeStreamFromAddr */ +#endif + +static void +setupCursesUtilCallbacks( CursesAppGlobals* globals, XW_UtilCtxt* util ) +{ + util->vtable->m_util_userError = curses_util_userError; + + util->vtable->m_util_getVTManager = curses_util_getVTManager; + util->vtable->m_util_askPassword = curses_util_askPassword; + util->vtable->m_util_yOffsetChange = curses_util_yOffsetChange; + util->vtable->m_util_warnIllegalWord = curses_util_warnIllegalWord; + util->vtable->m_util_remSelected = curses_util_remSelected; +#ifndef XWFEATURE_STANDALONE_ONLY + util->vtable->m_util_makeStreamFromAddr = curses_util_makeStreamFromAddr; +#endif + util->vtable->m_util_userQuery = curses_util_userQuery; + util->vtable->m_util_userPickTile = curses_util_userPickTile; + util->vtable->m_util_trayHiddenChange = curses_util_trayHiddenChange; + util->vtable->m_util_notifyGameOver = curses_util_notifyGameOver; + util->vtable->m_util_hiliteCell = curses_util_hiliteCell; + util->vtable->m_util_engineProgressCallback = + curses_util_engineProgressCallback; + + util->vtable->m_util_setTimer = curses_util_setTimer; + util->vtable->m_util_requestTime = curses_util_requestTime; + + util->closure = globals; +} /* setupCursesUtilCallbacks */ + +#ifndef XWFEATURE_STANDALONE_ONLY +static void +sendOnClose( XWStreamCtxt* stream, void* closure ) +{ + CursesAppGlobals* globals = closure; + XP_LOGF( "curses sendOnClose called" ); + XP_ASSERT( !!globals->cGlobals.game.comms ); + comms_send( globals->cGlobals.game.comms, stream ); +} /* sendOnClose */ +#endif + +static CursesMenuHandler +getHandlerForKey( const MenuList* list, char ch ) +{ + CursesMenuHandler handler = NULL; + while ( list->handler != NULL ) { + if ( list->key == ch ) { + handler = list->handler; + break; + } + ++list; + } + return handler; +} + +static XP_Bool +handleKeyEvent( CursesAppGlobals* globals, const MenuList* list, char ch ) +{ + CursesMenuHandler handler = getHandlerForKey( list, ch ); + XP_Bool result = XP_FALSE; + if ( !!handler ) { + result = (*handler)(globals); + } + return result; +} /* handleKeyEvent */ + +static XP_Bool +passKeyToBoard( CursesAppGlobals* globals, char ch ) +{ + XP_Bool handled = ch >= 'a' && ch <= 'z'; + if ( handled ) { + ch += 'A' - 'a'; + globals->doDraw = board_handleKey( globals->cGlobals.game.board, + ch, NULL ); + } + return handled; +} /* passKeyToBoard */ + +static void +positionSizeStuff( CursesAppGlobals* globals, int width, int height ) +{ + XP_U16 cellWidth, cellHt, scoreLeft, scoreWidth; + BoardCtxt* board = globals->cGlobals.game.board; + int remWidth = width; + + board_setPos( board, BOARD_OFFSET, BOARD_OFFSET, XP_FALSE ); + cellWidth = CURSES_CELL_WIDTH; + cellHt = CURSES_CELL_HT; + board_setScale( board, cellWidth, cellHt ); + scoreLeft = (cellWidth * MAX_COLS);// + BOARD_SCORE_PADDING; + remWidth -= cellWidth * MAX_COLS; + + /* If the scoreboard will right of the board, put it there. Otherwise try + to fit it below the boards. */ + int tileWidth = 3; + int trayWidth = (tileWidth*MAX_TRAY_TILES); + int trayLeft = scoreLeft; + int trayTop; + int trayHt = 4; + if ( trayWidth < remWidth ) { + trayLeft += XP_MIN(remWidth - trayWidth, BOARD_SCORE_PADDING ); + trayTop = 8; + } else { + trayLeft = BOARD_OFFSET; + trayTop = BOARD_OFFSET + (cellHt * MAX_ROWS); + if ( trayTop + trayHt > height ) { + trayHt = height - trayTop; + } + } + board_setTrayLoc( board, trayLeft, trayTop, (3*MAX_TRAY_TILES)+1, + trayHt, 1 ); + + scoreWidth = remWidth; + if ( scoreWidth > 45 ) { + scoreWidth = 45; + scoreLeft += (remWidth - scoreWidth) / 2; + } + board_setScoreboardLoc( board, scoreLeft, 1, + scoreWidth, 5, /*4 players + rem*/ XP_FALSE ); + + /* no divider -- yet */ + /* board_setTrayVisible( globals.board, XP_TRUE, XP_FALSE ); */ + + board_invalAll( board ); +} /* positionSizeStuff */ + +void +cursesmain( XP_Bool isServer, LaunchParams* params ) +{ + int piperesult; + DictionaryCtxt* dict; + XP_U16 gameID; + int width, height; + + memset( &g_globals, 0, sizeof(g_globals) ); + + g_globals.amServer = isServer; + g_globals.cGlobals.params = params; +#ifdef XWFEATURE_RELAY + g_globals.cGlobals.socket = -1; +#endif + + g_globals.cGlobals.socketChanged = curses_socket_changed; + g_globals.cGlobals.socketChangedClosure = &g_globals; + g_globals.cGlobals.addAcceptor = curses_socket_acceptor; + + g_globals.cp.showBoardArrow = XP_TRUE; + g_globals.cp.showRobotScores = params->showRobotScores; + + dict = params->dict; + + setupCursesUtilCallbacks( &g_globals, params->util ); + +#ifdef XWFEATURE_RELAY + if ( params->conType == COMMS_CONN_RELAY ) { + g_globals.cGlobals.defaultServerName + = params->connInfo.relay.relayName; + } +#endif + cursesListenOnSocket( &g_globals, 0 ); /* stdin */ + + piperesult = pipe( g_globals.timepipe ); + XP_ASSERT( piperesult == 0 ); + + /* reader pipe */ + cursesListenOnSocket( &g_globals, g_globals.timepipe[0] ); + signal( SIGWINCH, SIGWINCH_handler ); + + initCurses( &g_globals ); + getmaxyx( g_globals.boardWin, height, width ); + + g_globals.draw = (struct CursesDrawCtx*) + cursesDrawCtxtMake( g_globals.boardWin ); + + if ( !!params->fileName && file_exists( params->fileName ) ) { + XWStreamCtxt* stream; + stream = streamFromFile( &g_globals.cGlobals, params->fileName, &g_globals ); + + (void)game_makeFromStream( MEMPOOL stream, &g_globals.cGlobals.game, + ¶ms->gi, dict, params->util, + (DrawCtx*)g_globals.draw, + &g_globals.cp, + LINUX_SEND, IF_CH(linux_reset) &g_globals ); + + stream_destroy( stream ); + } else { + gameID = (XP_U16)util_getCurSeconds( g_globals.cGlobals.params->util ); + game_makeNewGame( MEMPOOL &g_globals.cGlobals.game, ¶ms->gi, + params->util, (DrawCtx*)g_globals.draw, + gameID, &g_globals.cp, LINUX_SEND, + IF_CH(linux_reset) &g_globals ); + } + +#ifndef XWFEATURE_STANDALONE_ONLY + if ( g_globals.cGlobals.game.comms ) { + CommsAddrRec addr; + + if ( 0 ) { +# ifdef XWFEATURE_RELAY + } else if ( params->conType == COMMS_CONN_RELAY ) { + addr.conType = COMMS_CONN_RELAY; + addr.u.ip_relay.ipAddr = 0; /* ??? */ + addr.u.ip_relay.port = params->connInfo.relay.defaultSendPort; + XP_STRNCPY( addr.u.ip_relay.hostName, params->connInfo.relay.relayName, + sizeof(addr.u.ip_relay.hostName) - 1 ); + XP_STRNCPY( addr.u.ip_relay.cookie, params->connInfo.relay.cookie, + sizeof(addr.u.ip_relay.cookie) - 1 ); +# endif +# ifdef XWFEATURE_BLUETOOTH + } else if ( params->conType == COMMS_CONN_BT ) { + addr.conType = COMMS_CONN_BT; + XP_ASSERT( sizeof(addr.u.bt.btAddr) + >= sizeof(params->connInfo.bt.hostAddr)); + XP_MEMCPY( &addr.u.bt.btAddr, ¶ms->connInfo.bt.hostAddr, + sizeof(params->connInfo.bt.hostAddr) ); +# endif + } + comms_setAddr( g_globals.cGlobals.game.comms, &addr ); + } +#endif + + model_setDictionary( g_globals.cGlobals.game.model, params->dict ); + + positionSizeStuff( &g_globals, width, height ); + +#ifndef XWFEATURE_STANDALONE_ONLY + /* send any events that need to get off before the event loop begins */ + if ( !isServer ) { + if ( 1 /* stream_open( params->info.clientInfo.stream ) */) { + server_initClientConnection( g_globals.cGlobals.game.server, + mem_stream_make( MEMPOOL + params->vtMgr, + &g_globals, + (XP_PlayerAddr)0, + sendOnClose ) ); + } else { + cursesUserError( &g_globals, "Unable to open connection to server"); + exit( 0 ); + } + } +#endif + + server_do( g_globals.cGlobals.game.server ); + + g_globals.menuList = g_boardMenuList; + drawMenuLargeOrSmall( &g_globals, g_boardMenuList ); + board_draw( g_globals.cGlobals.game.board ); + + while ( !g_globals.timeToExit ) { + int ch = 0; + if ( blocking_gotEvent( &g_globals, &ch ) ) { + remapKey( &ch ); + if ( +#ifdef CURSES_SMALL_SCREEN + handleKeyEvent( &g_globals, g_rootMenuListShow, ch ) || +#endif + handleKeyEvent( &g_globals, g_globals.menuList, ch ) + || handleKeyEvent( &g_globals, g_sharedMenuList, ch ) + || passKeyToBoard( &g_globals, ch ) ) { + if ( g_globals.doDraw ) { + board_draw( g_globals.cGlobals.game.board ); + g_globals.doDraw = XP_FALSE; + } + } + } + } + + endwin(); +} /* cursesmain */ +#endif /* PLATFORM_NCURSES */ diff --git a/xwords4/linux/cursesmain.h b/xwords4/linux/cursesmain.h new file mode 100644 index 000000000..fe9984265 --- /dev/null +++ b/xwords4/linux/cursesmain.h @@ -0,0 +1,112 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997-2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CURSESMAIN_H_ +#define _CURSESMAIN_H_ + + +#include +#include +#include +#include +#include + +#include + +#include "draw.h" +#include "main.h" +#include "board.h" +#include "model.h" +#include "dictnry.h" +#include "xwstream.h" +#include "comms.h" +#include "server.h" +#include "xwstate.h" +#include "util.h" +/* #include "compipe.h" */ + +typedef struct CursesAppGlobals CursesAppGlobals; + +typedef XP_Bool (*EventFunc)(CursesAppGlobals* globals, int ch); +/* typedef void (*MenuDrawer)(CursesAppGlobals* globals); */ + +#define FD_MAX 6 +#define FD_STDIN 0 +#define FD_TIMEEVT 1 +#define FD_FIRSTSOCKET 2 + +struct CursesAppGlobals { + CommonGlobals cGlobals; + + struct CursesDrawCtx* draw; + + DictionaryCtxt* dictionary; + EngineCtxt* engine; + CommonPrefs cp; + + XP_Bool amServer; /* this process acting as server */ + + WINDOW* mainWin; + WINDOW* menuWin; + WINDOW* boardWin; + + XP_Bool timeToExit; + XP_Bool doDraw; + const struct MenuList* menuList; + XP_U16 nLinesMenu; + + union { + struct { + XWStreamCtxt* stream; /* how we can reach the server */ + } client; + struct { + int serverSocket; + XP_Bool socketOpen; + } server; + } csInfo; + + short statusLine; + XWGameState state; + + struct sockaddr_in listenerSockAddr; + short fdCount; + struct pollfd fdArray[FD_MAX]; /* one for stdio, one for listening socket */ + + int timepipe[2]; /* for reading/writing "user events" */ + + XP_U32 nextTimer; +}; + + +DrawCtx* cursesDrawCtxtMake( WINDOW* boardWin ); + +/* Ports: Client and server pick a port at startup on which they'll listen. + * If both are to be on the same device using localhost as their ip address, + * then they need to be listening on different ports. Server finds out what + * port client is listening on from the return address of the first message + * client sends -- I think. (I'm not sure that when I create a socket to use + * to SEND to the server that I specify the port on which I'm listening. + * Maybe I need to include that in a platform-specific part of the connect + * message.... Clearly there will need to be such a thing. + */ + + +void cursesmain( XP_Bool isServer, LaunchParams* params ); + +#endif diff --git a/xwords4/linux/filestream.c b/xwords4/linux/filestream.c new file mode 100644 index 000000000..e14a6f24e --- /dev/null +++ b/xwords4/linux/filestream.c @@ -0,0 +1,176 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#if 0 +#include + +#include "xwstream.h" + +typedef struct LinuxFileStreamCtxt { + StreamCtxVTable* vtable; + + FILE* file; +} LinuxFileStreamCtxt; + +static void make_vtable( LinuxFileStreamCtxt* stream ); + + +XWStreamCtxt* +linux_make_fileStream( char* fileName, XP_Bool forWriting ) +{ + LinuxFileStreamCtxt* result = malloc( sizeof(*result) ); + XP_MEMSET( result, 0, sizeof(*result) ); + + make_vtable( result ); + + result->file = fopen( fileName, forWriting? "w":"r" ); + XP_ASSERT( result->file ); + + return (XWStreamCtxt*)result; +} /* linux_make_fileStream */ + +static void +linux_file_stream_getBytes( XWStreamCtxt* p_sctx, void* where, + XP_U16 count ) +{ + LinuxFileStreamCtxt* stream = (LinuxFileStreamCtxt*)p_sctx; + XP_ASSERT( !!stream->file ); + + fread( where, count, 1, stream->file ); +} /* linux_file_stream_getBytes */ + +static XP_U8 +linux_file_stream_getU8( XWStreamCtxt* p_sctx ) +{ + XP_U8 result; + linux_file_stream_getBytes( p_sctx, &result, sizeof(result) ); + return result; +} /* linux_file_stream_getU8 */ + +static XP_U16 +linux_file_stream_getU16( XWStreamCtxt* p_sctx ) +{ + XP_U16 result; + linux_file_stream_getBytes( p_sctx, &result, sizeof(result) ); + return result; +} /* linux_file_stream_getU16 */ + +static XP_U32 +linux_file_stream_getU32( XWStreamCtxt* p_sctx ) +{ + XP_U32 result; + linux_file_stream_getBytes( p_sctx, &result, sizeof(result) ); + return result; +} /* linux_file_stream_getU32 */ + +static void +linux_file_stream_putBytes( XWStreamCtxt* p_sctx, void* where, + XP_U16 count ) +{ + LinuxFileStreamCtxt* stream = (LinuxFileStreamCtxt*)p_sctx; + size_t written; + XP_ASSERT( !!stream->file ); + + written = fwrite( where, count, 1, stream->file ); + XP_ASSERT( written != 0 ); +} /* linux_file_stream_putBytes */ + +static void +linux_file_stream_putString( XWStreamCtxt* p_sctx, const char* where ) +{ + linux_file_stream_putBytes( p_sctx, (void*)where, XP_STRLEN(where) ); +} + +static void +linux_file_stream_putU8( XWStreamCtxt* p_sctx, XP_U8 data ) +{ + linux_file_stream_putBytes( p_sctx, &data, sizeof(data) ); +} /* linux_file_stream_putU8 */ + +static void +linux_file_stream_putU16( XWStreamCtxt* p_sctx, XP_U16 data ) +{ + linux_file_stream_putBytes( p_sctx, &data, sizeof(data) ); +} /* linux_common_stream_putUI16 */ + +static void +linux_file_stream_putU32( XWStreamCtxt* p_sctx, XP_U32 data ) +{ + linux_file_stream_putBytes( p_sctx, &data, sizeof(data) ); +} /* linux_file_stream_putUI32 */ + +static void +linux_file_stream_open( XWStreamCtxt* p_sctx ) +{ + LinuxFileStreamCtxt* stream = (LinuxFileStreamCtxt*)p_sctx; + XP_ASSERT( !!stream->file ); + rewind( stream->file ); +} /* linux_file_stream_open */ + +static void +linux_file_stream_close( XWStreamCtxt* p_sctx ) +{ + LinuxFileStreamCtxt* stream = (LinuxFileStreamCtxt*)p_sctx; + XP_ASSERT( !!stream->file ); + fclose( stream->file ); + stream->file = NULL; +} /* linux_file_stream_close */ + +static void +linux_file_stream_destroy( XWStreamCtxt* p_sctx ) +{ + LinuxFileStreamCtxt* stream; + + stream = (LinuxFileStreamCtxt*)p_sctx; + if ( !!stream->file ) { + stream_close( stream ); + } + + free( p_sctx->vtable ); + free( stream ); +} /* linux_file_stream_destroy */ + +static void +make_vtable( LinuxFileStreamCtxt* stream ) +{ + XP_ASSERT( !stream->vtable ); + stream->vtable = malloc( sizeof(*stream->vtable) ); + + SET_VTABLE_ENTRY( stream, stream_getU8, linux_file ); + SET_VTABLE_ENTRY( stream, stream_getBytes, linux_file ); + SET_VTABLE_ENTRY( stream, stream_getU16, linux_file ); + SET_VTABLE_ENTRY( stream, stream_getU32, linux_file ); + + SET_VTABLE_ENTRY( stream, stream_putU8, linux_file ); + SET_VTABLE_ENTRY( stream, stream_putBytes, linux_file ); + SET_VTABLE_ENTRY( stream, stream_putString, linux_file ); + SET_VTABLE_ENTRY( stream, stream_putU16, linux_file ); + SET_VTABLE_ENTRY( stream, stream_putU32, linux_file ); + + SET_VTABLE_ENTRY( stream, stream_destroy, linux_file ); + SET_VTABLE_ENTRY( stream, stream_open, linux_file ); + SET_VTABLE_ENTRY( stream, stream_close, linux_file ); + + /* PENDING(ehouse) These are part of some subclass, not of stream + overall */ +/* SET_VTABLE_ENTRY( stream, stream_getSize, linux_file ); */ +/* SET_VTABLE_ENTRY( stream, stream_getAddress, linux_file ); */ +/* SET_VTABLE_ENTRY( stream, stream_setAddress, linux_file ); */ +} /* make_vtable */ +#endif + diff --git a/xwords4/linux/filestream.h b/xwords4/linux/filestream.h new file mode 100644 index 000000000..589029aa0 --- /dev/null +++ b/xwords4/linux/filestream.h @@ -0,0 +1,26 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _FILESTREAM_H_ +#define _FILESTREAM_H_ + +XWStreamCtxt* linux_make_fileStream( char* fileName, XP_Bool forWriting ); + + +#endif diff --git a/xwords4/linux/flip.xpm b/xwords4/linux/flip.xpm new file mode 100644 index 000000000..fabecbd13 --- /dev/null +++ b/xwords4/linux/flip.xpm @@ -0,0 +1,14 @@ +/* XPM */ +static char * flip_xpm[] = { +"8 8 3 1", +" c None", +". c #000000", +"+ c #FFFFFF", +"........", +".+......", +".++.....", +".+++....", +".++++...", +".+++++..", +".++++++.", +"........"}; diff --git a/xwords4/linux/gtkask.c b/xwords4/linux/gtkask.c new file mode 100644 index 000000000..52d94d24d --- /dev/null +++ b/xwords4/linux/gtkask.c @@ -0,0 +1,38 @@ +/* -*-mode: C; fill-column: 78; compile-command: "make MEMDEBUG=TRUE"; -*- */ + +/* + * Copyright 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifdef PLATFORM_GTK + +#include + +#include "gtkask.h" + +XP_Bool +gtkask( const gchar *message, GtkButtonsType buttons ) +{ + GtkWidget* dlg = gtk_message_dialog_new( NULL, /* parent */ + GTK_MESSAGE_QUESTION, + GTK_DIALOG_MODAL, + buttons, "%s", message ); + gint response = gtk_dialog_run( GTK_DIALOG(dlg) ); + gtk_widget_destroy( dlg ); + return response == GTK_RESPONSE_OK || response == GTK_RESPONSE_YES; +} /* gtkask */ + +#endif diff --git a/xwords4/linux/gtkask.h b/xwords4/linux/gtkask.h new file mode 100644 index 000000000..dfc2ced75 --- /dev/null +++ b/xwords4/linux/gtkask.h @@ -0,0 +1,33 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifdef PLATFORM_GTK + +#ifndef _GTKASK_H_ +#define _GTKASK_H_ + +#include "gtkmain.h" + +/* Returns true for "yes" or "ok" answer, false otherwise. + */ +XP_Bool gtkask( const gchar *message, GtkButtonsType buttons ); + +#endif +#endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkdraw.c b/xwords4/linux/gtkdraw.c new file mode 100644 index 000000000..95a10b2f7 --- /dev/null +++ b/xwords4/linux/gtkdraw.c @@ -0,0 +1,1139 @@ +/* -*- mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE"; -*- */ +/* + * Copyright 1997-2008 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifdef PLATFORM_GTK + +#include +#include + +#include + +#include "gtkmain.h" +#include "draw.h" +#include "board.h" +#include "linuxmain.h" + +typedef enum { + XP_GTK_JUST_NONE + ,XP_GTK_JUST_CENTER + ,XP_GTK_JUST_TOPLEFT + ,XP_GTK_JUST_BOTTOMRIGHT +} XP_GTK_JUST; + +typedef struct FontPerSize { + unsigned int ht; + PangoFontDescription* fontdesc; + PangoLayout* layout; +} FontPerSize; + +/* static GdkGC* newGCForColor( GdkWindow* window, XP_Color* newC ); */ +static void +gtkInsetRect( XP_Rect* r, short i ) +{ + r->top += i; + r->left += i; + i *= 2; + + r->width -= i; + r->height -= i; +} /* gtkInsetRect */ + +#if 0 +#define DRAW_WHAT(dc) ((dc)->globals->pixmap) +#else +#define DRAW_WHAT(dc) ((dc)->drawing_area->window) +#endif + +#define GTKMIN_W_HT 12 + +static void +gtkFillRect( GtkDrawCtx* dctx, const XP_Rect* rect, const GdkColor* color ) +{ + gdk_gc_set_foreground( dctx->drawGC, color ); + gdk_draw_rectangle( DRAW_WHAT(dctx), dctx->drawGC, + TRUE, + rect->left, rect->top, rect->width, + rect->height ); +} + +static void +gtkEraseRect( GtkDrawCtx* dctx, const XP_Rect* rect ) +{ + gdk_draw_rectangle( DRAW_WHAT(dctx), + dctx->drawing_area->style->white_gc, + TRUE, rect->left, rect->top, + rect->width, rect->height ); +} /* gtkEraseRect */ + +static void +frameRect( GtkDrawCtx* dctx, const XP_Rect* rect ) +{ + gdk_draw_rectangle( DRAW_WHAT(dctx), + dctx->drawGC, FALSE, rect->left, rect->top, + rect->width, rect->height ); +} /* frameRect */ + +#ifdef DRAW_WITH_PRIMITIVES + +static void +gtk_prim_draw_setClip( DrawCtx* p_dctx, XP_Rect* newClip, XP_Rect* oldClip) +{ +} /* gtk_prim_draw_setClip */ + +static void +gtk_prim_draw_frameRect( DrawCtx* p_dctx, XP_Rect* rect ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + frameRect( dctx, rect ); +} /* gtk_prim_draw_frameRect */ + +static void +gtk_prim_draw_invertRect( DrawCtx* p_dctx, XP_Rect* rect ) +{ + /* not sure you can do this on GTK!! */ +} /* gtk_prim_draw_invertRect */ + +static void +gtk_prim_draw_clearRect( DrawCtx* p_dctx, XP_Rect* rect ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + gtkEraseRect( dctx, rect ); +} /* gtk_prim_draw_clearRect */ + +static void +gtk_prim_draw_drawString( DrawCtx* p_dctx, XP_UCHAR* str, + XP_U16 x, XP_U16 y ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + XP_U16 fontHeight = 10; /* FIX ME */ + gdk_draw_string( DRAW_WHAT(dctx), dctx->gdkFont, dctx->drawGC, + x, y + fontHeight, str ); +} /* gtk_prim_draw_drawString */ + +static void +gtk_prim_draw_drawBitmap( DrawCtx* p_dctx, XP_Bitmap bm, + XP_U16 x, XP_U16 y ) +{ +} /* gtk_prim_draw_drawBitmap */ + +static void +gtk_prim_draw_measureText( DrawCtx* p_dctx, XP_UCHAR* str, + XP_U16* widthP, XP_U16* heightP ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + gint len = strlen(str); + gint width = gdk_text_measure( dctx->gdkFont, str, len ); + + *widthP = width; + *heightP = 12; /* ??? :-) */ +} /* gtk_prim_draw_measureText */ + +#endif /* DRAW_WITH_PRIMITIVES */ + +static gint +compForHt( gconstpointer a, + gconstpointer b ) +{ + FontPerSize* fps1 = (FontPerSize*)a; + FontPerSize* fps2 = (FontPerSize*)b; + return fps1->ht - fps2->ht; +} + +static PangoLayout* +layout_for_ht( GtkDrawCtx* dctx, XP_U16 ht ) +{ + PangoLayout* layout = NULL; + + /* Try to find a cached layout. Otherwise create a new one. */ + FontPerSize fps = { .ht = ht }; + GList* gl = g_list_find_custom( dctx->fontsPerSize, &fps, + compForHt ); + if ( NULL != gl ) { + layout = ((FontPerSize*)gl->data)->layout; + } + + if ( NULL == layout ) { + FontPerSize* fps = g_malloc( sizeof(*fps) ); + dctx->fontsPerSize = g_list_insert( dctx->fontsPerSize, + fps, 0 ); + + char font[32]; + snprintf( font, sizeof(font), "helvetica normal %dpx", ht ); + + layout = pango_layout_new( dctx->pangoContext ); + fps->fontdesc = pango_font_description_from_string( font ); + pango_layout_set_font_description( layout, fps->fontdesc ); + fps->layout = layout; + + /* This only happens first time??? */ + pango_layout_set_alignment( layout, PANGO_ALIGN_CENTER ); + fps->ht = ht; + } + + return layout; +} /* layout_for_ht */ + +static void +draw_string_at( GtkDrawCtx* dctx, PangoLayout* layout, + const char* str, XP_U16 fontHt, + const XP_Rect* where, XP_GTK_JUST just, + const GdkColor* frground, const GdkColor* bkgrnd ) +{ + gint xx = where->left; + gint yy = where->top; + + if ( !layout ) { + layout = layout_for_ht( dctx, fontHt ); + } + + pango_layout_set_text( layout, str, strlen(str) ); + + if ( just != XP_GTK_JUST_NONE ) { + int width, height; + pango_layout_get_pixel_size( layout, &width, &height ); + + switch( just ) { + case XP_GTK_JUST_CENTER: + xx += (where->width - width) / 2; + yy += (where->height - height) / 2; + break; + case XP_GTK_JUST_BOTTOMRIGHT: + xx += where->width - width; + yy += where->height - height; + break; + case XP_GTK_JUST_TOPLEFT: + default: + /* nothing to do?? */ + break; + } + } + + gdk_draw_layout_with_colors( DRAW_WHAT(dctx), dctx->drawGC, + xx, yy, layout, + frground, bkgrnd ); +} /* draw_string_at */ + +static void +drawBitmapFromLBS( GtkDrawCtx* dctx, XP_Bitmap bm, const XP_Rect* rect ) +{ + GdkPixmap* pm; + LinuxBMStruct* lbs = (LinuxBMStruct*)bm; + gint x, y; + XP_U8* bp; + XP_U16 i; + XP_S16 nBytes; + XP_U16 nCols, nRows; + + nCols = lbs->nCols; + nRows = lbs->nRows; + bp = (XP_U8*)(lbs + 1); /* point to the bitmap data */ + nBytes = lbs->nBytes; + + pm = gdk_pixmap_new( DRAW_WHAT(dctx), nCols, nRows, -1 ); + + gdk_draw_rectangle( pm, dctx->drawing_area->style->white_gc, TRUE, + 0, 0, nCols, nRows ); + + x = 0; + y = 0; + + while ( nBytes-- ) { + XP_U8 byte = *bp++; + for ( i = 0; i < 8; ++i ) { + XP_Bool draw = ((byte & 0x80) != 0); + if ( draw ) { + gdk_draw_point( pm, dctx->drawing_area->style->black_gc, x, y ); + } + byte <<= 1; + if ( ++x == nCols ) { + x = 0; + if ( ++y == nRows ) { + break; + } + } + } + } + + XP_ASSERT( nBytes == -1 ); /* else we're out of sync */ + + gdk_draw_drawable( DRAW_WHAT(dctx), + dctx->drawGC, + (GdkDrawable*)pm, 0, 0, + rect->left+2, + rect->top+2, + lbs->nCols, + lbs->nRows ); + + g_object_unref( pm ); +} /* drawBitmapFromLBS */ + +static void +freer( gpointer data, gpointer XP_UNUSED(user_data) ) +{ + FontPerSize* fps = (FontPerSize*)data; + pango_font_description_free( fps->fontdesc ); + g_object_unref( fps->layout ); + g_free( fps ); +} + +static void +gtk_draw_destroyCtxt( DrawCtx* p_dctx ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + GtkAllocation* alloc = &dctx->drawing_area->allocation; + + gdk_draw_rectangle( DRAW_WHAT(dctx), + dctx->drawing_area->style->white_gc, + TRUE, + 0, 0, alloc->width, alloc->height ); + + g_list_foreach( dctx->fontsPerSize, freer, NULL ); + g_list_free( dctx->fontsPerSize ); + + g_object_unref( dctx->pangoContext ); + +} /* gtk_draw_destroyCtxt */ + + +static void +gtk_draw_dictChanged( DrawCtx* XP_UNUSED(p_dctx), + const DictionaryCtxt* XP_UNUSED(dict) ) +{ +} + +static XP_Bool +gtk_draw_boardBegin( DrawCtx* p_dctx, const XP_Rect* rect, + DrawFocusState XP_UNUSED(dfs) ) +{ + GdkRectangle gdkrect; + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + + gdk_gc_set_foreground( dctx->drawGC, &dctx->black ); + + gdkrect = *(GdkRectangle*)rect; + ++gdkrect.width; + ++gdkrect.height; +/* gdk_gc_set_clip_rectangle( dctx->drawGC, &gdkrect ); */ + + return XP_TRUE; +} /* draw_finish */ + +static void +gtk_draw_objFinished( DrawCtx* XP_UNUSED(p_dctx), + BoardObjectType XP_UNUSED(typ), + const XP_Rect* XP_UNUSED(rect), + DrawFocusState XP_UNUSED(dfs) ) +{ +} /* draw_finished */ + + +static XP_Bool +gtk_draw_vertScrollBoard( DrawCtx* p_dctx, XP_Rect* rect, + XP_S16 dist, DrawFocusState XP_UNUSED(dfs) ) +{ + /* Turn this on to mimic what palm does, but need to figure out some gtk + analog to copybits for it to actually work. */ +#if 1 + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + XP_Bool down = dist <= 0; + gint ysrc, ydest; + + if ( down ) { + ysrc = rect->top; + dist = -dist; /* make it positive */ + ydest = ysrc + dist; + } else { + ydest = rect->top; + ysrc = ydest + dist; + } + + gdk_draw_drawable( DRAW_WHAT(dctx), + dctx->drawGC, + DRAW_WHAT(dctx), + rect->left, + ysrc, + rect->left, + ydest, + rect->width, + rect->height - dist ); + + if ( !down ) { + rect->top += rect->height - dist; + } + rect->height = dist; +#endif + return XP_TRUE; +} + + +static void +drawHintBorders( GtkDrawCtx* dctx, const XP_Rect* rect, HintAtts hintAtts) +{ + if ( hintAtts != HINT_BORDER_NONE && hintAtts != HINT_BORDER_CENTER ) { + XP_Rect lrect = *rect; + gtkInsetRect( &lrect, 1 ); + + gdk_gc_set_foreground( dctx->drawGC, &dctx->black ); + + if ( (hintAtts & HINT_BORDER_LEFT) != 0 ) { + gdk_draw_rectangle( DRAW_WHAT(dctx), + dctx->drawGC, + FALSE, lrect.left, lrect.top, + 0, lrect.height); + } + if ( (hintAtts & HINT_BORDER_TOP) != 0 ) { + gdk_draw_rectangle( DRAW_WHAT(dctx), + dctx->drawGC, + FALSE, lrect.left, lrect.top, + lrect.width, 0/*rectInset.height*/); + } + if ( (hintAtts & HINT_BORDER_RIGHT) != 0 ) { + gdk_draw_rectangle( DRAW_WHAT(dctx), + dctx->drawGC, + FALSE, lrect.left+lrect.width, + lrect.top, + 0, lrect.height); + } + if ( (hintAtts & HINT_BORDER_BOTTOM) != 0 ) { + gdk_draw_rectangle( DRAW_WHAT(dctx), + dctx->drawGC, + FALSE, lrect.left, + lrect.top+lrect.height, + lrect.width, 0 ); + } + } +} + +static XP_Bool +gtk_draw_drawCell( DrawCtx* p_dctx, const XP_Rect* rect, const XP_UCHAR* letter, + XP_Bitmap bitmap, Tile XP_UNUSED(tile), XP_S16 owner, + XWBonusType bonus, HintAtts hintAtts, CellFlags flags ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + XP_Rect rectInset = *rect; + XP_Bool showGrid = dctx->globals->gridOn; + XP_Bool highlight = (flags & CELL_HIGHLIGHT) != 0; + GdkColor* cursor = + ((flags & CELL_ISCURSOR) != 0) ? &dctx->cursor : NULL; + + gtkEraseRect( dctx, rect ); + + gtkInsetRect( &rectInset, 1 ); + + if ( showGrid ) { + gdk_gc_set_foreground( dctx->drawGC, &dctx->black ); + gdk_draw_rectangle( DRAW_WHAT(dctx), + dctx->drawGC, + FALSE, + rect->left, rect->top, rect->width, + rect->height ); + } + + /* We draw just an empty, potentially colored, square IFF there's nothing + in the cell or if CELL_DRAGSRC is set */ + if ( (flags & (CELL_DRAGSRC|CELL_ISEMPTY)) != 0 ) { + if ( !!cursor || bonus != BONUS_NONE ) { + GdkColor* foreground; + if ( !!cursor ) { + foreground = cursor; + } else if ( bonus != BONUS_NONE ) { + foreground = &dctx->bonusColors[bonus-1]; + } else { + foreground = NULL; + } + if ( !!foreground ) { + gdk_gc_set_foreground( dctx->drawGC, foreground ); + gdk_draw_rectangle( DRAW_WHAT(dctx), dctx->drawGC, TRUE, + rectInset.left, rectInset.top, + rectInset.width+1, rectInset.height+1 ); + } + } + if ( (flags & CELL_ISSTAR) != 0 ) { + draw_string_at( dctx, NULL, "*", rect->height, rect, + XP_GTK_JUST_CENTER, &dctx->black, NULL ); + } + } else if ( !!letter ) { + GdkColor* foreground; + if ( cursor ) { + gdk_gc_set_foreground( dctx->drawGC, cursor ); + } else if ( !highlight ) { + gdk_gc_set_foreground( dctx->drawGC, &dctx->tileBack ); + } + gdk_draw_rectangle( DRAW_WHAT(dctx), dctx->drawGC, TRUE, + rectInset.left, rectInset.top, + rectInset.width+1, rectInset.height+1 ); + + foreground = highlight? &dctx->white : &dctx->playerColors[owner]; + draw_string_at( dctx, NULL, letter, rectInset.height-2, &rectInset, + XP_GTK_JUST_CENTER, foreground, cursor ); + + if ( (flags & CELL_ISBLANK) != 0 ) { + gdk_draw_arc( DRAW_WHAT(dctx), dctx->drawGC, + 0, /* filled */ + rect->left, /* x */ + rect->top, /* y */ + rect->width,/*width, */ + rect->height,/*width, */ + 0, 360*64 ); + } + } else if ( !!bitmap ) { + drawBitmapFromLBS( dctx, bitmap, rect ); + } + + drawHintBorders( dctx, rect, hintAtts ); + + return XP_TRUE; +} /* gtk_draw_drawCell */ + +static void +gtk_draw_invertCell( DrawCtx* XP_UNUSED(p_dctx), + const XP_Rect* XP_UNUSED(rect) ) +{ +/* GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; */ +/* (void)gtk_draw_drawMiniWindow( p_dctx, "f", rect); */ + +/* GdkGCValues values; */ + +/* gdk_gc_get_values( dctx->drawGC, &values ); */ + +/* gdk_gc_set_function( dctx->drawGC, GDK_INVERT ); */ + +/* gdk_gc_set_clip_rectangle( dctx->drawGC, (GdkRectangle*)rect ); */ +/* gdk_draw_rectangle( DRAW_WHAT(dctx), dctx->drawGC, */ +/* TRUE, rect->left, rect->top, */ +/* rect->width, rect->height ); */ + +/* gdk_gc_set_function( dctx->drawGC, values.function ); */ +} /* gtk_draw_invertCell */ + +static XP_Bool +gtk_draw_trayBegin( DrawCtx* p_dctx, const XP_Rect* XP_UNUSED(rect), + XP_U16 owner, DrawFocusState dfs ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + dctx->trayOwner = owner; + dctx->topFocus = dfs == DFS_TOP; + return XP_TRUE; +} /* gtk_draw_trayBegin */ + +static void +gtkDrawTileImpl( DrawCtx* p_dctx, const XP_Rect* rect, const XP_UCHAR* textP, + XP_Bitmap bitmap, XP_S16 val, CellFlags flags, + XP_Bool clearBack ) +{ + XP_UCHAR numbuf[3]; + gint len; + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + XP_Rect insetR = *rect; + XP_Bool isCursor = (flags & CELL_ISCURSOR) != 0; + + if ( clearBack ) { + gtkEraseRect( dctx, &insetR ); + } + + if ( isCursor || (val >= 0) ) { + GdkColor* foreground = &dctx->playerColors[dctx->trayOwner]; + XP_Rect formatRect = insetR; + + gtkInsetRect( &insetR, 1 ); + + if ( clearBack ) { + gtkFillRect( dctx, &insetR, isCursor? &dctx->cursor:&dctx->tileBack ); + } + + if ( val >= 0 ) { + formatRect.left += 3; + formatRect.width -= 6; + + if ( !!textP ) { + if ( *textP != LETTER_NONE ) { /* blank */ + draw_string_at( dctx, NULL, textP, formatRect.height>>1, + &formatRect, XP_GTK_JUST_TOPLEFT, + foreground, NULL ); + + } + } else if ( !!bitmap ) { + drawBitmapFromLBS( dctx, bitmap, &insetR ); + } + + sprintf( numbuf, "%d", val ); + len = strlen( numbuf ); + + draw_string_at( dctx, NULL, numbuf, formatRect.height>>2, + &formatRect, XP_GTK_JUST_BOTTOMRIGHT, + foreground, NULL ); + + /* frame the tile */ + gdk_gc_set_foreground( dctx->drawGC, &dctx->black ); + gdk_draw_rectangle( DRAW_WHAT(dctx), + dctx->drawGC, + FALSE, + insetR.left, insetR.top, insetR.width, + insetR.height ); + + if ( (flags & CELL_HIGHLIGHT) != 0 ) { + gtkInsetRect( &insetR, 1 ); + gdk_draw_rectangle( DRAW_WHAT(dctx), + dctx->drawGC, + FALSE, insetR.left, insetR.top, + insetR.width, insetR.height); + } + } + } +} /* gtkDrawTileImpl */ + +static void +gtk_draw_drawTile( DrawCtx* p_dctx, const XP_Rect* rect, const XP_UCHAR* textP, + XP_Bitmap bitmap, XP_S16 val, CellFlags flags ) +{ + gtkDrawTileImpl( p_dctx, rect, textP, bitmap, val, flags, XP_TRUE ); +} + +#ifdef POINTER_SUPPORT +static void +gtk_draw_drawTileMidDrag( DrawCtx* p_dctx, const XP_Rect* rect, + const XP_UCHAR* textP, XP_Bitmap bitmap, + XP_S16 val, XP_U16 owner, CellFlags flags ) +{ + gtk_draw_trayBegin( p_dctx, rect, owner, DFS_NONE ); + gtkDrawTileImpl( p_dctx, rect, textP, bitmap, val, + flags | CELL_HIGHLIGHT, + XP_FALSE ); +} +#endif + +static void +gtk_draw_drawTileBack( DrawCtx* p_dctx, const XP_Rect* rect, + CellFlags flags ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + XP_Bool hasCursor = (flags & CELL_ISCURSOR) != 0; + XP_Rect r = *rect; + + gtkInsetRect( &r, 1 ); + + gtkFillRect( dctx, &r, &dctx->playerColors[dctx->trayOwner] ); + + gtkInsetRect( &r, 1 ); + gtkFillRect( dctx, &r, hasCursor? &dctx->cursor : &dctx->tileBack ); + + draw_string_at( dctx, NULL, "?", r.height, + &r, XP_GTK_JUST_CENTER, + &dctx->playerColors[dctx->trayOwner], NULL ); + +} /* gtk_draw_drawTileBack */ + +static void +gtk_draw_drawTrayDivider( DrawCtx* p_dctx, const XP_Rect* rect, + CellFlags flags ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + XP_Rect r = *rect; + XP_Bool selected = 0 != (flags & CELL_HIGHLIGHT); + XP_Bool isCursor = 0 != (flags & CELL_ISCURSOR); + + gtkEraseRect( dctx, &r ); + + gtkFillRect( dctx, &r, isCursor? &dctx->cursor:&dctx->white ); + + r.left += 2; + r.width -= 4; + if ( selected ) { + --r.height; + } + + gdk_gc_set_foreground( dctx->drawGC, &dctx->black ); + gdk_draw_rectangle( DRAW_WHAT(dctx), + dctx->drawGC, + !selected, + r.left, r.top, r.width, r.height); + +} /* gtk_draw_drawTrayDivider */ + +static void +gtk_draw_clearRect( DrawCtx* p_dctx, const XP_Rect* rectP ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + XP_Rect rect = *rectP; + + ++rect.width; + ++rect.top; + + gtkEraseRect( dctx, &rect ); + +} /* gtk_draw_clearRect */ + +static void +gtk_draw_drawBoardArrow( DrawCtx* p_dctx, const XP_Rect* rectP, + XWBonusType XP_UNUSED(cursorBonus), XP_Bool vertical, + HintAtts hintAtts, CellFlags XP_UNUSED(flags) ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + const char* curs = vertical? "|":"-"; + + /* font needs to be small enough that "|" doesn't overwrite cell below */ + draw_string_at( dctx, NULL, curs, (rectP->height*2)/3, + rectP, XP_GTK_JUST_CENTER, + &dctx->black, NULL ); + drawHintBorders( dctx, rectP, hintAtts ); +} /* gtk_draw_drawBoardCursor */ + +static void +gtk_draw_scoreBegin( DrawCtx* p_dctx, const XP_Rect* rect, + XP_U16 XP_UNUSED(numPlayers), + DrawFocusState dfs ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + +/* gdk_gc_set_clip_rectangle( dctx->drawGC, (GdkRectangle*)rect ); */ + gtkEraseRect( dctx, rect ); + dctx->topFocus = dfs == DFS_TOP; +} /* gtk_draw_scoreBegin */ + +static PangoLayout* +getLayoutToFitRect( GtkDrawCtx* dctx, const char* str, const XP_Rect* rect ) +{ + PangoLayout* layout; + float ratio, ratioH; + int width, height; + XP_U16 len = strlen(str); + + /* First measure it using any font at all */ + layout = layout_for_ht( dctx, 24 ); + pango_layout_set_text( layout, str, len ); + pango_layout_get_pixel_size( layout, &width, &height ); + + /* Figure the ratio of is to should-be. The smaller of these is the one + we must use. */ + ratio = (float)rect->width / (float)width; + ratioH = (float)rect->height / (float)height; + if ( ratioH < ratio ) { + ratio = ratioH; + } + height = 24.0 * ratio; + + layout = layout_for_ht( dctx, height ); + pango_layout_set_text( layout, str, len ); + return layout; +} /* getLayoutToFitRect */ + +static void +gtkDrawDrawRemText( DrawCtx* p_dctx, const XP_Rect* rect, XP_S16 nTilesLeft, + XP_U16* widthP, XP_U16* heightP, XP_Bool focussed ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + char buf[10]; + PangoLayout* layout; + + snprintf( buf, sizeof(buf), "rem:%d", nTilesLeft ); + layout = getLayoutToFitRect( dctx, buf, rect ); + + if ( !!widthP ) { + int width, height; + pango_layout_get_pixel_size( layout, &width, &height ); + if ( width > rect->width ) { + width = rect->width; + } + if ( height > rect->height ) { + height = rect->height; + } + *widthP = width; + *heightP = height; + } else { + const GdkColor* cursor = NULL; + if ( focussed ) { + cursor = &dctx->cursor; + gtkFillRect( dctx, rect, cursor ); + } + draw_string_at( dctx, layout, buf, rect->height, + rect, XP_GTK_JUST_TOPLEFT, + &dctx->black, cursor ); + } +} /* gtkDrawDrawRemText */ + +static void +gtk_draw_measureRemText( DrawCtx* p_dctx, const XP_Rect* rect, XP_S16 nTilesLeft, + XP_U16* width, XP_U16* height ) +{ + if ( nTilesLeft <= 0 ) { + *width = *height = 0; + } else { + gtkDrawDrawRemText( p_dctx, rect, nTilesLeft, width, height, XP_FALSE ); + } +} /* gtk_draw_measureRemText */ + +static void +gtk_draw_drawRemText( DrawCtx* p_dctx, const XP_Rect* rInner, + const XP_Rect* XP_UNUSED(rOuter), XP_S16 nTilesLeft, + XP_Bool focussed ) +{ + gtkDrawDrawRemText( p_dctx, rInner, nTilesLeft, NULL, NULL, focussed ); +} /* gtk_draw_drawRemText */ + +static void +formatScoreText( char* buf, XP_U16 bufLen, const DrawScoreInfo* dsi ) +{ + XP_S16 score = dsi->totalScore; + XP_U16 nTilesLeft = dsi->nTilesLeft; + XP_Bool isTurn = dsi->isTurn; + int used; + char* borders = ""; + + if ( isTurn ) { + borders = "*"; + } + + used = snprintf( buf, bufLen, "%s%.3d", borders, score ); + if ( (nTilesLeft < MAX_TRAY_TILES) && (nTilesLeft > 0) ) { + char nbuf[10]; + sprintf( nbuf, ":%d", nTilesLeft ); + (void)strcat( buf, nbuf ); + } + snprintf( buf+used, bufLen-used, "%s", borders ); +} /* formatScoreText */ + +static void +gtk_draw_measureScoreText( DrawCtx* p_dctx, const XP_Rect* bounds, + const DrawScoreInfo* dsi, + XP_U16* widthP, XP_U16* heightP ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + char buf[20]; + PangoLayout* layout; + int height, width; + + formatScoreText( buf, sizeof(buf), dsi ); + layout = getLayoutToFitRect( dctx, buf, bounds ); + pango_layout_get_pixel_size( layout, &width, &height ); + + *widthP = width; + *heightP = height; +} /* gtk_draw_measureScoreText */ + +static void +gtk_draw_score_drawPlayer( DrawCtx* p_dctx, const XP_Rect* rInner, + const XP_Rect* rOuter, const DrawScoreInfo* dsi ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + char scoreBuf[20]; + XP_Bool hasCursor = (dsi->flags & CELL_ISCURSOR) != 0; + GdkColor* cursor = NULL; + + formatScoreText( scoreBuf, sizeof(scoreBuf), dsi ); + + if ( hasCursor ) { + cursor = &dctx->cursor; + gtkFillRect( dctx, rOuter, cursor ); + } + + gdk_gc_set_foreground( dctx->drawGC, &dctx->playerColors[dsi->playerNum] ); + + if ( dsi->selected ) { + XP_Rect selRect = *rOuter; + XP_S16 diff = selRect.width - rInner->width; + if ( diff > 0 ) { + selRect.width -= diff>>1; + selRect.left += diff>>2; + } + gdk_draw_rectangle( DRAW_WHAT(dctx), dctx->drawGC, + TRUE, selRect.left, selRect.top, + selRect.width, selRect.height ); + gtkEraseRect( dctx, rInner ); + } + + draw_string_at( dctx, NULL, scoreBuf, rInner->height - 1, + rInner, XP_GTK_JUST_CENTER, + &dctx->playerColors[dsi->playerNum], cursor ); + +} /* gtk_draw_score_drawPlayer */ + +static void +gtk_draw_score_pendingScore( DrawCtx* p_dctx, const XP_Rect* rect, + XP_S16 score, XP_U16 XP_UNUSED(playerNum), + CellFlags flags ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + char buf[5]; + XP_U16 ht; + XP_Rect localR; + GdkColor* cursor = ((flags & CELL_ISCURSOR) != 0) + ? &dctx->cursor : NULL; + + if ( score >= 0 ) { + sprintf( buf, "%.3d", score ); + } else { + strcpy( buf, "???" ); + } + +/* gdk_gc_set_clip_rectangle( dctx->drawGC, (GdkRectangle*)rect ); */ + + localR = *rect; + gtkInsetRect( &localR, 1 ); + + if ( !!cursor ) { + gtkFillRect( dctx, &localR, cursor ); + } else { + gtkEraseRect( dctx, &localR ); + } + + ht = localR.height >> 2; + draw_string_at( dctx, NULL, "Pts:", ht, + &localR, XP_GTK_JUST_TOPLEFT, + &dctx->black, cursor ); + draw_string_at( dctx, NULL, buf, ht, + &localR, XP_GTK_JUST_BOTTOMRIGHT, + &dctx->black, cursor ); + +} /* gtk_draw_score_pendingScore */ + +static void +gtkFormatTimerText( XP_UCHAR* buf, XP_S16 secondsLeft ) +{ + XP_U16 minutes, seconds; + + if ( secondsLeft < 0 ) { + *buf++ = '-'; + secondsLeft *= -1; + } + + minutes = secondsLeft / 60; + seconds = secondsLeft % 60; + sprintf( buf, "% 1d:%02d", minutes, seconds ); +} /* gtkFormatTimerText */ + +static void +gtk_draw_drawTimer( DrawCtx* p_dctx, const XP_Rect* rInner, + const XP_Rect* XP_UNUSED(rOuter), + XP_U16 XP_UNUSED(player), XP_S16 secondsLeft ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + XP_UCHAR buf[10]; + + gtkFormatTimerText( buf, secondsLeft ); + +/* gdk_gc_set_clip_rectangle( dctx->drawGC, (GdkRectangle*)rInner ); */ + gtkEraseRect( dctx, rInner ); + draw_string_at( dctx, NULL, buf, rInner->height-1, + rInner, XP_GTK_JUST_CENTER, + &dctx->black, NULL ); +} /* gtk_draw_drawTimer */ + +#define MINI_LINE_HT 12 +#define MINI_V_PADDING 6 +#define MINI_H_PADDING 8 + +static const XP_UCHAR* +gtk_draw_getMiniWText( DrawCtx* XP_UNUSED(p_dctx), XWMiniTextType textHint ) +{ +/* GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; */ + XP_UCHAR* str; + + switch( textHint ) { + case BONUS_DOUBLE_LETTER: + str = "Double letter"; break; + case BONUS_DOUBLE_WORD: + str = "Double word"; break; + case BONUS_TRIPLE_LETTER: + str = "Triple letter"; break; + case BONUS_TRIPLE_WORD: + str = "Triple word"; break; + case INTRADE_MW_TEXT: + str = "Trading tiles;\nclick D when done"; break; + default: + XP_ASSERT( XP_FALSE ); + } + return str; +} /* gtk_draw_getMiniWText */ + +static void +gtk_draw_measureMiniWText( DrawCtx* p_dctx, const XP_UCHAR* str, + XP_U16* widthP, XP_U16* heightP ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + int height, width; + + PangoLayout* layout = layout_for_ht( dctx, GTKMIN_W_HT ); + pango_layout_set_text( layout, str, strlen(str) ); + pango_layout_get_pixel_size( layout, &width, &height ); + *heightP = height; + *widthP = width + 6; +} /* gtk_draw_measureMiniWText */ + +static void +gtk_draw_drawMiniWindow( DrawCtx* p_dctx, const XP_UCHAR* text, + const XP_Rect* rect, void** XP_UNUSED(closureP) ) +{ + GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx; + XP_Rect localR = *rect; + + gdk_gc_set_foreground( dctx->drawGC, &dctx->black ); +/* gdk_gc_set_clip_rectangle( dctx->drawGC, (GdkRectangle*)&localR ); */ + + /* play some skanky games to get the shadow drawn under and to the + right... */ + gtkEraseRect( dctx, &localR ); + + gtkInsetRect( &localR, 1 ); + --localR.width; + --localR.height; + frameRect( dctx, &localR ); + + --localR.top; + --localR.left; + gtkEraseRect( dctx, &localR ); + frameRect( dctx, &localR ); + + draw_string_at( dctx, NULL, text, GTKMIN_W_HT, + &localR, XP_GTK_JUST_CENTER, + &dctx->black, NULL ); +} /* gtk_draw_drawMiniWindow */ + +#define SET_GDK_COLOR( c, r, g, b ) { \ + c.red = (r); \ + c.green = (g); \ + c.blue = (b); \ +} +static void +draw_doNothing( DrawCtx* XP_UNUSED(dctx), ... ) +{ +} /* draw_doNothing */ + +static void +allocAndSet( GdkColormap* map, GdkColor* color, unsigned short red, + unsigned short green, unsigned short blue ) + +{ + gboolean success; + + color->red = red; + color->green = green; + color->blue = blue; + + success = gdk_colormap_alloc_color( map, + color, + TRUE, /* writeable */ + TRUE ); /* best-match */ + XP_ASSERT( success ); +} /* allocAndSet */ + +DrawCtx* +gtkDrawCtxtMake( GtkWidget* drawing_area, GtkAppGlobals* globals ) +{ + GtkDrawCtx* dctx = g_malloc0( sizeof(GtkDrawCtx) ); + GdkColormap* map; + + short i; + + dctx->vtable = g_malloc( sizeof(*(((GtkDrawCtx*)dctx)->vtable)) ); + + for ( i = 0; i < sizeof(*dctx->vtable)/4; ++i ) { + ((void**)(dctx->vtable))[i] = draw_doNothing; + } + + SET_VTABLE_ENTRY( dctx->vtable, draw_clearRect, gtk ); + +#ifdef DRAW_WITH_PRIMITIVES + SET_VTABLE_ENTRY( dctx->vtable, draw_setClip, gtk_prim ); + SET_VTABLE_ENTRY( dctx->vtable, draw_frameRect, gtk_prim ); + SET_VTABLE_ENTRY( dctx->vtable, draw_invertRect, gtk_prim ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawString, gtk_prim ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawBitmap, gtk_prim ); + SET_VTABLE_ENTRY( dctx->vtable, draw_measureText, gtk_prim ); + + InitDrawDefaults( dctx->vtable ); +#else + + SET_VTABLE_ENTRY( dctx->vtable, draw_boardBegin, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawCell, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_invertCell, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_objFinished, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_vertScrollBoard, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_trayBegin, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTile, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTileBack, gtk ); +#ifdef POINTER_SUPPORT + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTileMidDrag, gtk ); +#endif + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTrayDivider, gtk ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_drawBoardArrow, gtk ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_scoreBegin, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_measureRemText, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawRemText, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_measureScoreText, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_score_drawPlayer, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_score_pendingScore, gtk ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTimer, gtk ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_getMiniWText, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_measureMiniWText, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawMiniWindow, gtk ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_destroyCtxt, gtk ); + SET_VTABLE_ENTRY( dctx->vtable, draw_dictChanged, gtk ); +#endif + + dctx->pangoContext = gtk_widget_get_pango_context( drawing_area ); + dctx->drawing_area = drawing_area; + dctx->globals = globals; + + map = gdk_colormap_get_system(); + + allocAndSet( map, &dctx->black, 0x0000, 0x0000, 0x0000 ); + allocAndSet( map, &dctx->white, 0xFFFF, 0xFFFF, 0xFFFF ); + + { + GdkWindow *window = NULL; + if ( GTK_WIDGET_FLAGS(GTK_WIDGET(drawing_area)) & GTK_NO_WINDOW ) { + /* XXX I'm not sure about this function because I never used it. + * (the name seems to indicate what you want though). + */ + window = gtk_widget_get_parent_window( GTK_WIDGET(drawing_area) ); + } else { + window = GTK_WIDGET(drawing_area)->window; + } + dctx->drawGC = gdk_gc_new( window ); + } + + map = gdk_colormap_get_system(); + + allocAndSet( map, &dctx->black, 0x0000, 0x0000, 0x0000 ); + allocAndSet( map, &dctx->white, 0xFFFF, 0xFFFF, 0xFFFF ); + + allocAndSet( map, &dctx->bonusColors[0], 0xFFFF, 0xAFFF, 0xAFFF ); + allocAndSet( map, &dctx->bonusColors[1], 0xAFFF, 0xFFFF, 0xAFFF ); + allocAndSet( map, &dctx->bonusColors[2], 0xAFFF, 0xAFFF, 0xFFFF ); + allocAndSet( map, &dctx->bonusColors[3], 0xFFFF, 0xAFFF, 0xFFFF ); + + allocAndSet( map, &dctx->playerColors[0], 0x0000, 0x0000, 0xAFFF ); + allocAndSet( map, &dctx->playerColors[1], 0xAFFF, 0x0000, 0x0000 ); + allocAndSet( map, &dctx->playerColors[2], 0x0000, 0xAFFF, 0x0000 ); + allocAndSet( map, &dctx->playerColors[3], 0xAFFF, 0x0000, 0xAFFF ); + + allocAndSet( map, &dctx->tileBack, 0xFFFF, 0xFFFF, 0x9999 ); + allocAndSet( map, &dctx->cursor, 253<<8, 12<<8, 222<<8 ); + allocAndSet( map, &dctx->red, 0xFFFF, 0x0000, 0x0000 ); + + return (DrawCtx*)dctx; +} /* gtkDrawCtxtMake */ + +#endif /* PLATFORM_GTK */ + diff --git a/xwords4/linux/gtkdraw.h b/xwords4/linux/gtkdraw.h new file mode 100644 index 000000000..08499adcf --- /dev/null +++ b/xwords4/linux/gtkdraw.h @@ -0,0 +1,27 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make debug"; -*- */ +/* + * Copyright 1997 - 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _GTKDRAW_H_ +#define _GTKDRAW_H_ + +#include "draw.h" + +DrawCtx* gtkDrawCtxtMake( GtkWidget *widget, GtkAppGlobals* globals ); + +#endif diff --git a/xwords4/linux/gtkletterask.c b/xwords4/linux/gtkletterask.c new file mode 100644 index 000000000..f635ee842 --- /dev/null +++ b/xwords4/linux/gtkletterask.c @@ -0,0 +1,127 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifdef PLATFORM_GTK + +#include + +#include "gtkask.h" + +static void +button_event( GtkWidget* XP_UNUSED(widget), gpointer closure ) +{ + XP_Bool* whichSet = (XP_Bool*)closure; + *whichSet = 1; + + gtk_main_quit(); +} /* button_event */ + +#ifdef FEATURE_TRAY_EDIT +static void +abort_button_event( GtkWidget* XP_UNUSED(widget), gpointer XP_UNUSED(closure) ) +{ + gtk_main_quit(); +} /* abort_button_event */ +#endif + +#define BUTTONS_PER_ROW 13 + +XP_S16 +gtkletterask( XP_Bool forBlank, XP_UCHAR* name, + XP_U16 nTiles, const XP_UCHAR4* texts ) +{ + GtkWidget* dialog; + GtkWidget* label; + XP_Bool results[MAX_UNIQUE_TILES]; + GtkWidget* vbox; + GtkWidget* hbox = NULL; + char* txt; + XP_S16 i; + GtkWidget* button; + XP_UCHAR buf[64]; + + XP_MEMSET( results, 0, sizeof(results) ); + + vbox = gtk_vbox_new( FALSE, 0 ); + + for ( i = 0; i < nTiles; ++i ) { + + if ( i % BUTTONS_PER_ROW == 0 ) { + hbox = gtk_hbox_new( FALSE, 0 ); + } + button = gtk_button_new_with_label( texts[i] ); + + gtk_box_pack_start( GTK_BOX(hbox), button, FALSE, TRUE, 0 ); + g_signal_connect( GTK_OBJECT(button), "clicked", + G_CALLBACK(button_event), &results[i] ); + gtk_widget_show( button ); + + if ( i+1 == nTiles || (i % BUTTONS_PER_ROW == 0) ) { + gtk_widget_show( hbox ); + gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); + } + } + +#ifdef FEATURE_TRAY_EDIT + button = gtk_button_new_with_label( "Just pick em!" ); + hbox = gtk_hbox_new( FALSE, 0 ); + g_signal_connect( GTK_OBJECT(button), "clicked", + G_CALLBACK(abort_button_event), NULL ); + gtk_box_pack_start( GTK_BOX(hbox), button, FALSE, TRUE, 0 ); + gtk_widget_show( button ); + gtk_widget_show( hbox ); + gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); +#endif + + gtk_widget_show( vbox ); + + /* Create the widgets */ + dialog = gtk_dialog_new(); + gtk_window_set_modal( GTK_WINDOW( dialog ), TRUE ); + + if ( forBlank ) { + txt = "Choose a letter for your blank."; + } else { + char* fmt = "Choose a tile for %s."; + XP_SNPRINTF( buf, sizeof(buf), fmt, name ); + txt = buf; + } + label = gtk_label_new( txt ); + + gtk_container_add (GTK_CONTAINER (GTK_DIALOG(dialog)->vbox), + label); + gtk_container_add( GTK_CONTAINER( GTK_DIALOG(dialog)->action_area), vbox); + gtk_widget_show_all( dialog ); + + gtk_main(); + + gtk_widget_destroy( dialog ); + + for ( i = 0; i < nTiles; ++i ) { + if ( results[i] ) { + break; + } + } + if ( i == nTiles ) { + i = -1; + } + + return i; +} /* gtkletterask */ + +#endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkletterask.h b/xwords4/linux/gtkletterask.h new file mode 100644 index 000000000..5fb118d37 --- /dev/null +++ b/xwords4/linux/gtkletterask.h @@ -0,0 +1,33 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifdef PLATFORM_GTK + +#ifndef _GTKLETTERASK_H_ +#define _GTKLETTERASK_H_ + +#include "gtkmain.h" + +XP_S16 gtkletterask( XP_Bool forBlank, XP_UCHAR* name, XP_U16 nTiles, + const XP_UCHAR4* texts ); + + +#endif /* _GTKLETTERASK_H_ */ +#endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c new file mode 100644 index 000000000..a996fa519 --- /dev/null +++ b/xwords4/linux/gtkmain.c @@ -0,0 +1,1945 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE"; -*- */ +/* + * Copyright 2000-2008 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef PLATFORM_GTK + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifndef CLIENT_ONLY +/* # include */ +#endif +#include +#include + +#include "main.h" +#include "linuxmain.h" +#include "linuxutl.h" +#include "linuxbt.h" +#include "linuxudp.h" +/* #include "gtkmain.h" */ + +#include "draw.h" +#include "game.h" +#include "gtkask.h" +#include "gtknewgame.h" +#include "gtkletterask.h" +#include "gtkpasswdask.h" +#include "gtkntilesask.h" +/* #include "undo.h" */ +#include "gtkdraw.h" +#include "memstream.h" +#include "filestream.h" + +/* static guint gtkSetupClientSocket( GtkAppGlobals* globals, int sock ); */ +#ifndef XWFEATURE_STANDALONE_ONLY +static void sendOnClose( XWStreamCtxt* stream, void* closure ); +#endif +static void setCtrlsForTray( GtkAppGlobals* globals ); +static void printFinalScores( GtkAppGlobals* globals ); + +#define GTK_TRAY_HT_ROWS 3 + +#if 0 +static XWStreamCtxt* +lookupClientStream( GtkAppGlobals* globals, int sock ) +{ + short i; + for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { + ClientStreamRec* rec = &globals->clientRecs[i]; + if ( rec->sock == sock ) { + XP_ASSERT( rec->stream != NULL ); + return rec->stream; + } + } + XP_ASSERT( i < MAX_NUM_PLAYERS ); + return NULL; +} /* lookupClientStream */ + +static void +rememberClient( GtkAppGlobals* globals, guint key, int sock, + XWStreamCtxt* stream ) +{ + short i; + for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { + ClientStreamRec* rec = &globals->clientRecs[i]; + if ( rec->stream == NULL ) { + XP_ASSERT( stream != NULL ); + rec->stream = stream; + rec->key = key; + rec->sock = sock; + break; + } + } + XP_ASSERT( i < MAX_NUM_PLAYERS ); +} /* rememberClient */ +#endif + +static void +gtkSetAltState( GtkAppGlobals* globals, guint state ) +{ + globals->altKeyDown + = (state & (GDK_MOD1_MASK|GDK_SHIFT_MASK|GDK_CONTROL_MASK)) != 0; +} + +static gint +button_press_event( GtkWidget* XP_UNUSED(widget), GdkEventButton *event, + GtkAppGlobals* globals ) +{ + XP_Bool redraw, handled; + + gtkSetAltState( globals, event->state ); + + if ( !globals->mouseDown ) { + globals->mouseDown = XP_TRUE; + redraw = board_handlePenDown( globals->cGlobals.game.board, + event->x, event->y, &handled ); + if ( redraw ) { + board_draw( globals->cGlobals.game.board ); + } + } + return 1; +} /* button_press_event */ + +static gint +motion_notify_event( GtkWidget* XP_UNUSED(widget), GdkEventMotion *event, + GtkAppGlobals* globals ) +{ + XP_Bool handled; + + gtkSetAltState( globals, event->state ); + + if ( globals->mouseDown ) { + handled = board_handlePenMove( globals->cGlobals.game.board, event->x, + event->y ); + if ( handled ) { + board_draw( globals->cGlobals.game.board ); + } + } else { + handled = XP_FALSE; + } + + return handled; +} /* motion_notify_event */ + +static gint +button_release_event( GtkWidget* XP_UNUSED(widget), GdkEventMotion *event, + GtkAppGlobals* globals ) +{ + XP_Bool redraw; + + gtkSetAltState( globals, event->state ); + + if ( globals->mouseDown ) { + redraw = board_handlePenUp( globals->cGlobals.game.board, + event->x, + event->y ); + if ( redraw ) { + board_draw( globals->cGlobals.game.board ); + } + globals->mouseDown = XP_FALSE; + } + return 1; +} /* button_release_event */ + +#ifdef KEY_SUPPORT +static XP_Key +evtToXPKey( GdkEventKey* event, XP_Bool* movesCursorP ) +{ + XP_Key xpkey = XP_KEY_NONE; + XP_Bool movesCursor = XP_FALSE; + guint keyval = event->keyval; + + switch( keyval ) { +#ifdef KEYBOARD_NAV + case GDK_Return: + xpkey = XP_RETURN_KEY; + break; + case GDK_space: + xpkey = XP_RAISEFOCUS_KEY; + break; + + case GDK_Left: + xpkey = XP_CURSOR_KEY_LEFT; + movesCursor = XP_TRUE; + break; + case GDK_Right: + xpkey = XP_CURSOR_KEY_RIGHT; + movesCursor = XP_TRUE; + break; + case GDK_Up: + xpkey = XP_CURSOR_KEY_UP; + movesCursor = XP_TRUE; + break; + case GDK_Down: + xpkey = XP_CURSOR_KEY_DOWN; + movesCursor = XP_TRUE; + break; +#endif + case GDK_BackSpace: + XP_LOGF( "... it's a DEL" ); + xpkey = XP_CURSOR_KEY_DEL; + break; + default: + keyval = keyval & 0x00FF; /* mask out gtk stuff */ + if ( isalpha( keyval ) ) { + xpkey = toupper(keyval); + break; +#ifdef NUMBER_KEY_AS_INDEX + } else if ( isdigit( keyval ) ) { + xpkey = keyval; + break; +#endif + } + } + *movesCursorP = movesCursor; + return xpkey; +} /* evtToXPKey */ + +#ifdef KEYBOARD_NAV +static gint +key_press_event( GtkWidget* XP_UNUSED(widget), GdkEventKey* event, + GtkAppGlobals* globals ) +{ + XP_Bool handled = XP_FALSE; + XP_Bool movesCursor; + XP_Key xpkey = evtToXPKey( event, &movesCursor ); + + gtkSetAltState( globals, event->state ); + + if ( xpkey != XP_KEY_NONE ) { + XP_Bool draw = globals->keyDown ? + board_handleKeyRepeat( globals->cGlobals.game.board, xpkey, + &handled ) + : board_handleKeyDown( globals->cGlobals.game.board, xpkey, + &handled ); + if ( draw ) { + board_draw( globals->cGlobals.game.board ); + } + } + globals->keyDown = XP_TRUE; + return 1; +} +#endif + +static gint +key_release_event( GtkWidget* XP_UNUSED(widget), GdkEventKey* event, + GtkAppGlobals* globals ) +{ + XP_Bool handled = XP_FALSE; + XP_Bool movesCursor; + XP_Key xpkey = evtToXPKey( event, &movesCursor ); + + gtkSetAltState( globals, event->state ); + + if ( xpkey != XP_KEY_NONE ) { + XP_Bool draw; + draw = board_handleKeyUp( globals->cGlobals.game.board, xpkey, + &handled ); +#ifdef KEYBOARD_NAV + if ( movesCursor && !handled ) { + BoardObjectType order[] = { OBJ_SCORE, OBJ_BOARD, OBJ_TRAY }; + draw = linShiftFocus( &globals->cGlobals, xpkey, order, + NULL ) || draw; + } +#endif + if ( draw ) { + board_draw( globals->cGlobals.game.board ); + } + } + +/* XP_ASSERT( globals->keyDown ); */ + globals->keyDown = XP_FALSE; + + return handled? 1 : 0; /* gtk will do something with the key if 0 returned */ +} /* key_release_event */ +#endif + +#ifdef MEM_DEBUG +# define MEMPOOL globals->cGlobals.params->util->mpool, +#else +# define MEMPOOL +#endif + +static void +createOrLoadObjects( GtkAppGlobals* globals ) +{ + XWStreamCtxt* stream = NULL; + XP_Bool opened = XP_FALSE; + +#ifndef XWFEATURE_STANDALONE_ONLY + DeviceRole serverRole = globals->cGlobals.params->serverRole; + XP_Bool isServer = serverRole != SERVER_ISCLIENT; +#endif + LaunchParams* params = globals->cGlobals.params; + + globals->draw = (GtkDrawCtx*)gtkDrawCtxtMake( globals->drawing_area, + globals ); + + if ( !!params->fileName && file_exists( params->fileName ) ) { + + stream = streamFromFile( &globals->cGlobals, params->fileName, globals ); + + opened = game_makeFromStream( MEMPOOL stream, &globals->cGlobals.game, + &globals->cGlobals.params->gi, + params->dict, params->util, + (DrawCtx*)globals->draw, + &globals->cp, + LINUX_SEND, IF_CH(linux_reset) globals ); + + stream_destroy( stream ); + } + + if ( !opened ) { + CommsAddrRec addr; + + XP_MEMSET( &addr, 0, sizeof(addr) ); + addr.conType = params->conType; + +#ifdef XWFEATURE_RELAY + if ( addr.conType == COMMS_CONN_RELAY ) { + XP_ASSERT( !!params->connInfo.relay.relayName ); + globals->cGlobals.defaultServerName + = params->connInfo.relay.relayName; + } +#endif + + params->gi.gameID = util_getCurSeconds(globals->cGlobals.params->util); + XP_STATUSF( "grabbed gameID: %d\n", params->gi.gameID ); + + game_makeNewGame( MEMPOOL &globals->cGlobals.game, ¶ms->gi, + params->util, (DrawCtx*)globals->draw, + params->gi.gameID, &globals->cp, LINUX_SEND, + IF_CH(linux_reset) globals ); + + addr.conType = params->conType; + if ( 0 ) { +#ifdef XWFEATURE_RELAY + } else if ( addr.conType == COMMS_CONN_RELAY ) { + addr.u.ip_relay.ipAddr = 0; + addr.u.ip_relay.port = params->connInfo.relay.defaultSendPort; + XP_STRNCPY( addr.u.ip_relay.hostName, params->connInfo.relay.relayName, + sizeof(addr.u.ip_relay.hostName) - 1 ); + XP_STRNCPY( addr.u.ip_relay.cookie, params->connInfo.relay.cookie, + sizeof(addr.u.ip_relay.cookie) - 1 ); +#endif +#ifdef XWFEATURE_BLUETOOTH + } else if ( addr.conType == COMMS_CONN_BT ) { + XP_ASSERT( sizeof(addr.u.bt.btAddr) + >= sizeof(params->connInfo.bt.hostAddr)); + XP_MEMCPY( &addr.u.bt.btAddr, ¶ms->connInfo.bt.hostAddr, + sizeof(params->connInfo.bt.hostAddr) ); +#endif +#ifdef XWFEATURE_IP_DIRECT + } else if ( addr.conType == COMMS_CONN_IP_DIRECT ) { + XP_STRNCPY( addr.u.ip.hostName_ip, params->connInfo.ip.hostName, + sizeof(addr.u.ip.hostName_ip) - 1 ); + addr.u.ip.port_ip = params->connInfo.ip.port; +#endif + } + +#ifndef XWFEATURE_STANDALONE_ONLY + /* This may trigger network activity */ + if ( !!globals->cGlobals.game.comms ) { + comms_setAddr( globals->cGlobals.game.comms, &addr ); + } +#endif + model_setDictionary( globals->cGlobals.game.model, params->dict ); + + /* params->gi.phoniesAction = PHONIES_DISALLOW; */ +#ifdef XWFEATURE_SEARCHLIMIT + params->gi.allowHintRect = params->allowHintRect; +#endif + +#ifndef XWFEATURE_STANDALONE_ONLY + if ( !isServer ) { + XWStreamCtxt* stream = + mem_stream_make( MEMPOOL params->vtMgr, globals, CHANNEL_NONE, + sendOnClose ); + server_initClientConnection( globals->cGlobals.game.server, + stream ); + } +#endif + } + +#ifndef XWFEATURE_STANDALONE_ONLY + if ( !!globals->cGlobals.game.comms ) { + comms_start( globals->cGlobals.game.comms ); + } +#endif + server_do( globals->cGlobals.game.server ); + +} /* createOrLoadObjects */ + +/* Create a new backing pixmap of the appropriate size and set up contxt to + * draw using that size. + */ +static gboolean +configure_event( GtkWidget* widget, GdkEventConfigure* XP_UNUSED(event), + GtkAppGlobals* globals ) +{ + short bdWidth, bdHeight, leftMargin, topMargin; + short timerLeft, timerTop; + gint hscale, vscale; + gint trayTop; + gint boardTop = 0; + + if ( globals->draw == NULL ) { + createOrLoadObjects( globals ); + } + + bdWidth = widget->allocation.width - (GTK_RIGHT_MARGIN + + GTK_BOARD_LEFT_MARGIN); + if ( globals->cGlobals.params->verticalScore ) { + bdWidth -= GTK_VERT_SCORE_WIDTH; + } + bdHeight = widget->allocation.height - (GTK_TOP_MARGIN + GTK_BOTTOM_MARGIN) + - GTK_MIN_TRAY_SCALEV - GTK_BOTTOM_MARGIN; + + hscale = bdWidth / GTK_NUM_COLS; + vscale = (bdHeight / (GTK_NUM_ROWS + GTK_TRAY_HT_ROWS)); /* makd tray + height 3x cell + height */ + leftMargin = (bdWidth - (hscale*GTK_NUM_COLS)) / 2; + topMargin = (bdHeight - (vscale*(GTK_NUM_ROWS*2))) / 2; + + if ( !globals->cGlobals.params->verticalScore ) { + boardTop += GTK_HOR_SCORE_HEIGHT; + } + + trayTop = boardTop + (vscale * GTK_NUM_ROWS); + /* move tray up if part of board's meant to be hidden */ + trayTop -= vscale * globals->cGlobals.params->nHidden; + board_setPos( globals->cGlobals.game.board, GTK_BOARD_LEFT, boardTop, + XP_FALSE ); + board_setScale( globals->cGlobals.game.board, hscale, vscale ); + board_setShowColors( globals->cGlobals.game.board, XP_TRUE ); + globals->gridOn = XP_TRUE; + + timerTop = GTK_TIMER_TOP; + if ( globals->cGlobals.params->verticalScore ) { + timerLeft = GTK_BOARD_LEFT + (hscale*GTK_NUM_COLS) + 1; + board_setScoreboardLoc( globals->cGlobals.game.board, + timerLeft, + GTK_VERT_SCORE_TOP, + GTK_VERT_SCORE_WIDTH, + vscale*GTK_NUM_COLS, + XP_FALSE ); + + } else { + timerLeft = GTK_BOARD_LEFT + (hscale*GTK_NUM_COLS) - GTK_TIMER_WIDTH; + board_setScoreboardLoc( globals->cGlobals.game.board, + GTK_BOARD_LEFT, GTK_HOR_SCORE_TOP, + timerLeft-GTK_BOARD_LEFT, + GTK_HOR_SCORE_HEIGHT, + XP_TRUE ); + + } + + board_setTimerLoc( globals->cGlobals.game.board, timerLeft, timerTop, + GTK_TIMER_WIDTH, GTK_HOR_SCORE_HEIGHT ); + + board_setTrayLoc( globals->cGlobals.game.board, GTK_TRAY_LEFT, trayTop, + hscale * GTK_NUM_COLS, vscale * GTK_TRAY_HT_ROWS + 10, + GTK_DIVIDER_WIDTH ); + + setCtrlsForTray( globals ); + + board_invalAll( globals->cGlobals.game.board ); + + return TRUE; +} /* configure_event */ + +/* Redraw the screen from the backing pixmap */ +static gint +expose_event( GtkWidget* XP_UNUSED(widget), + GdkEventExpose* XP_UNUSED(event), + GtkAppGlobals* globals ) +{ + /* + gdk_draw_rectangle( widget->window,//((GtkDrawCtx*)globals->draw)->pixmap, + widget->style->white_gc, + TRUE, + 0, 0, + widget->allocation.width, + widget->allocation.height+widget->allocation.y ); + */ + /* I want to inval only the area that's exposed, but the rect is always + empty, even when clearly shouldn't be. Need to investigate. Until + fixed, use board_invalAll to ensure board is drawn.*/ +/* board_invalRect( globals->cGlobals.game.board, (XP_Rect*)&event->area ); */ + + board_invalAll( globals->cGlobals.game.board ); + board_draw( globals->cGlobals.game.board ); + +/* gdk_draw_pixmap( widget->window, */ +/* widget->style->fg_gc[GTK_WIDGET_STATE (widget)], */ +/* ((GtkDrawCtx*)globals->draw)->pixmap, */ +/* event->area.x, event->area.y, */ +/* event->area.x, event->area.y, */ +/* event->area.width, event->area.height ); */ + + return FALSE; +} /* expose_event */ + +#if 0 +static gint +handle_client_event( GtkWidget *widget, GdkEventClient *event, + GtkAppGlobals* globals ) +{ + XP_LOGF( "handle_client_event called: event->type = " ); + if ( event->type == GDK_CLIENT_EVENT ) { + XP_LOGF( "GDK_CLIENT_EVENT" ); + return 1; + } else { + XP_LOGF( "%d", event->type ); + return 0; + } +} /* handle_client_event */ +#endif + +static void +quit( void* XP_UNUSED(dunno), GtkAppGlobals* globals ) +{ + if ( !!globals->cGlobals.params->fileName ) { + XWStreamCtxt* outStream; + + outStream = mem_stream_make( MEMPOOL globals->cGlobals.params->vtMgr, + globals, 0, writeToFile ); + stream_open( outStream ); + + game_saveToStream( &globals->cGlobals.game, + &globals->cGlobals.params->gi, + outStream ); + + stream_destroy( outStream ); + } + + game_dispose( &globals->cGlobals.game ); /* takes care of the dict */ + gi_disposePlayerInfo( MEMPOOL &globals->cGlobals.params->gi ); + +#ifdef XWFEATURE_BLUETOOTH + linux_bt_close( &globals->cGlobals ); +#endif +#ifdef XWFEATURE_IP_DIRECT + linux_udp_close( &globals->cGlobals ); +#endif + vtmgr_destroy( MEMPOOL globals->cGlobals.params->vtMgr ); + + mpool_destroy( globals->cGlobals.params->util->mpool ); + + gtk_exit( 0 ); +} /* quit */ + +GtkWidget* +makeAddSubmenu( GtkWidget* menubar, gchar* label ) +{ + GtkWidget* submenu; + GtkWidget* item; + + item = gtk_menu_item_new_with_label( label ); + gtk_menu_bar_append( GTK_MENU_BAR(menubar), item ); + + submenu = gtk_menu_new(); + gtk_menu_item_set_submenu( GTK_MENU_ITEM(item), submenu ); + + gtk_widget_show(item); + + return submenu; +} /* makeAddSubmenu */ + +static void +tile_values( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + if ( !!globals->cGlobals.game.server ) { + XWStreamCtxt* stream = + mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + globals, + CHANNEL_NONE, + catOnClose ); + server_formatDictCounts( globals->cGlobals.game.server, stream, 5 ); + stream_putU8( stream, '\n' ); + stream_destroy( stream ); + } + +} /* tile_values */ + +static void +game_history( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + catGameHistory( &globals->cGlobals ); +} /* game_history */ + +static void +final_scores( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + XP_Bool gameOver = server_getGameIsOver( globals->cGlobals.game.server ); + + if ( gameOver ) { + printFinalScores( globals ); + } else { + if ( gtkask( "Are you sure everybody wants to end the game now?", + GTK_BUTTONS_YES_NO ) ) { + server_endGame( globals->cGlobals.game.server ); + gameOver = TRUE; + } + } + + /* the end game listener will take care of printing the final scores */ +} /* final_scores */ + +static void +new_game( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + gboolean confirmed; + + confirmed = newGameDialog( globals, XP_TRUE ); + if ( confirmed ) { + CurGameInfo* gi = &globals->cGlobals.params->gi; +#ifndef XWFEATURE_STANDALONE_ONLY + XP_Bool isClient = gi->serverRole == SERVER_ISCLIENT; +#endif + XP_U32 gameID = util_getCurSeconds( globals->cGlobals.params->util ); + + XP_STATUSF( "grabbed gameID: %ld\n", gameID ); + game_reset( MEMPOOL &globals->cGlobals.game, gi, + globals->cGlobals.params->util, + gameID, &globals->cp, LINUX_SEND, + IF_CH(linux_reset) globals ); + +#ifndef XWFEATURE_STANDALONE_ONLY + if ( isClient ) { + XWStreamCtxt* stream = + mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + globals, + CHANNEL_NONE, + sendOnClose ); + server_initClientConnection( globals->cGlobals.game.server, + stream ); + } +#endif + (void)server_do( globals->cGlobals.game.server ); /* assign tiles, etc. */ + board_invalAll( globals->cGlobals.game.board ); + board_draw( globals->cGlobals.game.board ); + } + +} /* new_game */ + +static void +game_info( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + /* Anything to do if OK is clicked? Changed names etc. already saved. Try + server_do in case one's become a robot. */ + if ( newGameDialog( globals, XP_FALSE ) ) { + if ( server_do( globals->cGlobals.game.server ) ) { + board_draw( globals->cGlobals.game.board ); + } + } +} + +static void +load_game( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* XP_UNUSED(globals) ) +{ +} /* load_game */ + +static void +save_game( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* XP_UNUSED(globals) ) +{ +} /* save_game */ + +static void +load_dictionary( GtkWidget* XP_UNUSED(widget), + GtkAppGlobals* XP_UNUSED(globals) ) +{ +} /* load_dictionary */ + +static void +handle_undo( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* XP_UNUSED(globals) ) +{ +} /* handle_undo */ + +static void +handle_redo( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* XP_UNUSED(globals) ) +{ +} /* handle_redo */ + +#ifdef FEATURE_TRAY_EDIT +static void +handle_trayEditToggle( GtkWidget* XP_UNUSED(widget), + GtkAppGlobals* XP_UNUSED(globals), + XP_Bool XP_UNUSED(on) ) +{ +} /* handle_trayEditToggle */ + +static void +handle_trayEditToggle_on( GtkWidget* widget, GtkAppGlobals* globals ) +{ + handle_trayEditToggle( widget, globals, XP_TRUE ); +} + +static void +handle_trayEditToggle_off( GtkWidget* widget, GtkAppGlobals* globals ) +{ + handle_trayEditToggle( widget, globals, XP_FALSE ); +} +#endif + +#ifndef XWFEATURE_STANDALONE_ONLY +static void +handle_resend( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + CommsCtxt* comms = globals->cGlobals.game.comms; + if ( comms != NULL ) { + comms_resendAll( comms ); + } +} /* handle_resend */ + +#ifdef DEBUG +static void +handle_commstats( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + CommsCtxt* comms = globals->cGlobals.game.comms; + + if ( !!comms ) { + XWStreamCtxt* stream = + mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + globals, + CHANNEL_NONE, catOnClose ); + comms_getStats( comms, stream ); + stream_destroy( stream ); + } +} /* handle_commstats */ +#endif +#endif + +#ifdef MEM_DEBUG +static void +handle_memstats( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + XWStreamCtxt* stream = mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + globals, + CHANNEL_NONE, catOnClose ); + mpool_stats( MEMPOOL stream ); + stream_destroy( stream ); + +} /* handle_memstats */ +#endif + +static GtkWidget* +createAddItem( GtkWidget* parent, gchar* label, + GtkSignalFunc handlerFunc, GtkAppGlobals* globals ) +{ + GtkWidget* item = gtk_menu_item_new_with_label( label ); + +/* g_print( "createAddItem called with label %s\n", label ); */ + + if ( handlerFunc != NULL ) { + g_signal_connect( GTK_OBJECT(item), "activate", + G_CALLBACK(handlerFunc), globals ); + } + + gtk_menu_append( GTK_MENU(parent), item ); + gtk_widget_show( item ); + + return item; +} /* createAddItem */ + +static GtkWidget* +makeMenus( GtkAppGlobals* globals, int XP_UNUSED(argc), + char** XP_UNUSED(argv) ) +{ + GtkWidget* menubar = gtk_menu_bar_new(); + GtkWidget* fileMenu; + + fileMenu = makeAddSubmenu( menubar, "File" ); + (void)createAddItem( fileMenu, "Tile values", + GTK_SIGNAL_FUNC(tile_values), globals ); + (void)createAddItem( fileMenu, "Game history", + GTK_SIGNAL_FUNC(game_history), globals ); + + (void)createAddItem( fileMenu, "Final scores", + GTK_SIGNAL_FUNC(final_scores), globals ); + + (void)createAddItem( fileMenu, "New game", + GTK_SIGNAL_FUNC(new_game), globals ); + (void)createAddItem( fileMenu, "Game info", + GTK_SIGNAL_FUNC(game_info), globals ); + + (void)createAddItem( fileMenu, "Load game", + GTK_SIGNAL_FUNC(load_game), globals ); + (void)createAddItem( fileMenu, "Save game", + GTK_SIGNAL_FUNC(save_game), globals ); + + (void)createAddItem( fileMenu, "Load dictionary", + GTK_SIGNAL_FUNC(load_dictionary), globals ); + + fileMenu = makeAddSubmenu( menubar, "Edit" ); + + (void)createAddItem( fileMenu, "Undo", + GTK_SIGNAL_FUNC(handle_undo), globals ); + (void)createAddItem( fileMenu, "Redo", + GTK_SIGNAL_FUNC(handle_redo), globals ); + +#ifdef FEATURE_TRAY_EDIT + (void)createAddItem( fileMenu, "Allow tray edit", + GTK_SIGNAL_FUNC(handle_trayEditToggle_on), globals ); + (void)createAddItem( fileMenu, "Dis-allow tray edit", + GTK_SIGNAL_FUNC(handle_trayEditToggle_off), globals ); +#endif + + fileMenu = makeAddSubmenu( menubar, "Network" ); + +#ifndef XWFEATURE_STANDALONE_ONLY + (void)createAddItem( fileMenu, "Resend", + GTK_SIGNAL_FUNC(handle_resend), globals ); +# ifdef DEBUG + (void)createAddItem( fileMenu, "Stats", + GTK_SIGNAL_FUNC(handle_commstats), globals ); +# endif +#endif +#ifdef MEM_DEBUG + (void)createAddItem( fileMenu, "Mem stats", + GTK_SIGNAL_FUNC(handle_memstats), globals ); +#endif + + /* (void)createAddItem( fileMenu, "Print board", */ + /* GTK_SIGNAL_FUNC(handle_print_board), globals ); */ + + /* listAllGames( menubar, argc, argv, globals ); */ + + gtk_widget_show( menubar ); + + return menubar; +} /* makeMenus */ + +static gboolean +handle_flip_button( GtkWidget* XP_UNUSED(widget), gpointer _globals ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)_globals; + if ( board_flip( globals->cGlobals.game.board ) ) { + board_draw( globals->cGlobals.game.board ); + } + return TRUE; +} /* handle_flip_button */ + +static gboolean +handle_value_button( GtkWidget* XP_UNUSED(widget), gpointer closure ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)closure; + if ( board_toggle_showValues( globals->cGlobals.game.board ) ) { + board_draw( globals->cGlobals.game.board ); + } + return TRUE; +} /* handle_value_button */ + +static void +handle_hint_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + XP_Bool redo; + if ( board_requestHint( globals->cGlobals.game.board, +#ifdef XWFEATURE_SEARCHLIMIT + XP_FALSE, +#endif + &redo ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* handle_hint_button */ + +static void +handle_nhint_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + XP_Bool redo; + + board_resetEngine( globals->cGlobals.game.board ); + if ( board_requestHint( globals->cGlobals.game.board, +#ifdef XWFEATURE_SEARCHLIMIT + XP_TRUE, +#endif + &redo ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* handle_nhint_button */ + +static void +handle_colors_button( GtkWidget* XP_UNUSED(widget), + GtkAppGlobals* XP_UNUSED(globals) ) +{ +/* XP_Bool oldVal = board_getShowColors( globals->cGlobals.game.board ); */ +/* if ( board_setShowColors( globals->cGlobals.game.board, !oldVal ) ) { */ +/* board_draw( globals->cGlobals.game.board ); */ +/* } */ +} /* handle_colors_button */ + +static void +handle_juggle_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + if ( board_juggleTray( globals->cGlobals.game.board ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* handle_juggle_button */ + +static void +handle_undo_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + if ( server_handleUndo( globals->cGlobals.game.server ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* handle_undo_button */ + +static void +handle_redo_button( GtkWidget* XP_UNUSED(widget), + GtkAppGlobals* XP_UNUSED(globals) ) +{ +} /* handle_redo_button */ + +static void +handle_trade_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + if ( board_beginTrade( globals->cGlobals.game.board ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* handle_juggle_button */ + +static void +handle_done_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + if ( board_commitTurn( globals->cGlobals.game.board ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* handle_done_button */ + +static void +scroll_value_changed( GtkAdjustment *adj, GtkAppGlobals* globals ) +{ + XP_U16 newValue; + gfloat newValueF = adj->value; + + XP_ASSERT( newValueF >= 0.0 + && newValueF <= globals->cGlobals.params->nHidden ); + newValue = (XP_U16)newValueF; + + if ( board_setYOffset( globals->cGlobals.game.board, newValue ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* scroll_value_changed */ + +static void +handle_grid_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + XP_U16 scaleH, scaleV; + XP_Bool gridOn = globals->gridOn; + + board_getScale( globals->cGlobals.game.board, &scaleH, &scaleV ); + + if ( gridOn ) { + --scaleH; + --scaleV; + } else { + ++scaleH; + ++scaleV; + } + + board_setScale( globals->cGlobals.game.board, scaleH, scaleV ); + globals->gridOn = !gridOn; + + board_draw( globals->cGlobals.game.board ); +} /* handle_grid_button */ + +static void +handle_hide_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + BoardCtxt* board; + XP_Bool draw = XP_FALSE; + + if ( globals->cGlobals.params->nHidden > 0 ) { + globals->adjustment->page_size = GTK_NUM_ROWS; + globals->adjustment->value = 0.0; + + gtk_signal_emit_by_name( GTK_OBJECT(globals->adjustment), "changed" ); + gtk_adjustment_value_changed( GTK_ADJUSTMENT(globals->adjustment) ); + } + + board = globals->cGlobals.game.board; + if ( TRAY_REVEALED == board_getTrayVisState( board ) ) { + draw = board_hideTray( board ); + } else { + draw = board_showTray( board ); + } + if ( draw ) { + board_draw( board ); + } +} /* handle_hide_button */ + +static void +handle_commit_button( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* globals ) +{ + if ( board_commitTurn( globals->cGlobals.game.board ) ) { + board_draw( globals->cGlobals.game.board ); + } +} /* handle_commit_button */ + +static void +gtkUserError( GtkAppGlobals* XP_UNUSED(globals), const char* format, ... ) +{ + char buf[512]; + va_list ap; + + va_start( ap, format ); + + vsprintf( buf, format, ap ); + + (void)gtkask( buf, GTK_BUTTONS_OK ); + + va_end(ap); +} /* gtkUserError */ + +static VTableMgr* +gtk_util_getVTManager(XW_UtilCtxt* uc) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + return globals->cGlobals.params->vtMgr; +} /* linux_util_getVTManager */ + +static XP_S16 +gtk_util_userPickTile( XW_UtilCtxt* uc, const PickInfo* pi, + XP_U16 playerNum, + const XP_UCHAR4* texts, XP_U16 nTiles ) +{ + XP_S16 chosen; + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + XP_UCHAR* name = globals->cGlobals.params->gi.players[playerNum].name; + + chosen = gtkletterask( pi->why == PICK_FOR_BLANK, name, nTiles, texts ); + return chosen; +} /* gtk_util_userPickTile */ + +static XP_Bool +gtk_util_askPassword( XW_UtilCtxt* XP_UNUSED(uc), const XP_UCHAR* name, + XP_UCHAR* buf, XP_U16* len ) +{ + XP_Bool ok = gtkpasswdask( name, buf, len ); + return ok; +} /* gtk_util_askPassword */ + +static void +setCtrlsForTray( GtkAppGlobals* globals ) +{ + XW_TrayVisState state = + board_getTrayVisState( globals->cGlobals.game.board ); + XP_S16 nHidden = globals->cGlobals.params->nHidden; + + if ( nHidden != 0 ) { + XP_U16 pageSize = GTK_NUM_ROWS; + + if ( state == TRAY_HIDDEN ) { /* we recover what tray covers */ + nHidden -= GTK_TRAY_HT_ROWS; + } + if ( nHidden > 0 ) { + pageSize -= nHidden; + } + globals->adjustment->page_size = pageSize; + + globals->adjustment->value = + board_getYOffset( globals->cGlobals.game.board ); + gtk_signal_emit_by_name( GTK_OBJECT(globals->adjustment), "changed" ); + } +} /* setCtrlsForTray */ + +static void +gtk_util_trayHiddenChange( XW_UtilCtxt* uc, XW_TrayVisState XP_UNUSED(state), + XP_U16 XP_UNUSED(nVisibleRows) ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + setCtrlsForTray( globals ); +} /* gtk_util_trayHiddenChange */ + +static void +gtk_util_yOffsetChange( XW_UtilCtxt* uc, XP_U16 XP_UNUSED(oldOffset), + XP_U16 newOffset ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + XP_ASSERT( globals->cGlobals.params->nHidden > 0 ); + globals->adjustment->value = newOffset; + gtk_adjustment_value_changed( GTK_ADJUSTMENT(globals->adjustment) ); +} /* gtk_util_yOffsetChange */ + +static void +printFinalScores( GtkAppGlobals* globals ) +{ + XWStreamCtxt* stream; + + stream = mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + globals, CHANNEL_NONE, catOnClose ); + server_writeFinalScores( globals->cGlobals.game.server, stream ); + stream_putU8( stream, '\n' ); + stream_destroy( stream ); +} /* printFinalScores */ + +static void +gtk_util_notifyGameOver( XW_UtilCtxt* uc ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + + if ( globals->cGlobals.params->printHistory ) { + catGameHistory( &globals->cGlobals ); + } + + printFinalScores( globals ); + + if ( globals->cGlobals.params->quitAfter >= 0 ) { + sleep( globals->cGlobals.params->quitAfter ); + quit( NULL, globals ); + } else if ( globals->cGlobals.params->undoWhenDone ) { + server_handleUndo( globals->cGlobals.game.server ); + } + + board_draw( globals->cGlobals.game.board ); +} /* gtk_util_notifyGameOver */ + +/* define this to prevent user events during debugging from stopping the engine */ +/* #define DONT_ABORT_ENGINE */ + +static XP_Bool +gtk_util_hiliteCell( XW_UtilCtxt* uc, XP_U16 col, XP_U16 row ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; +#ifndef DONT_ABORT_ENGINE + gboolean pending; +#endif + + board_hiliteCellAt( globals->cGlobals.game.board, col, row ); + if ( globals->cGlobals.params->sleepOnAnchor ) { + } + +#ifdef DONT_ABORT_ENGINE + return XP_TRUE; /* keep going */ +#else + pending = gdk_events_pending(); + if ( pending ) { + XP_DEBUGF( "gtk_util_hiliteCell=>%d", pending ); + } + return !pending; +#endif +} /* gtk_util_hiliteCell */ + +static XP_Bool +gtk_util_altKeyDown( XW_UtilCtxt* uc ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + return globals->altKeyDown; +} + +static XP_Bool +gtk_util_engineProgressCallback( XW_UtilCtxt* XP_UNUSED(uc) ) +{ +#ifdef DONT_ABORT_ENGINE + return XP_TRUE; /* keep going */ +#else + gboolean pending = gdk_events_pending(); + +/* XP_DEBUGF( "gdk_events_pending returned %d\n", pending ); */ + + return !pending; +#endif +} /* gtk_util_engineProgressCallback */ + +static void +cancelTimer( GtkAppGlobals* globals, XWTimerReason why ) +{ + guint src = globals->timerSources[why-1]; + if ( src != 0 ) { + g_source_remove( src ); + globals->timerSources[why-1] = 0; + } +} /* cancelTimer */ + +static gint +pentimer_idle_func( gpointer data ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)data; + struct timeval tv; + XP_Bool callAgain = XP_TRUE; + + gettimeofday( &tv, NULL ); + + if ( (tv.tv_usec - globals->penTv.tv_usec) >= globals->penTimerInterval) { + linuxFireTimer( &globals->cGlobals, TIMER_PENDOWN ); + callAgain = XP_FALSE; + } + + return callAgain; +} /* timer_idle_func */ + +static gint +score_timer_func( gpointer data ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)data; + + linuxFireTimer( &globals->cGlobals, TIMER_TIMERTICK ); + + return XP_FALSE; +} /* score_timer_func */ + +#if defined RELAY_HEARTBEAT || defined COMMS_HEARTBEAT +static gint +heartbeat_timer_func( gpointer data ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)data; + + if ( !globals->cGlobals.params->noHeartbeat ) { + linuxFireTimer( &globals->cGlobals, TIMER_HEARTBEAT ); + } + + return (gint)0; +} +#endif + +static void +gtk_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why, + XP_U16 when, + XWTimerProc proc, void* closure ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + guint newSrc; + + cancelTimer( globals, why ); + + if ( why == TIMER_PENDOWN ) { + /* half a second */ + globals->penTimerInterval = 50 * 10000; + + (void)gettimeofday( &globals->penTv, NULL ); + newSrc = g_idle_add( pentimer_idle_func, globals ); + } else if ( why == TIMER_TIMERTICK ) { + /* one second */ + globals->scoreTimerInterval = 100 * 10000; + + (void)gettimeofday( &globals->scoreTv, NULL ); + + newSrc = g_timeout_add( 1000, score_timer_func, globals ); +#if defined RELAY_HEARTBEAT || defined COMMS_HEARTBEAT + } else if ( why == TIMER_HEARTBEAT ) { + newSrc = g_timeout_add( 1000 * when, heartbeat_timer_func, globals ); +#endif + } else { + XP_ASSERT( 0 ); + } + + globals->cGlobals.timerProcs[why] = proc; + globals->cGlobals.timerClosures[why] = closure; + XP_ASSERT( newSrc != 0 ); + globals->timerSources[why-1] = newSrc; +} /* gtk_util_setTimer */ + +static gint +idle_func( gpointer data ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)data; +/* XP_DEBUGF( "idle_func called\n" ); */ + + /* remove before calling server_do. If server_do puts up a dialog that + calls gtk_main, then this idle proc will also apply to that event loop + and bad things can happen. So kill the idle proc asap. */ + gtk_idle_remove( globals->idleID ); + + if ( server_do( globals->cGlobals.game.server ) ) { + XP_ASSERT( globals->cGlobals.game.board != NULL ); + board_draw( globals->cGlobals.game.board ); + } + return 0; /* 0 will stop it from being called again */ +} /* idle_func */ + +static void +gtk_util_requestTime( XW_UtilCtxt* uc ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + globals->idleID = gtk_idle_add( idle_func, globals ); +} /* gtk_util_requestTime */ + +static XP_Bool +gtk_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi, XP_U16 player, + XP_Bool turnLost ) +{ + XP_Bool result; + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + char buf[300]; + + if ( turnLost ) { + char wordsBuf[256]; + XP_U16 i; + XP_UCHAR* name = globals->cGlobals.params->gi.players[player].name; + XP_ASSERT( !!name ); + + for ( i = 0, wordsBuf[0] = '\0'; ; ) { + char wordBuf[18]; + sprintf( wordBuf, "\"%s\"", bwi->words[i] ); + strcat( wordsBuf, wordBuf ); + if ( ++i == bwi->nWords ) { + break; + } + strcat( wordsBuf, ", " ); + } + + sprintf( buf, "Player %d (%s) played illegal word[s] %s; loses turn.", + player+1, name, wordsBuf ); + + if ( globals->cGlobals.params->skipWarnings ) { + XP_LOGF( "%s", buf ); + } else { + gtkUserError( globals, buf ); + } + result = XP_TRUE; + } else { + XP_ASSERT( bwi->nWords == 1 ); + sprintf( buf, "Word \"%s\" not in the current dictionary. " + "Use it anyway?", bwi->words[0] ); + result = gtkask( buf, GTK_BUTTONS_YES_NO ); + } + + return result; +} /* gtk_util_warnIllegalWord */ + +static void +gtk_util_remSelected( XW_UtilCtxt* uc ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + XWStreamCtxt* stream; + XP_UCHAR* text; + + stream = mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + globals, CHANNEL_NONE, NULL ); + board_formatRemainingTiles( globals->cGlobals.game.board, stream ); + text = strFromStream( stream ); + stream_destroy( stream ); + + (void)gtkask( text, GTK_BUTTONS_OK ); + free( text ); +} + +#ifndef XWFEATURE_STANDALONE_ONLY +static XWStreamCtxt* +gtk_util_makeStreamFromAddr(XW_UtilCtxt* uc, XP_PlayerAddr channelNo ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + + XWStreamCtxt* stream = mem_stream_make( MEMPOOL + globals->cGlobals.params->vtMgr, + uc->closure, channelNo, + sendOnClose ); + return stream; +} /* gtk_util_makeStreamFromAddr */ +#endif + +#ifdef XWFEATURE_SEARCHLIMIT +static XP_Bool +gtk_util_getTraySearchLimits( XW_UtilCtxt* XP_UNUSED(uc), + XP_U16* XP_UNUSED(min), XP_U16* max ) +{ + *max = askNTiles( MAX_TRAY_TILES, *max ); + return XP_TRUE; +} +#endif + + +static void +gtk_util_userError( XW_UtilCtxt* uc, UtilErrID id ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + XP_Bool silent; + const XP_UCHAR* message = linux_getErrString( id, &silent ); + + XP_LOGF( "%s(%d)", __func__, id ); + + if ( silent ) { + XP_LOGF( "%s", message ); + } else { + gtkUserError( globals, message ); + } +} /* gtk_util_userError */ + +static XP_Bool +gtk_util_userQuery( XW_UtilCtxt* XP_UNUSED(uc), UtilQueryID id, + XWStreamCtxt* stream ) +{ + XP_Bool result; + char* question; + XP_Bool freeMe = XP_FALSE; + GtkButtonsType buttons = GTK_BUTTONS_YES_NO; + + switch( id ) { + + case QUERY_COMMIT_TURN: + question = strFromStream( stream ); + freeMe = XP_TRUE; + break; + case QUERY_COMMIT_TRADE: + question = "Are you sure you want to trade the selected tiles?"; + break; + case QUERY_ROBOT_MOVE: + case QUERY_ROBOT_TRADE: + question = strFromStream( stream ); + freeMe = XP_TRUE; + buttons = GTK_BUTTONS_OK; + break; + + default: + XP_ASSERT( 0 ); + return XP_FALSE; + } + + result = gtkask( question, buttons ); + + if ( freeMe > 0 ) { + free( question ); + } + + return result; +} /* gtk_util_userQuery */ + +static GtkWidget* +makeShowButtonFromBitmap( void* closure, const gchar* filename, + const gchar* alt, GCallback func ) +{ + GtkWidget* widget; + GtkWidget* button; + + if ( file_exists( filename ) ) { + widget = gtk_image_new_from_file (filename); + } else { + widget = gtk_label_new( alt ); + } + gtk_widget_show( widget ); + + button = gtk_button_new(); + gtk_container_add (GTK_CONTAINER (button), widget ); + gtk_widget_show (button); + + if ( func != NULL ) { + g_signal_connect( GTK_OBJECT(button), "clicked", func, closure ); + } + + return button; +} /* makeShowButtonFromBitmap */ + +static GtkWidget* +makeVerticalBar( GtkAppGlobals* globals, GtkWidget* XP_UNUSED(window) ) +{ + GtkWidget* vbox; + GtkWidget* button; + + vbox = gtk_vbutton_box_new(); + + button = makeShowButtonFromBitmap( globals, "../flip.xpm", "f", + G_CALLBACK(handle_flip_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + button = makeShowButtonFromBitmap( globals, "../value.xpm", "v", + G_CALLBACK(handle_value_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + button = makeShowButtonFromBitmap( globals, "../hint.xpm", "?", + G_CALLBACK(handle_hint_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + button = makeShowButtonFromBitmap( globals, "../hintNum.xpm", "n", + G_CALLBACK(handle_nhint_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + button = makeShowButtonFromBitmap( globals, "../colors.xpm", "c", + G_CALLBACK(handle_colors_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + /* undo and redo buttons */ + button = makeShowButtonFromBitmap( globals, "../undo.xpm", "u", + G_CALLBACK(handle_undo_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + button = makeShowButtonFromBitmap( globals, "../redo.xpm", "r", + G_CALLBACK(handle_redo_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + /* the four buttons that on palm are beside the tray */ + button = makeShowButtonFromBitmap( globals, "../juggle.xpm", "j", + G_CALLBACK(handle_juggle_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + button = makeShowButtonFromBitmap( globals, "../trade.xpm", "t", + G_CALLBACK(handle_trade_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + button = makeShowButtonFromBitmap( globals, "../done.xpm", "d", + G_CALLBACK(handle_done_button) ); + gtk_box_pack_start( GTK_BOX(vbox), button, FALSE, TRUE, 0 ); + + gtk_widget_show( vbox ); + return vbox; +} /* makeVerticalBar */ + +static GtkWidget* +makeButtons( GtkAppGlobals* globals, int XP_UNUSED(argc), + char** XP_UNUSED(argv) ) +{ + short i; + GtkWidget* hbox; + GtkWidget* button; + + struct { + char* name; + GCallback func; + } buttons[] = { + /* { "Flip", handle_flip_button }, */ + { "Grid", G_CALLBACK(handle_grid_button) }, + { "Hide", G_CALLBACK(handle_hide_button) }, + { "Commit", G_CALLBACK(handle_commit_button) }, + }; + + hbox = gtk_hbox_new( FALSE, 0 ); + + for ( i = 0; i < sizeof(buttons)/sizeof(*buttons); ++i ) { + button = gtk_button_new_with_label( buttons[i].name ); + gtk_widget_show( button ); + g_signal_connect( GTK_OBJECT(button), "clicked", + G_CALLBACK(buttons[i].func), globals ); + + gtk_box_pack_start( GTK_BOX(hbox), button, FALSE, TRUE, 0); + } + + gtk_widget_show( hbox ); + return hbox; +} /* makeButtons */ + +static void +setupGtkUtilCallbacks( GtkAppGlobals* globals, XW_UtilCtxt* util ) +{ + util->vtable->m_util_userError = gtk_util_userError; + util->vtable->m_util_userQuery = gtk_util_userQuery; + util->vtable->m_util_getVTManager = gtk_util_getVTManager; + util->vtable->m_util_userPickTile = gtk_util_userPickTile; + util->vtable->m_util_askPassword = gtk_util_askPassword; + util->vtable->m_util_trayHiddenChange = gtk_util_trayHiddenChange; + util->vtable->m_util_yOffsetChange = gtk_util_yOffsetChange; + util->vtable->m_util_notifyGameOver = gtk_util_notifyGameOver; + util->vtable->m_util_hiliteCell = gtk_util_hiliteCell; + util->vtable->m_util_altKeyDown = gtk_util_altKeyDown; + util->vtable->m_util_engineProgressCallback = + gtk_util_engineProgressCallback; + util->vtable->m_util_setTimer = gtk_util_setTimer; + util->vtable->m_util_requestTime = gtk_util_requestTime; + util->vtable->m_util_warnIllegalWord = gtk_util_warnIllegalWord; + util->vtable->m_util_remSelected = gtk_util_remSelected; +#ifndef XWFEATURE_STANDALONE_ONLY + util->vtable->m_util_makeStreamFromAddr = gtk_util_makeStreamFromAddr; +#endif +#ifdef XWFEATURE_SEARCHLIMIT + util->vtable->m_util_getTraySearchLimits = gtk_util_getTraySearchLimits; +#endif + + util->closure = globals; +} /* setupGtkUtilCallbacks */ + +#ifndef XWFEATURE_STANDALONE_ONLY +static gboolean +newConnectionInput( GIOChannel *source, + GIOCondition condition, + gpointer data ) +{ + gboolean keepSource; + int sock = g_io_channel_unix_get_fd( source ); + GtkAppGlobals* globals = (GtkAppGlobals*)data; + + XP_LOGF( "%s:condition = 0x%x", __func__, (int)condition ); + +/* XP_ASSERT( sock == globals->cGlobals.socket ); */ + + if ( (condition & (G_IO_HUP | G_IO_ERR)) != 0 ) { + XP_LOGF( "dropping socket %d", sock ); + close( sock ); +#ifdef XWFEATURE_RELAY + globals->cGlobals.socket = -1; +#endif + if ( 0 ) { +#ifdef XWFEATURE_BLUETOOTH + } else if ( COMMS_CONN_BT == globals->cGlobals.params->conType ) { + linux_bt_socketclosed( &globals->cGlobals, sock ); +#endif +#ifdef XWFEATURE_IP_DIRECT + } else if ( COMMS_CONN_IP_DIRECT == globals->cGlobals.params->conType ) { + linux_udp_socketclosed( &globals->cGlobals, sock ); +#endif + } + keepSource = FALSE; /* remove the event source */ + + } else if ( (condition & G_IO_IN) != 0 ) { + ssize_t nRead; + unsigned char buf[512]; + CommsAddrRec addr; + CommsAddrRec* addrp = NULL; + + if ( 0 ) { +#ifdef XWFEATURE_RELAY + } else if ( globals->cGlobals.params->conType == COMMS_CONN_RELAY ) { + nRead = linux_relay_receive( &globals->cGlobals, + buf, sizeof(buf) ); +#endif +#ifdef XWFEATURE_BLUETOOTH + } else if ( globals->cGlobals.params->conType == COMMS_CONN_BT ) { + nRead = linux_bt_receive( sock, buf, sizeof(buf) ); +#endif +#ifdef XWFEATURE_IP_DIRECT + } else if ( globals->cGlobals.params->conType == COMMS_CONN_IP_DIRECT ) { + addrp = &addr; + nRead = linux_udp_receive( sock, buf, sizeof(buf), addrp, &globals->cGlobals ); +#endif + } else { + XP_ASSERT( 0 ); + } + + if ( !globals->dropIncommingMsgs && nRead > 0 ) { + XWStreamCtxt* inboundS; + XP_Bool redraw = XP_FALSE; + + inboundS = stream_from_msgbuf( &globals->cGlobals, buf, nRead ); + if ( !!inboundS ) { + if ( comms_checkIncomingStream( globals->cGlobals.game.comms, + inboundS, addrp ) ) { + redraw = + server_receiveMessage(globals->cGlobals.game.server, + inboundS ); + } + stream_destroy( inboundS ); + } + + /* if there's something to draw resulting from the message, we + need to give the main loop time to reflect that on the screen + before giving the server another shot. So just call the idle + proc. */ + if ( redraw ) { + gtk_util_requestTime( globals->cGlobals.params->util ); + } else { + redraw = server_do( globals->cGlobals.game.server ); + } + if ( redraw ) { + board_draw( globals->cGlobals.game.board ); + } + } else { + XP_LOGF( "errno from read: %d", errno ); + } + keepSource = TRUE; + } + return keepSource; /* FALSE means to remove event source */ +} /* newConnectionInput */ + +typedef struct SockInfo { + GIOChannel* channel; + guint watch; + int socket; +} SockInfo; + +static void +gtk_socket_changed( void* closure, int oldSock, int newSock, void** storage ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)closure; + SockInfo* info = (SockInfo*)*storage; + XP_LOGF( "%s(old:%d; new:%d)", __func__, oldSock, newSock ); + + if ( oldSock != -1 ) { + XP_ASSERT( info != NULL ); + g_source_remove( info->watch ); + g_io_channel_unref( info->channel ); + XP_FREE( globals->cGlobals.params->util->mpool, info ); + *storage = NULL; + XP_LOGF( "Removed socket %d from gtk's list of listened-to sockets", oldSock ); + } + if ( newSock != -1 ) { + info = (SockInfo*)XP_MALLOC( globals->cGlobals.params->util->mpool, + sizeof(*info) ); + GIOChannel* channel = g_io_channel_unix_new( newSock ); + g_io_channel_set_close_on_unref( channel, TRUE ); + guint result = g_io_add_watch( channel, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_PRI, + newConnectionInput, + globals ); + info->channel = channel; + info->watch = result; + *storage = info; + XP_LOGF( "g_io_add_watch(%d) => %d", newSock, result ); + } +#ifdef XWFEATURE_RELAY + globals->cGlobals.socket = newSock; +#endif + /* A hack for the bluetooth case. */ + CommsCtxt* comms = globals->cGlobals.game.comms; + if ( (comms != NULL) && (comms_getConType(comms) == COMMS_CONN_BT) ) { + comms_resendAll( comms ); + } + LOG_RETURN_VOID(); +} + +static gboolean +acceptorInput( GIOChannel* source, GIOCondition condition, gpointer data ) +{ + gboolean keepSource; + CommonGlobals* globals = (CommonGlobals*)data; + LOG_FUNC(); + + if ( (condition & G_IO_IN) != 0 ) { + int listener = g_io_channel_unix_get_fd( source ); + XP_LOGF( "%s: input on socket %d", __func__, listener ); + keepSource = (*globals->acceptor)( listener, data ); + } else { + keepSource = FALSE; + } + + return keepSource; +} /* acceptorInput */ + +static void +gtk_socket_acceptor( int listener, Acceptor func, CommonGlobals* globals, + void** storage ) +{ + SockInfo* info = (SockInfo*)*storage; + GIOChannel* channel; + guint watch; + + LOG_FUNC(); + + if ( listener == -1 ) { + XP_ASSERT( !!globals->acceptor ); + globals->acceptor = NULL; + XP_ASSERT( !!info ); +#ifdef DEBUG + int oldSock = info->socket; +#endif + g_source_remove( info->watch ); + g_io_channel_unref( info->channel ); + XP_FREE( globals->params->util->mpool, info ); + *storage = NULL; + XP_LOGF( "Removed listener %d from gtk's list of listened-to sockets", oldSock ); + } else { + XP_ASSERT( !globals->acceptor || (func == globals->acceptor) ); + globals->acceptor = func; + + channel = g_io_channel_unix_new( listener ); + g_io_channel_set_close_on_unref( channel, TRUE ); + watch = g_io_add_watch( channel, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_PRI, + acceptorInput, globals ); + g_io_channel_unref( channel ); /* only main loop holds it now */ + XP_LOGF( "%s: g_io_add_watch(%d) => %d", __func__, listener, watch ); + + XP_ASSERT( NULL == info ); + info = XP_MALLOC( globals->params->util->mpool, sizeof(*info) ); + info->channel = channel; + info->watch = watch; + info->socket = listener; + *storage = info; + } +} /* gtk_socket_acceptor */ + +static void +sendOnClose( XWStreamCtxt* stream, void* closure ) +{ + XP_S16 result; + GtkAppGlobals* globals = closure; + + XP_LOGF( "sendOnClose called" ); + result = comms_send( globals->cGlobals.game.comms, stream ); +} /* sendOnClose */ + +static void +drop_msg_toggle( GtkWidget* toggle, GtkAppGlobals* globals ) +{ + globals->dropIncommingMsgs = gtk_toggle_button_get_active( + GTK_TOGGLE_BUTTON(toggle) ); +} /* drop_msg_toggle */ +#endif + +static GtkAppGlobals* g_globals_for_signal; +static void +handle_sigint( int XP_UNUSED(sig) ) +{ + quit( NULL, g_globals_for_signal ); +} + +int +gtkmain( LaunchParams* params, int argc, char *argv[] ) +{ + short width, height; + GtkWidget* window; + GtkWidget* drawing_area; + GtkWidget* menubar; + GtkWidget* buttonbar; + GtkWidget* vbox; + GtkWidget* hbox; + GtkAppGlobals globals; +#ifndef XWFEATURE_STANDALONE_ONLY + GtkWidget* dropCheck; +#endif + + g_globals_for_signal = &globals; + struct sigaction act = { .sa_handler = handle_sigint }; + sigaction( SIGINT, &act, NULL ); + + memset( &globals, 0, sizeof(globals) ); + + globals.cGlobals.params = params; + globals.cGlobals.lastNTilesToUse = MAX_TRAY_TILES; +#ifndef XWFEATURE_STANDALONE_ONLY +# ifdef XWFEATURE_RELAY + globals.cGlobals.socket = -1; +# endif + + globals.cGlobals.socketChanged = gtk_socket_changed; + globals.cGlobals.socketChangedClosure = &globals; + globals.cGlobals.addAcceptor = gtk_socket_acceptor; +#endif + + globals.cp.showBoardArrow = XP_TRUE; + globals.cp.showRobotScores = params->showRobotScores; + + setupGtkUtilCallbacks( &globals, params->util ); + + /* Now set up the gtk stuff. This is necessary before we make the + draw ctxt */ + gtk_init( &argc, &argv ); + + globals.window = window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); + if ( !!params->fileName ) { + gtk_window_set_title( GTK_WINDOW(window), params->fileName ); + } + + vbox = gtk_vbox_new (FALSE, 0); + gtk_container_add( GTK_CONTAINER(window), vbox ); + gtk_widget_show( vbox ); + + g_signal_connect( G_OBJECT (window), "destroy", + G_CALLBACK( quit ), &globals ); + + menubar = makeMenus( &globals, argc, argv ); + gtk_box_pack_start( GTK_BOX(vbox), menubar, FALSE, TRUE, 0); + +#ifndef XWFEATURE_STANDALONE_ONLY + dropCheck = gtk_check_button_new_with_label( "drop incoming messages" ); + g_signal_connect( GTK_OBJECT(dropCheck), + "toggled", G_CALLBACK(drop_msg_toggle), &globals ); + gtk_box_pack_start( GTK_BOX(vbox), dropCheck, FALSE, TRUE, 0); + gtk_widget_show( dropCheck ); +#endif + + buttonbar = makeButtons( &globals, argc, argv ); + gtk_box_pack_start( GTK_BOX(vbox), buttonbar, FALSE, TRUE, 0); + + drawing_area = gtk_drawing_area_new(); + globals.drawing_area = drawing_area; + gtk_widget_show( drawing_area ); + + width = GTK_HOR_SCORE_WIDTH + GTK_TIMER_WIDTH + GTK_TIMER_PAD; + if ( globals.cGlobals.params->verticalScore ) { + width += GTK_VERT_SCORE_WIDTH; + } + height = 196; + if ( globals.cGlobals.params->nHidden == 0 ) { + height += GTK_MIN_SCALE * GTK_TRAY_HT_ROWS; + } + + gtk_widget_set_size_request( GTK_WIDGET(drawing_area), width, height ); + + hbox = gtk_hbox_new( FALSE, 0 ); + gtk_box_pack_start( GTK_BOX (hbox), drawing_area, TRUE, TRUE, 0); + + if ( globals.cGlobals.params->nHidden != 0 ) { + GtkWidget* vscrollbar; + globals.adjustment = + (GtkAdjustment*)gtk_adjustment_new( 0, 0, GTK_NUM_ROWS, 1, 2, + GTK_NUM_ROWS-globals.cGlobals.params->nHidden ); + vscrollbar = gtk_vscrollbar_new( globals.adjustment ); + g_signal_connect( GTK_OBJECT(globals.adjustment), "value_changed", + G_CALLBACK(scroll_value_changed), &globals ); + + gtk_widget_show( vscrollbar ); + gtk_box_pack_start( GTK_BOX(hbox), vscrollbar, TRUE, TRUE, 0 ); + } + + gtk_box_pack_start( GTK_BOX (hbox), + makeVerticalBar( &globals, window ), + FALSE, TRUE, 0 ); + gtk_widget_show( hbox ); + + gtk_box_pack_start( GTK_BOX(vbox), hbox/* drawing_area */, TRUE, TRUE, 0); + + g_signal_connect( GTK_OBJECT(drawing_area), "expose_event", + G_CALLBACK(expose_event), &globals ); + g_signal_connect( GTK_OBJECT(drawing_area),"configure_event", + G_CALLBACK(configure_event), &globals ); + g_signal_connect( GTK_OBJECT(drawing_area), "button_press_event", + G_CALLBACK(button_press_event), &globals ); + g_signal_connect( GTK_OBJECT(drawing_area), "motion_notify_event", + G_CALLBACK(motion_notify_event), &globals ); + g_signal_connect( GTK_OBJECT(drawing_area), "button_release_event", + G_CALLBACK(button_release_event), &globals ); + +#ifdef KEY_SUPPORT +# ifdef KEYBOARD_NAV + g_signal_connect( GTK_OBJECT(window), "key_press_event", + G_CALLBACK(key_press_event), &globals ); +# endif + g_signal_connect( GTK_OBJECT(window), "key_release_event", + G_CALLBACK(key_release_event), &globals ); +#endif + + gtk_widget_set_events( drawing_area, GDK_EXPOSURE_MASK + | GDK_LEAVE_NOTIFY_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_POINTER_MOTION_MASK + | GDK_BUTTON_RELEASE_MASK +#ifdef KEY_SUPPORT +# ifdef KEYBOARD_NAV + | GDK_KEY_PRESS_MASK +# endif + | GDK_KEY_RELEASE_MASK +#endif +/* | GDK_POINTER_MOTION_HINT_MASK */ + ); + + gtk_widget_show( window ); + + gtk_main(); + +/* MONCONTROL(1); */ + + return 0; +} /* gtkmain */ + +#endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkmain.h b/xwords4/linux/gtkmain.h new file mode 100644 index 000000000..ff47c1cb5 --- /dev/null +++ b/xwords4/linux/gtkmain.h @@ -0,0 +1,146 @@ +/* -*- mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* Copyright 1997 - 2005 by Eric House (xwords@eehouse.org) (fixin@peak.org). All rights reserved. + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _GTKMAIN_H_ +#define _GTKMAIN_H_ + +#ifdef PLATFORM_GTK +#include +#include +#include +#include + +#include "draw.h" +#include "main.h" +#include "game.h" +#include "dictnry.h" + +enum { + LAYOUT_BOARD + ,LAYOUT_SMALL + ,LAYOUT_LARGE + ,LAYOUT_NLAYOUTS +}; + +typedef struct GtkDrawCtx { + DrawCtxVTable* vtable; + +/* GdkDrawable* pixmap; */ + GtkWidget* drawing_area; + struct GtkAppGlobals* globals; + + GdkGC* drawGC; + + GdkColor black; + GdkColor white; + GdkColor red; /* for pending tiles */ + GdkColor tileBack; /* for pending tiles */ + GdkColor cursor; + GdkColor bonusColors[4]; + GdkColor playerColors[MAX_NUM_PLAYERS]; + + /* new for gtk 2.0 */ + PangoContext* pangoContext; + GList* fontsPerSize; + + XP_U16 trayOwner; + XP_Bool topFocus; +} GtkDrawCtx; + +typedef struct ClientStreamRec { + XWStreamCtxt* stream; + guint key; + int sock; +} ClientStreamRec; + +typedef struct GtkAppGlobals { + CommonGlobals cGlobals; + GtkWidget* window; + GtkDrawCtx* draw; + +/* GdkPixmap* pixmap; */ + GtkWidget* drawing_area; + + EngineCtxt* engine; + + guint idleID; + + struct timeval penTv; /* for timer */ + XP_U32 penTimerInterval; + struct timeval scoreTv; /* for timer */ + XP_U32 scoreTimerInterval; + + GtkAdjustment* adjustment; + + ClientStreamRec clientRecs[MAX_NUM_PLAYERS]; + + guint timerSources[NUM_TIMERS_PLUS_ONE - 1]; + + CommonPrefs cp; + + XP_Bool gridOn; + XP_Bool dropIncommingMsgs; + XP_Bool mouseDown; + XP_Bool altKeyDown; +#ifdef KEYBOARD_NAV + XP_Bool keyDown; +#endif +} GtkAppGlobals; + +/* DictionaryCtxt* gtk_dictionary_make(); */ +int gtkmain( LaunchParams* params, int argc, char *argv[] ); + +#define GTK_NUM_COLS 15 +#define GTK_NUM_ROWS 15 +#define GTK_MIN_SCALE 12 /* was 14 */ + +#define GTK_MIN_TRAY_SCALEH 24 +#define GTK_MIN_TRAY_SCALEV GTK_MIN_TRAY_SCALEH +#define GTK_TRAYPAD_WIDTH 2 + +#define GTK_TOP_MARGIN 0 /* was 2 */ +#define GTK_BOARD_LEFT_MARGIN 2 +#define GTK_TRAY_LEFT_MARGIN 2 +#define GTK_SCORE_BOARD_PADDING 0 + +#define GTK_HOR_SCORE_LEFT (GTK_BOARD_LEFT_MARGIN) +#define GTK_HOR_SCORE_HEIGHT 12 +#define GTK_TIMER_HEIGHT GTK_HOR_SCORE_HEIGHT +#define GTK_HOR_SCORE_TOP (GTK_TOP_MARGIN) +#define GTK_TIMER_PAD 10 +#define GTK_VERT_SCORE_TOP (GTK_TIMER_HEIGHT + GTK_TIMER_PAD) +#define GTK_VERT_SCORE_HEIGHT ((MIN_SCALE*MAX_COLS) - GTK_TIMER_HEIGHT - \ + GTK_TIMER_PAD) +#define GTK_TIMER_WIDTH 40 +#define GTK_TIMER_TOP GTK_HOR_SCORE_TOP +#define GTK_HOR_SCORE_WIDTH ((GTK_MIN_SCALE*MAX_COLS)-GTK_TIMER_PAD) +#define GTK_VERT_SCORE_WIDTH 40 + +#define GTK_BOARD_TOP (GTK_SCORE_TOP + GTK_SCORE_HEIGHT \ + + GTK_SCORE_BOARD_PADDING ) +#define GTK_BOARD_LEFT (GTK_BOARD_LEFT_MARGIN) + +#define GTK_TRAY_LEFT GTK_TRAY_LEFT_MARGIN + +#define GTK_DIVIDER_WIDTH 5 + +#define GTK_BOTTOM_MARGIN GTK_TOP_MARGIN +#define GTK_RIGHT_MARGIN GTK_BOARD_LEFT_MARGIN + +#endif /* PLATFORM_GTK */ + +#endif diff --git a/xwords4/linux/gtknewgame.c b/xwords4/linux/gtknewgame.c new file mode 100644 index 000000000..c1f8f8587 --- /dev/null +++ b/xwords4/linux/gtknewgame.c @@ -0,0 +1,529 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE"; -*- */ +/* + * Copyright 2001-2008 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifdef PLATFORM_GTK + +#include + +#include "linuxutl.h" +#include "gtknewgame.h" +#include "strutils.h" +#include "nwgamest.h" + +#define MAX_SIZE_CHOICES 10 + +typedef struct GtkNewGameState { + GtkAppGlobals* globals; + NewGameCtx* newGameCtxt; + + gboolean revert; + gboolean cancelled; + short nCols; /* for board size */ + +#ifndef XWFEATURE_STANDALONE_ONLY + GtkWidget* remoteChecks[MAX_NUM_PLAYERS]; + GtkWidget* roleCombo; +#endif + GtkWidget* robotChecks[MAX_NUM_PLAYERS]; + GtkWidget* nameLabels[MAX_NUM_PLAYERS]; + GtkWidget* nameFields[MAX_NUM_PLAYERS]; + GtkWidget* passwdLabels[MAX_NUM_PLAYERS]; + GtkWidget* passwdFields[MAX_NUM_PLAYERS]; + GtkWidget* nPlayersCombo; + GtkWidget* nPlayersLabel; + GtkWidget* juggleButton; +} GtkNewGameState; + +static void +nplayers_menu_changed( GtkComboBox* combo, GtkNewGameState* state ) +{ + gint index = gtk_combo_box_get_active( GTK_COMBO_BOX(combo) ); + if ( index >= 0 ) { + NGValue value = { .ng_u16 = index + 1 }; + newg_attrChanged( state->newGameCtxt, NG_ATTR_NPLAYERS, value ); + } +} /* nplayers_menu_changed */ + +#ifndef XWFEATURE_STANDALONE_ONLY +static void +role_combo_changed( GtkComboBox* combo, gpointer gp ) +{ + NGValue value; + GtkNewGameState* state = (GtkNewGameState*)gp; + gint index = gtk_combo_box_get_active( GTK_COMBO_BOX(combo) ); + if ( index >= 0 ) { + value.ng_role = (DeviceRole)index; + newg_attrChanged( state->newGameCtxt, NG_ATTR_ROLE, value ); + } +} /* role_combo_changed */ +#endif + +static void +callChangedWithIndex( GtkNewGameState* state, GtkWidget* item, + GtkWidget** items ) +{ + NGValue value; + gint player; + for ( player = 0; player < MAX_NUM_PLAYERS; ++player ) { + if ( item == items[player] ) { + break; + } + } + XP_ASSERT( player < MAX_NUM_PLAYERS ); + + value.ng_bool = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(item) ); + newg_colChanged( state->newGameCtxt, player ); +} /* callChangedWithIndex */ + +static void +handle_robot_toggled( GtkWidget* item, GtkNewGameState* state ) +{ + callChangedWithIndex( state, item, state->robotChecks ); +} + +static void +handle_juggle( GtkWidget* XP_UNUSED(item), GtkNewGameState* state ) +{ + while ( !newg_juggle( state->newGameCtxt ) ) { + } +} + +#ifndef XWFEATURE_STANDALONE_ONLY +static void +handle_remote_toggled( GtkWidget* item, GtkNewGameState* state ) +{ + callChangedWithIndex( state, item, state->remoteChecks ); +} +#endif + +static void +size_combo_changed( GtkComboBox* combo, gpointer gp ) +{ + GtkNewGameState* state = (GtkNewGameState*)gp; + gint index = gtk_combo_box_get_active( GTK_COMBO_BOX(combo) ); + if ( index >= 0 ) { + state->nCols = MAX_COLS - index; + XP_LOGF( "set nCols = %d", state->nCols ); + } +} /* size_combo_changed */ + +static void +handle_ok( GtkWidget* XP_UNUSED(widget), gpointer closure ) +{ + GtkNewGameState* state = (GtkNewGameState*)closure; + state->cancelled = XP_FALSE; + + gtk_main_quit(); +} /* handle_ok */ + +static void +handle_cancel( GtkWidget* XP_UNUSED(widget), void* closure ) +{ + GtkNewGameState* state = (GtkNewGameState*)closure; + state->cancelled = XP_TRUE; + gtk_main_quit(); +} + +static void +handle_revert( GtkWidget* XP_UNUSED(widget), void* closure ) +{ + GtkNewGameState* state = (GtkNewGameState*)closure; + state->revert = TRUE; + gtk_main_quit(); +} /* handle_revert */ + +static GtkWidget* +makeButton( char* text, GCallback func, gpointer data ) +{ + GtkWidget* button = gtk_button_new_with_label( text ); + g_signal_connect( GTK_OBJECT(button), "clicked", func, data ); + gtk_widget_show( button ); + + return button; +} /* makeButton */ + +static GtkWidget* +makeNewGameDialog( GtkNewGameState* state, XP_Bool isNewGame ) +{ + GtkWidget* dialog; + GtkWidget* vbox; + GtkWidget* hbox; +#ifndef XWFEATURE_STANDALONE_ONLY + GtkWidget* roleCombo; + char* roles[] = { "Standalone", "Host", "Guest" }; +#endif + GtkWidget* nPlayersCombo; + GtkWidget* boardSizeCombo; + CurGameInfo* gi; + short i; + + dialog = gtk_dialog_new(); + gtk_window_set_modal( GTK_WINDOW( dialog ), TRUE ); + + vbox = gtk_vbox_new( FALSE, 0 ); + +#ifndef XWFEATURE_STANDALONE_ONLY + hbox = gtk_hbox_new( FALSE, 0 ); + gtk_box_pack_start( GTK_BOX(hbox), gtk_label_new("Role:"), + FALSE, TRUE, 0 ); + roleCombo = gtk_combo_box_new_text(); + state->roleCombo = roleCombo; + + for ( i = 0; i < VSIZE(roles); ++i ) { + gtk_combo_box_append_text( GTK_COMBO_BOX(roleCombo), roles[i] ); + } + g_signal_connect( GTK_OBJECT(roleCombo), "changed", + G_CALLBACK(role_combo_changed), state ); + + gtk_box_pack_start( GTK_BOX(hbox), roleCombo, FALSE, TRUE, 0 ); + gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); +#endif + + /* NPlayers menu */ + hbox = gtk_hbox_new( FALSE, 0 ); + state->nPlayersLabel = gtk_label_new(""); + gtk_box_pack_start( GTK_BOX(hbox), state->nPlayersLabel, FALSE, TRUE, 0 ); + + nPlayersCombo = gtk_combo_box_new_text(); + state->nPlayersCombo = nPlayersCombo; + + gi = &state->globals->cGlobals.params->gi; + + for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { + char buf[2] = { i + '1', '\0' }; + gtk_combo_box_append_text( GTK_COMBO_BOX(nPlayersCombo), buf ); + } + + gtk_widget_show( nPlayersCombo ); + gtk_box_pack_start( GTK_BOX(hbox), nPlayersCombo, FALSE, TRUE, 0 ); + g_signal_connect( GTK_OBJECT(nPlayersCombo), "changed", + G_CALLBACK(nplayers_menu_changed), state ); + + state->juggleButton = makeButton( "Juggle", + GTK_SIGNAL_FUNC(handle_juggle), + state ); + gtk_box_pack_start( GTK_BOX(hbox), state->juggleButton, FALSE, TRUE, 0 ); + gtk_widget_show( hbox ); + + gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); + + for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { + GtkWidget* label = gtk_label_new("Name:"); +#ifndef XWFEATURE_STANDALONE_ONLY + GtkWidget* remoteCheck = gtk_check_button_new_with_label( "Remote" ); +#endif + GtkWidget* nameField = gtk_entry_new(); + GtkWidget* passwdField = gtk_entry_new_with_max_length( 6 ); + GtkWidget* robotCheck = gtk_check_button_new_with_label( "Robot" ); + +#ifndef XWFEATURE_STANDALONE_ONLY + g_signal_connect( GTK_OBJECT(remoteCheck), "toggled", + GTK_SIGNAL_FUNC(handle_remote_toggled), state ); +#endif + g_signal_connect( GTK_OBJECT(robotCheck), "toggled", + GTK_SIGNAL_FUNC(handle_robot_toggled), state ); + + hbox = gtk_hbox_new( FALSE, 0 ); + +#ifndef XWFEATURE_STANDALONE_ONLY + gtk_box_pack_start( GTK_BOX(hbox), remoteCheck, FALSE, TRUE, 0 ); + gtk_widget_show( remoteCheck ); + state->remoteChecks[i] = remoteCheck; +#endif + + gtk_box_pack_start( GTK_BOX(hbox), label, FALSE, TRUE, 0 ); + gtk_widget_show( label ); + state->nameLabels[i] = label; + + gtk_box_pack_start( GTK_BOX(hbox), nameField, FALSE, TRUE, 0 ); + gtk_widget_show( nameField ); + state->nameFields[i] = nameField; + + gtk_box_pack_start( GTK_BOX(hbox), robotCheck, FALSE, TRUE, 0 ); + gtk_widget_show( robotCheck ); + state->robotChecks[i] = robotCheck; + + label = gtk_label_new("Passwd:"); + gtk_box_pack_start( GTK_BOX(hbox), label, FALSE, TRUE, 0 ); + gtk_widget_show( label ); + state->passwdLabels[i] = label; + + gtk_box_pack_start( GTK_BOX(hbox), passwdField, FALSE, TRUE, 0 ); + gtk_widget_show( passwdField ); + state->passwdFields[i] = passwdField; + + gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); + gtk_widget_show( hbox ); + } + + /* board size choices */ + hbox = gtk_hbox_new( FALSE, 0 ); + gtk_box_pack_start( GTK_BOX(hbox), gtk_label_new("Board size"), + FALSE, TRUE, 0 ); + + boardSizeCombo = gtk_combo_box_new_text(); + if ( !isNewGame ) { + gtk_widget_set_sensitive( boardSizeCombo, FALSE ); + } + + for ( i = 0; i < MAX_SIZE_CHOICES; ++i ) { + char buf[10]; + XP_U16 siz = MAX_COLS - i; + snprintf( buf, sizeof(buf), "%dx%d", siz, siz ); + gtk_combo_box_append_text( GTK_COMBO_BOX(boardSizeCombo), buf ); + if ( siz == state->nCols ) { + gtk_combo_box_set_active( GTK_COMBO_BOX(boardSizeCombo), i ); + } + } + + g_signal_connect( GTK_OBJECT(boardSizeCombo), "changed", + G_CALLBACK(size_combo_changed), state ); + + gtk_widget_show( boardSizeCombo ); + gtk_box_pack_start( GTK_BOX(hbox), boardSizeCombo, FALSE, TRUE, 0 ); + + gtk_box_pack_start( GTK_BOX(hbox), gtk_label_new("Dictionary: "), + FALSE, TRUE, 0 ); + + XP_ASSERT( gi->dictName ); + gtk_box_pack_start( GTK_BOX(hbox), + gtk_label_new(gi->dictName), + FALSE, TRUE, 0 ); + + gtk_widget_show( hbox ); + + gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); + + /* buttons at the bottom */ + hbox = gtk_hbox_new( FALSE, 0 ); + gtk_box_pack_start( GTK_BOX(hbox), + makeButton( "Ok", GTK_SIGNAL_FUNC(handle_ok) , state ), + FALSE, TRUE, 0 ); + gtk_box_pack_start( GTK_BOX(hbox), + makeButton( "Revert", GTK_SIGNAL_FUNC(handle_revert), + state ), + FALSE, TRUE, 0 ); + gtk_box_pack_start( GTK_BOX(hbox), + makeButton( "Cancel", GTK_SIGNAL_FUNC(handle_cancel), + state ), + FALSE, TRUE, 0 ); + gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); + + + gtk_widget_show( vbox ); + gtk_container_add( GTK_CONTAINER( GTK_DIALOG(dialog)->action_area), vbox); + + gtk_widget_show_all (dialog); + + return dialog; +} /* makeNewGameDialog */ + +static GtkWidget* +widgetForCol( const GtkNewGameState* state, XP_U16 player, NewGameColumn col ) +{ + GtkWidget* widget = NULL; + if ( col == NG_COL_NAME ) { + widget = state->nameFields[player]; + } else if ( col == NG_COL_PASSWD ) { + widget = state->passwdFields[player]; +#ifndef XWFEATURE_STANDALONE_ONLY + } else if ( col == NG_COL_REMOTE ) { + widget = state->remoteChecks[player]; +#endif + } else if ( col == NG_COL_ROBOT ) { + widget = state->robotChecks[player]; + } + XP_ASSERT( !!widget ); + return widget; +} /* widgetForCol */ + +static GtkWidget* +labelForCol( const GtkNewGameState* state, XP_U16 player, NewGameColumn col ) +{ + GtkWidget* widget = NULL; + if ( col == NG_COL_NAME ) { + widget = state->nameLabels[player]; + } else if ( col == NG_COL_PASSWD ) { + widget = state->passwdLabels[player]; + } + return widget; +} /* widgetForCol */ + +static void +gtk_newgame_col_enable( void* closure, XP_U16 player, NewGameColumn col, + XP_TriEnable enable ) +{ + GtkNewGameState* state = (GtkNewGameState*)closure; + GtkWidget* widget = widgetForCol( state, player, col ); + GtkWidget* label = labelForCol( state, player, col ); + + if ( enable == TRI_ENAB_HIDDEN ) { + gtk_widget_hide( widget ); + if ( !!label ) { + gtk_widget_hide( label ); + } + } else { + gtk_widget_show( widget ); + gtk_widget_set_sensitive( widget, enable == TRI_ENAB_ENABLED ); + if ( !!label ) { + gtk_widget_show( label ); + gtk_widget_set_sensitive( label, enable == TRI_ENAB_ENABLED ); + } + } +} /* gtk_newgame_col_enable */ + +static void +gtk_newgame_attr_enable( void* closure, NewGameAttr attr, XP_TriEnable enable ) +{ + GtkNewGameState* state = (GtkNewGameState*)closure; + GtkWidget* widget = NULL; + if ( attr == NG_ATTR_NPLAYERS ) { + widget = state->nPlayersCombo; +#ifndef XWFEATURE_STANDALONE_ONLY + } else if ( attr == NG_ATTR_ROLE ) { + widget = state->roleCombo; +#endif + } else if ( attr == NG_ATTR_CANJUGGLE ) { + widget = state->juggleButton; + } + + if ( !!widget ) { + gtk_widget_set_sensitive( widget, enable == TRI_ENAB_ENABLED ); + } +} + +static void +gtk_newgame_col_set( void* closure, XP_U16 player, NewGameColumn col, + NGValue value ) +{ + GtkNewGameState* state = (GtkNewGameState*)closure; + GtkWidget* widget = widgetForCol( state, player, col ); + const XP_UCHAR* cp; + + switch ( col ) { + case NG_COL_NAME: + case NG_COL_PASSWD: + cp = value.ng_cp? value.ng_cp : ""; + gtk_entry_set_text( GTK_ENTRY(widget), cp ); + break; +#ifndef XWFEATURE_STANDALONE_ONLY + case NG_COL_REMOTE: +#endif + case NG_COL_ROBOT: + gtk_toggle_button_set_state( GTK_TOGGLE_BUTTON(widget), + value.ng_bool ); + break; + } +} /* gtk_newgame_set */ + +static void +gtk_newgame_col_get( void* closure, XP_U16 player, NewGameColumn col, + NgCpCallbk cpcb, const void* cbClosure ) +{ + GtkNewGameState* state = (GtkNewGameState*)closure; + NGValue value; + + GtkWidget* widget = widgetForCol( state, player, col ); + switch ( col ) { +#ifndef XWFEATURE_STANDALONE_ONLY + case NG_COL_REMOTE: +#endif + case NG_COL_ROBOT: + value.ng_bool = + gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(widget)); + break; + case NG_COL_PASSWD: + case NG_COL_NAME: + value.ng_cp = gtk_entry_get_text( GTK_ENTRY(widget) ); + break; + } + + (*cpcb)( value, cbClosure ); +} /* gtk_newgame_col_get */ + +static void +gtk_newgame_attr_set( void* closure, NewGameAttr attr, NGValue value ) +{ + GtkNewGameState* state = (GtkNewGameState*)closure; + if ( attr == NG_ATTR_NPLAYERS ) { + XP_U16 i = value.ng_u16; + XP_LOGF( "%s: setting menu %d", __func__, i-1 ); + gtk_combo_box_set_active( GTK_COMBO_BOX(state->nPlayersCombo), i-1 ); +#ifndef XWFEATURE_STANDALONE_ONLY + } else if ( attr == NG_ATTR_ROLE ) { + gtk_combo_box_set_active( GTK_COMBO_BOX(state->roleCombo), + value.ng_role ); + } else if ( attr == NG_ATTR_REMHEADER ) { + /* ignored on GTK: no headers at all */ +#endif + } else if ( attr == NG_ATTR_NPLAYHEADER ) { + gtk_label_set_text( GTK_LABEL(state->nPlayersLabel), value.ng_cp ); + } +} + +gboolean +newGameDialog( GtkAppGlobals* globals, XP_Bool isNewGame ) +{ + GtkNewGameState state; + XP_MEMSET( &state, 0, sizeof(state) ); + + state.globals = globals; + state.newGameCtxt = newg_make( MPPARM(globals->cGlobals.params + ->util->mpool) + isNewGame, + globals->cGlobals.params->util, + gtk_newgame_col_enable, + gtk_newgame_attr_enable, + gtk_newgame_col_get, + gtk_newgame_col_set, + gtk_newgame_attr_set, + &state ); + + /* returns when button handler calls gtk_main_quit */ + do { + GtkWidget* dialog; + + state.revert = FALSE; + state.nCols = globals->cGlobals.params->gi.boardSize; + + dialog = makeNewGameDialog( &state, isNewGame ); + + newg_load( state.newGameCtxt, + &globals->cGlobals.params->gi ); + + gtk_main(); + if ( !state.cancelled && !state.revert ) { + if ( newg_store( state.newGameCtxt, &globals->cGlobals.params->gi, + XP_TRUE ) ) { + globals->cGlobals.params->gi.boardSize = state.nCols; + } else { + /* Do it again if we warned user of inconsistency. */ + state.revert = XP_TRUE; + } + } + + gtk_widget_destroy( dialog ); + } while ( state.revert ); + + newg_destroy( state.newGameCtxt ); + + return !state.cancelled; +} /* newGameDialog */ + +#endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtknewgame.h b/xwords4/linux/gtknewgame.h new file mode 100644 index 000000000..0969b755f --- /dev/null +++ b/xwords4/linux/gtknewgame.h @@ -0,0 +1,31 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifdef PLATFORM_GTK + +#ifndef _GTKNEWGAME_H_ +#define _GTKNEWGAME_H_ + +#include "gtkmain.h" + +gboolean newGameDialog( GtkAppGlobals* globals, XP_Bool isNewGame ); + +#endif /* _GTKNEWGAME_H_ */ +#endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkntilesask.c b/xwords4/linux/gtkntilesask.c new file mode 100644 index 000000000..763982182 --- /dev/null +++ b/xwords4/linux/gtkntilesask.c @@ -0,0 +1,96 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2003 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef PLATFORM_GTK + +#include "gtkntilesask.h" + +static void +button_event( GtkWidget* XP_UNUSED(widget), void* closure ) +{ + XP_Bool* result = (XP_Bool*)closure; + *result = XP_TRUE; + gtk_main_quit(); +} /* button_event */ + +XP_U16 +askNTiles( XP_U16 max, XP_U16 deflt ) +{ + XP_U16 result = 0; + XP_Bool results[MAX_TRAY_TILES]; + GtkWidget* dialog; + GtkWidget* label; + GtkWidget* hbox = NULL; + XP_U16 i; + GtkWidget* button; + XP_UCHAR defbuf[48]; + + XP_MEMSET( results, 0, sizeof(results) ); + + dialog = gtk_dialog_new(); + gtk_window_set_modal( GTK_WINDOW( dialog ), TRUE ); + + sprintf( defbuf, "Limit hint to how many tiles (deflt=%d)?", deflt ); + label = gtk_label_new( defbuf ); + gtk_container_add (GTK_CONTAINER (GTK_DIALOG(dialog)->vbox), + label); + + hbox = gtk_hbox_new( FALSE, 0 ); + for ( i = 0; i < max; ++i ) { + XP_UCHAR buf[3]; + + sprintf( buf, "%d", i+1 ); + button = gtk_button_new_with_label( buf ); + + gtk_box_pack_start( GTK_BOX(hbox), button, FALSE, TRUE, 0 ); + g_signal_connect( GTK_OBJECT(button), "clicked", + G_CALLBACK(button_event), + &results[i] ); + gtk_widget_show( button ); + } + gtk_widget_show( hbox ); + gtk_box_pack_start( GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, + FALSE, TRUE, 0 ); + + sprintf( defbuf, "Default (%d)", deflt ); + button = gtk_button_new_with_label( defbuf ); + gtk_box_pack_start( GTK_BOX(GTK_DIALOG(dialog)->vbox), button, FALSE, + TRUE, 0 ); + g_signal_connect( GTK_OBJECT(button), "clicked", + G_CALLBACK(button_event), + &results[deflt-1] ); + gtk_widget_show( button ); + + gtk_widget_show_all( dialog ); + + gtk_main(); + + gtk_widget_destroy( dialog ); + + for ( i = 0; i < MAX_TRAY_TILES; ++i ) { + if ( results[i] ) { + result = i + 1; + break; + } + } + + return result; +} /* askNTiles */ + +#endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkntilesask.h b/xwords4/linux/gtkntilesask.h new file mode 100644 index 000000000..c4c1548a3 --- /dev/null +++ b/xwords4/linux/gtkntilesask.h @@ -0,0 +1,31 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2003 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + + + +#ifndef _GTKNTILESASK_H_ +#define _GTKNTILESASK_H_ + +#include "gtkmain.h" + +XP_U16 askNTiles( XP_U16 nTilesMax, XP_U16 deflt ); + + +#endif diff --git a/xwords4/linux/gtkpasswdask.c b/xwords4/linux/gtkpasswdask.c new file mode 100644 index 000000000..3dd81af44 --- /dev/null +++ b/xwords4/linux/gtkpasswdask.c @@ -0,0 +1,95 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifdef PLATFORM_GTK + +#include "gtkpasswdask.h" +#include + +static void +button_event( GtkWidget* XP_UNUSED(widget), void* closure ) +{ + XP_Bool* okptr = (XP_Bool*)closure; + *okptr = XP_TRUE; + gtk_main_quit(); +} /* ok_button_event */ + +XP_Bool +gtkpasswdask( const char* name, char* outbuf, XP_U16* buflen ) +{ + XP_Bool ok = XP_FALSE; + XP_Bool ignore; + char buf[64]; + short i; + + GtkWidget* entry; + GtkWidget* vbox; + GtkWidget* hbox; + GtkWidget* dialog; + GtkWidget* label; + + char* labels[] = { "Ok", "Cancel" }; + XP_Bool* boolps[] = { &ok, &ignore }; + + dialog = gtk_dialog_new(); + gtk_window_set_modal( GTK_WINDOW( dialog ), TRUE ); + + snprintf( buf, sizeof(buf), "Password for player \"%s\"", name ); + label = gtk_label_new( buf ); + + gtk_container_add( GTK_CONTAINER (GTK_DIALOG(dialog)->vbox), + label ); + + /* we need a text field and two buttons as well */ + vbox = gtk_vbox_new(FALSE, 0); + + entry = gtk_entry_new(); + gtk_widget_show( entry ); + gtk_box_pack_start( GTK_BOX(vbox), entry, FALSE, TRUE, 0 ); + + hbox = gtk_hbox_new(FALSE, 0); + + for ( i = 0; i < 2; ++i ) { + GtkWidget* button = gtk_button_new_with_label( labels[i] ); + g_signal_connect( GTK_OBJECT(button), "clicked", + G_CALLBACK(button_event), + boolps[i] ); + gtk_box_pack_start( GTK_BOX(hbox), button, FALSE, TRUE, 0 ); + gtk_widget_show( button ); + } + + gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); + + gtk_container_add( GTK_CONTAINER( GTK_DIALOG(dialog)->action_area), vbox); + + gtk_widget_show_all( dialog ); + + gtk_main(); + + if ( ok ) { + const char* text = gtk_entry_get_text( GTK_ENTRY(entry) ); + strncpy( outbuf, text, *buflen ); + *buflen = strlen(outbuf); + } + + gtk_widget_destroy( dialog ); + + return ok; +} /* gtkpasswdask */ + +#endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkpasswdask.h b/xwords4/linux/gtkpasswdask.h new file mode 100644 index 000000000..92c4aef2f --- /dev/null +++ b/xwords4/linux/gtkpasswdask.h @@ -0,0 +1,31 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef PLATFORM_GTK + +#ifndef _GTKPASSWDASK_H_ +#define _GTKPASSWDASK_H_ + +#include "comtypes.h" + +XP_Bool gtkpasswdask( const char* name, char* buf, XP_U16* len ); + +#endif /* _GTKPASSWDASK_H_ */ + +#endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/hint.xpm b/xwords4/linux/hint.xpm new file mode 100644 index 000000000..b9be7a036 --- /dev/null +++ b/xwords4/linux/hint.xpm @@ -0,0 +1,17 @@ +/* XPM */ +static char * hint_xpm[] = { +"8 11 3 1", +" c None", +". c #FFFFFF", +"+ c #000000", +".++++++.", +"+++..+++", +"++.++.++", +"+.++++.+", +"+.++++.+", +"++.++.++", +"++.++.++", +"+++..+++", +"+++..+++", +"+++..+++", +".++++++."}; diff --git a/xwords4/linux/juggle.xpm b/xwords4/linux/juggle.xpm new file mode 100644 index 000000000..edc11e718 --- /dev/null +++ b/xwords4/linux/juggle.xpm @@ -0,0 +1,14 @@ +/* XPM */ +static char * juggle_xpm[] = { +"8 8 3 1", +" c None", +". c #000000", +"+ c #FFFFFF", +"........", +"...+++..", +"....+...", +"....+...", +"....+...", +"..+.+...", +"...+....", +"........"}; diff --git a/xwords4/linux/linuxbt.c b/xwords4/linux/linuxbt.c new file mode 100644 index 000000000..232c6e822 --- /dev/null +++ b/xwords4/linux/linuxbt.c @@ -0,0 +1,518 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE";-*- */ +/* + * Copyright 2006-2007 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef XWFEATURE_BLUETOOTH + +/* + http://www.btessentials.com/examples/examples.html is good for some of this + stuff. Copyright allows free use. +*/ + +#include +#include +#include +#include +#include +#if defined BT_USE_L2CAP +# include +#elif defined BT_USE_RFCOMM +# include +#endif +#include +#include + +#include "linuxbt.h" +#include "comms.h" +#include "strutils.h" + +#define MAX_CLIENTS 1 + +#if defined BT_USE_L2CAP +# define L2_RF_ADDR struct sockaddr_l2 +#elif defined BT_USE_RFCOMM +# define L2_RF_ADDR struct sockaddr_rc +#endif + +typedef struct LinBtStuff { + CommonGlobals* globals; + void* sockStorage; + union { + struct { + int listener; /* socket */ + void* listenerStorage; + XP_Bool threadDie; + sdp_session_t* session; + } master; + } u; + + /* A single socket's fine as long as there's only one client allowed. */ + int socket; + XP_Bool amMaster; +} LinBtStuff; + +static LinBtStuff* +lbt_make( MPFORMAL XP_Bool amMaster ) +{ + LinBtStuff* btStuff = (LinBtStuff*)XP_MALLOC( mpool, sizeof(*btStuff) ); + XP_MEMSET( btStuff, 0, sizeof(*btStuff) ); + + btStuff->amMaster = amMaster; + btStuff->socket = -1; + + return btStuff; +} /* lbt_make */ + +static L2_RF_ADDR* +getL2Addr( const CommsAddrRec const* addrP, L2_RF_ADDR* const saddr ) +{ + LOG_FUNC(); + L2_RF_ADDR* result = NULL; + + uint8_t svc_uuid_int[] = XW_BT_UUID; + + int status; + bdaddr_t target; + uuid_t svc_uuid; + sdp_list_t *response_list, *search_list, *attrid_list; + sdp_session_t *session = 0; + uint32_t range = 0x0000ffff; + + XP_MEMCPY( &target, &addrP->u.bt.btAddr, sizeof(target) ); + + // connect to the SDP server running on the remote machine + session = sdp_connect( BDADDR_ANY, &target, 0 ); + if ( NULL == session ) { + XP_LOGF( "%s: sdp_connect->%s", __func__, strerror(errno) ); + } else { + sdp_uuid128_create( &svc_uuid, &svc_uuid_int ); + search_list = sdp_list_append( 0, &svc_uuid ); + attrid_list = sdp_list_append( 0, &range ); + + response_list = NULL; + status = sdp_service_search_attr_req( session, search_list, + SDP_ATTR_REQ_RANGE, attrid_list, + &response_list ); + + if( status == 0 ) { + sdp_list_t *r; + + // go through each of the service records + for ( r = response_list; r && !result; r = r->next ) { + sdp_list_t *proto_list = NULL; + sdp_record_t *rec = (sdp_record_t*) r->data; + + // get a list of the protocol sequences + if( sdp_get_access_protos( rec, &proto_list ) == 0 ) { +#if defined BT_USE_L2CAP + unsigned short psm = sdp_get_proto_port( proto_list, + L2CAP_UUID ); + if ( psm > 0 ) { + sdp_list_free( proto_list, 0 ); + saddr->l2_family = AF_BLUETOOTH; + saddr->l2_psm = htobs( psm ); + XP_MEMCPY( &saddr->l2_bdaddr, &addrP->u.bt.btAddr, + sizeof(saddr->l2_bdaddr) ); + result = saddr; + } +#elif defined BT_USE_RFCOMM + int channel = sdp_get_proto_port( proto_list, + RFCOMM_UUID ); + if ( channel > 0 ) { + XP_LOGF( "got channel: %d", channel ); + saddr->rc_channel = (uint8_t)channel; + saddr->rc_family = AF_BLUETOOTH; + XP_MEMCPY( &saddr->rc_bdaddr, &addrP->u.bt.btAddr, + sizeof(saddr->rc_bdaddr) ); + result = saddr; + } +#endif + } + sdp_record_free( rec ); + } + } + sdp_list_free( response_list, 0 ); + sdp_list_free( search_list, 0 ); + sdp_list_free( attrid_list, 0 ); + sdp_close( session ); + } + + LOG_RETURNF( "%p", result ); + return result; +} /* getL2Addr */ + +static void +lbt_connectSocket( LinBtStuff* btStuff, const CommsAddrRec* addrP ) +{ + int sock; + LOG_FUNC(); + + // allocate a socket + sock = socket( AF_BLUETOOTH, +#if defined BT_USE_L2CAP + SOCK_SEQPACKET, BTPROTO_L2CAP +#elif defined BT_USE_RFCOMM + SOCK_STREAM, BTPROTO_RFCOMM +#endif + ); + if ( sock < 0 ) { + XP_LOGF( "%s: socket->%s", __func__, strerror(errno) ); + } else { + L2_RF_ADDR saddr; + XP_MEMSET( &saddr, 0, sizeof(saddr) ); + if ( (NULL != getL2Addr( addrP, &saddr ) ) + // set the connection parameters (who to connect to) + // connect to server + && (0 == connect( sock, (struct sockaddr *)&saddr, sizeof(saddr) )) ) { + CommonGlobals* globals = btStuff->globals; + (*globals->socketChanged)( globals->socketChangedClosure, + -1, sock, &btStuff->sockStorage ); + btStuff->socket = sock; + } else { + XP_LOGF( "%s: connect->%s; closing socket %d", __func__, strerror(errno), sock ); + close( sock ); + } + } +} /* lbt_connectSocket */ + +static XP_Bool +lbt_accept( int listener, void* ctxt ) +{ + CommonGlobals* globals = (CommonGlobals*)ctxt; + LinBtStuff* btStuff = globals->btStuff; + int sock = -1; + L2_RF_ADDR inaddr; + socklen_t slen; + XP_Bool success; + + LOG_FUNC(); + + XP_LOGF( "%s: calling accept", __func__ ); + slen = sizeof( inaddr ); + XP_ASSERT( listener == btStuff->u.master.listener ); + sock = accept( listener, (struct sockaddr *)&inaddr, &slen ); + XP_LOGF( "%s: accept returned; socket = %d", __func__, sock ); + + success = sock >= 0; + if ( success ) { + (*globals->socketChanged)( globals->socketChangedClosure, + -1, sock, &btStuff->sockStorage ); + XP_ASSERT( btStuff->socket == -1 ); + btStuff->socket = sock; + } else { + XP_LOGF( "%s: accept->%s", __func__, strerror(errno) ); + } + return success; +} /* lbt_accept */ + +static void +lbt_register( LinBtStuff* btStuff, unsigned short l2_psm, + uint8_t XP_UNUSED_RFCOMM(rc_channel) ) +{ + LOG_FUNC(); + if ( NULL == btStuff->u.master.session ) { + uint8_t svc_uuid_int[] = XW_BT_UUID; + const char *service_name = XW_BT_NAME; + const char *svc_dsc = "An open source word game"; + const char *service_prov = "xwords.sf.net"; + + uuid_t svc_uuid; + sdp_list_t + *root_list = 0, + *proto_list = 0, + *access_proto_list = 0, + *svc_class_list = 0, + *profile_list = 0; + sdp_record_t record = { 0 }; + sdp_session_t *session = NULL; + + sdp_list_t *l2cap_list = 0; + sdp_data_t *psm = 0; +#if defined BT_USE_L2CAP +#elif defined BT_USE_RFCOMM + sdp_list_t *rfcomm_list = 0; + sdp_data_t *channel = 0; +#endif + + + // set the general service ID + sdp_uuid128_create( &svc_uuid, &svc_uuid_int ); + sdp_set_service_id( &record, svc_uuid ); + + // set l2cap information + uuid_t l2cap_uuid; + sdp_uuid16_create( &l2cap_uuid, L2CAP_UUID ); + l2cap_list = sdp_list_append( 0, &l2cap_uuid ); + /* from pybluez source */ + unsigned short l2cap_psm = l2_psm; + psm = sdp_data_alloc( SDP_UINT16, &l2cap_psm ); + sdp_list_append( l2cap_list, psm ); + proto_list = sdp_list_append( 0, l2cap_list ); + +#if defined BT_USE_RFCOMM + uuid_t rfcomm_uuid; + uint8_t rfcomm_channel = rc_channel; + sdp_uuid16_create( &rfcomm_uuid, RFCOMM_UUID ); + channel = sdp_data_alloc( SDP_UINT8, &rfcomm_channel ); + rfcomm_list = sdp_list_append( 0, &rfcomm_uuid ); + sdp_list_append( rfcomm_list, channel ); + sdp_list_append( proto_list, rfcomm_list ); +#endif + + access_proto_list = sdp_list_append( 0, proto_list ); + sdp_set_access_protos( &record, access_proto_list ); + + // set the name, provider, and description + sdp_set_info_attr(&record, service_name, service_prov, svc_dsc); + + // connect to the local SDP server, register the service record, + // and disconnect + session = sdp_connect( BDADDR_ANY, BDADDR_LOCAL, SDP_RETRY_IF_BUSY ); + if ( NULL == session ) { + XP_LOGF( "%s: sdp_connect->%s", __func__, strerror(errno) ); + } + XP_ASSERT( NULL != session ); + sdp_record_register( session, &record, 0 ); + + // cleanup + sdp_data_free( psm ); + sdp_list_free( root_list, 0 ); + sdp_list_free( access_proto_list, 0 ); + sdp_list_free( svc_class_list, 0 ); + sdp_list_free( profile_list, 0 ); +#if defined BT_USE_L2CAP + sdp_list_free( l2cap_list, 0 ); +#elif defined BT_USE_RFCOMM + sdp_list_free( rfcomm_list, 0 ); + sdp_data_free( channel ); +#endif + + btStuff->u.master.session = session; + } +} /* lbt_register */ + +static void +lbt_listenerSetup( CommonGlobals* globals ) +{ + LinBtStuff* btStuff = globals->btStuff; + L2_RF_ADDR saddr; + int listener; + uint8_t rc_channel = 0; + + listener = socket( AF_BLUETOOTH, +#if defined BT_USE_L2CAP + SOCK_SEQPACKET, BTPROTO_L2CAP +#elif defined BT_USE_RFCOMM + SOCK_STREAM, BTPROTO_RFCOMM +#endif + ); + btStuff->u.master.listener = listener; + + XP_MEMSET( &saddr, 0, sizeof(saddr) ); +#if defined BT_USE_L2CAP + saddr.l2_family = AF_BLUETOOTH; + saddr.l2_bdaddr = *BDADDR_ANY; + saddr.l2_psm = htobs( XW_PSM ); /* need to associate uuid with this before opening? */ + if ( 0 != bind( listener, (struct sockaddr *)&saddr, sizeof(saddr) ) ) { + XP_LOGF( "%s: bind->%s", __func__, strerror(errno) ); + } +#elif defined BT_USE_RFCOMM + saddr.rc_family = AF_BLUETOOTH; + saddr.rc_bdaddr = *BDADDR_ANY; + for ( rc_channel = 1; rc_channel < 30; ++rc_channel ) { + saddr.rc_channel = rc_channel; + XP_LOGF( "setting channel: %d", saddr.rc_channel ); + if ( 0 == bind( listener, (struct sockaddr *)&saddr, sizeof(saddr) ) ) { + break; + } + } +#endif + + XP_LOGF( "%s: calling listen on socket %d", __func__, listener ); + listen( listener, MAX_CLIENTS ); + + lbt_register( btStuff, htobs( XW_PSM ), rc_channel ); + + (*globals->addAcceptor)( listener, lbt_accept, globals, + &btStuff->u.master.listenerStorage ); +} /* lbt_listenerSetup */ + +void +linux_bt_open( CommonGlobals* globals, XP_Bool amMaster ) +{ + LinBtStuff* btStuff = globals->btStuff; + if ( !btStuff ) { + btStuff = globals->btStuff + = lbt_make( MPPARM(globals->params->util->mpool) amMaster ); + btStuff->globals = globals; + btStuff->socket = -1; + + globals->btStuff = btStuff; + + if ( amMaster ) { + lbt_listenerSetup( globals ); + } else { + if ( btStuff->socket < 0 ) { + CommsAddrRec addr; + comms_getAddr( globals->game.comms, &addr ); + lbt_connectSocket( btStuff, &addr ); + } + } + } +} /* linux_bt_open */ + +void +linux_bt_reset( CommonGlobals* globals ) +{ + XP_Bool amMaster = globals->btStuff->amMaster; + LOG_FUNC(); + linux_bt_close( globals ); + linux_bt_open( globals, amMaster ); + LOG_RETURN_VOID(); +} + +void +linux_bt_close( CommonGlobals* globals ) +{ + LinBtStuff* btStuff = globals->btStuff; + + if ( !!btStuff ) { + if ( btStuff->amMaster ) { + XP_LOGF( "%s: closing listener socket %d", __func__, btStuff->u.master.listener ); + /* Remove from main event loop */ + (*globals->addAcceptor)( -1, NULL, globals, + &btStuff->u.master.listenerStorage ); + close( btStuff->u.master.listener ); + btStuff->u.master.listener = -1; + + sdp_close( btStuff->u.master.session ); + XP_LOGF( "sleeping for Palm's sake..." ); + sleep( 2 ); /* see if this gives palm a chance to not hang */ + } + + if ( btStuff->socket != -1 ) { + (*globals->socketChanged)( globals->socketChangedClosure, + btStuff->socket, -1, &btStuff->sockStorage ); + (void)close( btStuff->socket ); + } + + XP_FREE( globals->params->util->mpool, btStuff ); + globals->btStuff = NULL; + } +} /* linux_bt_close */ + +XP_S16 +linux_bt_send( const XP_U8* buf, XP_U16 buflen, + const CommsAddrRec* addrP, + CommonGlobals* globals ) +{ + XP_S16 nSent = -1; + LinBtStuff* btStuff; + + XP_LOGF( "%s(len=%d)", __func__, buflen ); + LOG_HEX( buf, buflen, __func__ ); + + btStuff = globals->btStuff; + if ( !!btStuff ) { + CommsAddrRec addr; + if ( !addrP ) { + comms_getAddr( globals->game.comms, &addr ); + addrP = &addr; + } + + if ( btStuff->socket < 0 && !btStuff->amMaster ) { + lbt_connectSocket( btStuff, addrP ); + } + + if ( btStuff->socket >= 0 ) { +#if defined BT_USE_RFCOMM + unsigned short len = htons(buflen); + nSent = write( btStuff->socket, &len, sizeof(len) ); + assert( nSent == sizeof(len) ); +#endif + nSent = write( btStuff->socket, buf, buflen ); + if ( nSent < 0 ) { + XP_LOGF( "%s: send->%s", __func__, strerror(errno) ); + } else if ( nSent < buflen ) { + XP_LOGF( "%s: sent only %d bytes of %d", __func__, nSent, + buflen ); + /* Need to loop until sent if this is happening */ + XP_ASSERT( 0 ); + } + } else { + XP_LOGF( "%s: socket still not set", __func__ ); + } + } + LOG_RETURNF( "%d", nSent ); + return nSent; +} /* linux_bt_send */ + +#if defined BT_USE_RFCOMM +static void +read_all( int sock, unsigned char* buf, const int len ) +{ + int totalRead = 0; + while ( totalRead < len ) { + int nRead = read( sock, buf+totalRead, len-totalRead ); + if ( nRead < 0 ) { + XP_LOGF( "%s: read->%s", __func__, strerror(errno) ); + break; + } + totalRead += nRead; + XP_ASSERT( totalRead <= len ); + } +} +#endif + +XP_S16 +linux_bt_receive( int sock, XP_U8* buf, XP_U16 buflen ) +{ + XP_S16 nRead = 0; + LOG_FUNC(); + XP_ASSERT( sock >= 0 ); + +#if defined BT_USE_RFCOMM + read_all( sock, (unsigned char*)&nRead, sizeof(nRead) ); + nRead = ntohs(nRead); + XP_LOGF( "nRead=%d", nRead ); + XP_ASSERT( nRead < buflen ); + + read_all( sock, buf, nRead ); + LOG_HEX( buf, nRead, __func__ ); +#else + nRead = read( sock, buf, buflen ); + if ( nRead < 0 ) { + XP_LOGF( "%s: read->%s", __func__, strerror(errno) ); + } +#endif + + LOG_RETURNF( "%d", nRead ); + return nRead; +} + +void +linux_bt_socketclosed( CommonGlobals* globals, int sock ) +{ + LinBtStuff* btStuff = globals->btStuff; + LOG_FUNC(); + XP_ASSERT( sock == btStuff->socket ); + btStuff->socket = -1; +} + +#endif /* XWFEATURE_BLUETOOTH */ diff --git a/xwords4/linux/linuxbt.h b/xwords4/linux/linuxbt.h new file mode 100644 index 000000000..d00bf3b41 --- /dev/null +++ b/xwords4/linux/linuxbt.h @@ -0,0 +1,39 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE";-*- */ +/* + * Copyright 2006 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef _LINUXBT_H_ +#define _LINUXBT_H_ + +#ifdef XWFEATURE_BLUETOOTH + +#include "main.h" + +void linux_bt_open( CommonGlobals* globals, XP_Bool amMaster ); +void linux_bt_reset( CommonGlobals* globals ); +void linux_bt_close( CommonGlobals* globals ); + +XP_S16 linux_bt_send( const XP_U8* buf, XP_U16 buflen, + const CommsAddrRec* addrRec, + CommonGlobals* globals ); +XP_S16 linux_bt_receive( int sock, XP_U8* buf, XP_U16 buflen ); + +void linux_bt_socketclosed( CommonGlobals* globals, int sock ); + +#endif /* XWFEATURE_BLUETOOTH */ +#endif /* #ifndef _LINUXBT_H_ */ diff --git a/xwords4/linux/linuxdict.c b/xwords4/linux/linuxdict.c new file mode 100644 index 000000000..8a3c37a8a --- /dev/null +++ b/xwords4/linux/linuxdict.c @@ -0,0 +1,397 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4;-*- */ +/* + * Copyright 1997-2002 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef CLIENT_ONLY /* there's an else in the middle!!! */ + +#include +#include +/* #include */ + +#include "comtypes.h" +#include "dictnryp.h" +#include "linuxmain.h" +#include "strutils.h" + +typedef struct DictStart { + XP_U32 numNodes; + /* XP_U32 indexStart; */ + array_edge* array; +} DictStart; + +typedef struct LinuxDictionaryCtxt { + DictionaryCtxt super; + /* prc_t* pt; */ + /* DictStart* starts; */ + /* XP_U16 numStarts; */ +} LinuxDictionaryCtxt; + + +/************************ Prototypes ***********************/ +static XP_Bool initFromDictFile( LinuxDictionaryCtxt* dctx, + const char* fileName ); +static void linux_dictionary_destroy( DictionaryCtxt* dict ); +static const XP_UCHAR* linux_dict_getShortName( const DictionaryCtxt* dict ); + +/***************************************************************************** + * + ****************************************************************************/ +DictionaryCtxt* +linux_dictionary_make( MPFORMAL const char* dictFileName ) +{ + LinuxDictionaryCtxt* result = + (LinuxDictionaryCtxt*)XP_MALLOC(mpool, sizeof(*result)); + XP_MEMSET( result, 0, sizeof(*result) ); + + dict_super_init( (DictionaryCtxt*)result ); + MPASSIGN(result->super.mpool, mpool); + + if ( !!dictFileName ) { + XP_Bool success = initFromDictFile( result, dictFileName ); + if ( success ) { + result->super.destructor = linux_dictionary_destroy; + result->super.func_dict_getShortName = linux_dict_getShortName; + setBlankTile( &result->super ); + } else { + XP_FREE( mpool, result ); + result = NULL; + } + } + + return (DictionaryCtxt*)result; +} /* gtk_dictionary_make */ + +static XP_U16 +countSpecials( LinuxDictionaryCtxt* ctxt ) +{ + XP_U16 result = 0; + XP_U16 i; + + for ( i = 0; i < ctxt->super.nFaces; ++i ) { + if ( IS_SPECIAL(ctxt->super.faces16[i] ) ) { + ++result; + } + } + + return result; +} /* countSpecials */ + +static XP_Bitmap +skipBitmap( LinuxDictionaryCtxt* ctxt, FILE* dictF ) +{ + XP_U8 nCols, nRows, nBytes; + LinuxBMStruct* lbs = NULL; + + (void)fread( &nCols, sizeof(nCols), 1, dictF ); + if ( nCols > 0 ) { + (void)fread( &nRows, sizeof(nRows), 1, dictF ); + + nBytes = ((nRows * nCols) + 7) / 8; + + lbs = XP_MALLOC( ctxt->super.mpool, sizeof(*lbs) + nBytes ); + lbs->nRows = nRows; + lbs->nCols = nCols; + lbs->nBytes = nBytes; + + (void)fread( lbs + 1, nBytes, 1, dictF ); + } + + return lbs; +} /* skipBitmap */ + +static void +skipBitmaps( LinuxDictionaryCtxt* ctxt, FILE* dictF ) +{ + XP_U16 nSpecials; + XP_UCHAR* text; + XP_UCHAR** texts; + SpecialBitmaps* bitmaps; + Tile tile; + + nSpecials = countSpecials( ctxt ); + + texts = (XP_UCHAR**)XP_MALLOC( ctxt->super.mpool, + nSpecials * sizeof(*texts) ); + bitmaps = (SpecialBitmaps*)XP_MALLOC( ctxt->super.mpool, + nSpecials * sizeof(*bitmaps) ); + + for ( tile = 0; tile < ctxt->super.nFaces; ++tile ) { + + XP_CHAR16 face = ctxt->super.faces16[(short)tile]; + if ( IS_SPECIAL(face) ) { + XP_U8 txtlen; + XP_ASSERT( face < nSpecials ); + + /* get the string */ + (void)fread( &txtlen, sizeof(txtlen), 1, dictF ); + text = (XP_UCHAR*)XP_MALLOC(ctxt->super.mpool, txtlen+1); + (void)fread( text, txtlen, 1, dictF ); + text[txtlen] = '\0'; + texts[face] = text; + + XP_DEBUGF( "skipping bitmaps for %s", texts[face] ); + + bitmaps[face].largeBM = skipBitmap( ctxt, dictF ); + bitmaps[face].smallBM = skipBitmap( ctxt, dictF ); + } + } + + ctxt->super.chars = texts; + ctxt->super.bitmaps = bitmaps; +} /* skipBitmaps */ + +static XP_Bool +initFromDictFile( LinuxDictionaryCtxt* dctx, const char* fileName ) +{ + XP_Bool formatOk = XP_TRUE; + XP_U8 numFaces; + long curPos, dictLength; + XP_U32 topOffset; + FILE* dictF = fopen( fileName, "r" ); + unsigned short xloc; + XP_U16 flags; + XP_U16 facesSize; + XP_U16 charSize; + + XP_ASSERT( dictF ); + (void)fread( &flags, sizeof(flags), 1, dictF ); + flags = ntohs(flags); + XP_DEBUGF( "flags=0x%x", flags ); +#ifdef NODE_CAN_4 + if ( flags == 0x0001 ) { + dctx->super.nodeSize = 3; + charSize = 1; + dctx->super.is_4_byte = XP_FALSE; + } else if ( flags == 0x0002 ) { + dctx->super.nodeSize = 3; + charSize = 2; + dctx->super.is_4_byte = XP_FALSE; + } else if ( flags == 0x0003 ) { + dctx->super.nodeSize = 4; + charSize = 2; + dctx->super.is_4_byte = XP_TRUE; + } else { + /* case I don't know how to deal with */ + formatOk = XP_FALSE; + XP_ASSERT(0); + } +#else + XP_ASSERT( flags == 0x0001 ); +#endif + + if ( formatOk ) { + (void)fread( &numFaces, sizeof(numFaces), 1, dictF ); + + dctx->super.nFaces = numFaces; + + dctx->super.countsAndValues = XP_MALLOC( dctx->super.mpool, + numFaces*2 ); + facesSize = numFaces * sizeof(dctx->super.faces16[0]); + dctx->super.faces16 = XP_MALLOC( dctx->super.mpool, facesSize ); + XP_MEMSET( dctx->super.faces16, 0, facesSize ); + + fread( dctx->super.faces16, numFaces * charSize, + 1, dictF ); + if ( charSize == sizeof(dctx->super.faces16[0]) ) { + /* fix endianness */ + XP_U16 i; + for ( i = 0; i < numFaces; ++i ) { + XP_CHAR16 tmp = dctx->super.faces16[i]; + dctx->super.faces16[i] = ntohs(tmp); + } + } else { + XP_UCHAR* src = ((XP_UCHAR*)(dctx->super.faces16)) + numFaces; + XP_CHAR16* dest = dctx->super.faces16 + numFaces; + while ( src-- <= (XP_UCHAR*)(dest--) ) { + *dest = (XP_CHAR16)*src; + } + } + + fread( &xloc, 2, 1, dictF ); /* read in (dump) the xloc header for + now */ + fread( dctx->super.countsAndValues, numFaces*2, 1, dictF ); + + skipBitmaps( dctx, dictF ); + + curPos = ftell( dictF ); + fseek( dictF, 0L, SEEK_END ); + dictLength = ftell( dictF ) - curPos; + fseek( dictF, curPos, SEEK_SET ); + + if ( dictLength > 0 ) { + fread( &topOffset, sizeof(topOffset), 1, dictF ); + /* it's in big-endian order */ + topOffset = ntohl(topOffset); + dictLength -= sizeof(topOffset); /* first four bytes are offset */ + } + + if ( dictLength > 0 ) { +#ifdef DEBUG +# ifdef NODE_CAN_4 + dctx->super.numEdges = dictLength / dctx->super.nodeSize; + XP_ASSERT( (dictLength % dctx->super.nodeSize) == 0 ); +# else + dctx->super.numEdges = dictLength / 3; + XP_ASSERT( (dictLength % 3) == 0 ); +# endif +#endif + + dctx->super.base = (array_edge*)XP_MALLOC( dctx->super.mpool, + dictLength ); + XP_ASSERT( !!dctx->super.base ); + fread( dctx->super.base, dictLength, 1, dictF ); + + dctx->super.topEdge = dctx->super.base + topOffset; + } else { + dctx->super.base = NULL; + dctx->super.topEdge = NULL; + } + + dctx->super.name = copyString( dctx->super.mpool, fileName); + } + + fclose( dictF ); + return formatOk; +} /* initFromDictFile */ + +static void +freeSpecials( LinuxDictionaryCtxt* ctxt ) +{ + XP_U16 nSpecials = 0; + XP_U16 i; + + for ( i = 0; i < ctxt->super.nFaces; ++i ) { + if ( IS_SPECIAL(ctxt->super.faces16[i]) ) { + if ( !!ctxt->super.bitmaps ) { + XP_Bitmap* bmp = ctxt->super.bitmaps[nSpecials].largeBM; + if ( !!bmp ) { + XP_FREE( ctxt->super.mpool, bmp ); + } + bmp = ctxt->super.bitmaps[nSpecials].smallBM; + if ( !!bmp ) { + XP_FREE( ctxt->super.mpool, bmp ); + } + } + if ( !!ctxt->super.chars && !!ctxt->super.chars[nSpecials]) { + XP_FREE( ctxt->super.mpool, ctxt->super.chars[nSpecials] ); + } + ++nSpecials; + } + } + if ( !!ctxt->super.bitmaps ) { + XP_FREE( ctxt->super.mpool, ctxt->super.bitmaps ); + } + if ( !!ctxt->super.chars ) { + XP_FREE( ctxt->super.mpool, ctxt->super.chars ); + } +} /* freeSpecials */ + +static void +linux_dictionary_destroy( DictionaryCtxt* dict ) +{ + LinuxDictionaryCtxt* ctxt = (LinuxDictionaryCtxt*)dict; + + freeSpecials( ctxt ); + + if ( !!dict->topEdge ) { + XP_FREE( dict->mpool, dict->topEdge ); + } + + XP_FREE( dict->mpool, ctxt->super.countsAndValues ); + XP_FREE( dict->mpool, ctxt->super.faces16 ); + XP_FREE( dict->mpool, ctxt->super.name ); + XP_FREE( dict->mpool, ctxt ); +} /* linux_dictionary_destroy */ + +static const XP_UCHAR* +linux_dict_getShortName( const DictionaryCtxt* dict ) +{ + const XP_UCHAR* full = dict_getName( dict ); + const char* c = strrchr( full, '/' ); + if ( !!c ) { + ++c; + } else { + c = full; + } + return c; +} + +#else /* CLIENT_ONLY *IS* defined */ + +/* initFromDictFile: + * This guy reads in from a prc file, and probably hasn't worked in a year. + */ +#define RECS_BEFORE_DAWG 3 /* a hack */ +static XP_Bool +initFromDictFile( LinuxDictionaryCtxt* dctx, const char* fileName ) +{ + short i; + unsigned short* dataP; + unsigned nRecs; + prc_record_t* prect; + + prc_t* pt = prcopen( fileName, PRC_OPEN_READ ); + dctx->pt = pt; /* remember so we can close it later */ + + nRecs = prcgetnrecords( pt ); + + /* record 0 holds a struct whose 5th byte is the record num of the first + dawg record. 1 and 2 hold tile data. Let's assume 3 is the first dawg + record for now. */ + + prect = prcgetrecord( pt, 1 ); + dctx->super.numFaces = prect->datalen; /* one char per byte */ + dctx->super.faces = malloc( prect->datalen ); + memcpy( dctx->super.faces, prect->data, prect->datalen ); + + dctx->super.counts = malloc( dctx->super.numFaces ); + dctx->super.values = malloc( dctx->super.numFaces ); + + prect = prcgetrecord( pt, 2 ); + dataP = (unsigned short*)prect->data + 1; /* skip the xloc header */ + + for ( i = 0; i < dctx->super.numFaces; ++i ) { + unsigned short byt = *dataP++; + dctx->super.values[i] = byt >> 8; + dctx->super.counts[i] = byt & 0xFF; + if ( dctx->super.values[i] == 0 ) { + dctx->super.counts[i] = 4; /* 4 blanks :-) */ + } + } + + dctx->numStarts = nRecs - RECS_BEFORE_DAWG; + dctx->starts = XP_MALLOC( dctx->numStarts * sizeof(*dctx->starts) ); + + for ( i = 0/* , offset = 0 */; i < dctx->numStarts; ++i ) { + prect = prcgetrecord( pt, i + RECS_BEFORE_DAWG ); + dctx->starts[i].numNodes = prect->datalen / 3; + dctx->starts[i].array = (array_edge*)prect->data; + + XP_ASSERT( (prect->datalen % 3) == 0 ); + } +} /* initFromDictFile */ + +void +linux_dictionary_destroy( DictionaryCtxt* dict ) +{ + LinuxDictionaryCtxt* ctxt = (LinuxDictionaryCtxt*)dict; + prcclose( ctxt->pt ); +} + +#endif /* CLIENT_ONLY */ + diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c new file mode 100644 index 000000000..ca580236b --- /dev/null +++ b/xwords4/linux/linuxmain.c @@ -0,0 +1,1001 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE"; -*- */ +/* + * Copyright 2000-2008 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include +#include +#include +#include + +#include /* gethostbyname */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef XWFEATURE_BLUETOOTH +# include +# include +# include +#endif + +/* #include */ + +#include "linuxmain.h" +#include "linuxutl.h" +#include "linuxbt.h" +#include "linuxudp.h" +#include "main.h" +#ifdef PLATFORM_NCURSES +# include "cursesmain.h" +#endif +#ifdef PLATFORM_GTK +# include "gtkmain.h" +#endif +#include "model.h" +#include "util.h" +#include "strutils.h" +/* #include "commgr.h" */ +/* #include "compipe.h" */ +#include "memstream.h" +#include "LocalizedStrIncludes.h" + +#define DEFAULT_PORT 10999 +#define DEFAULT_LISTEN_PORT 4998 + +XP_Bool +file_exists( const char* fileName ) +{ + struct stat statBuf; + + int statResult = stat( fileName, &statBuf ); + return statResult == 0; +} /* file_exists */ + +XWStreamCtxt* +streamFromFile( CommonGlobals* cGlobals, char* name, void* closure ) +{ + XP_U8* buf; + struct stat statBuf; + FILE* f; + XWStreamCtxt* stream; + + (void)stat( name, &statBuf ); + buf = malloc( statBuf.st_size ); + f = fopen( name, "r" ); + fread( buf, statBuf.st_size, 1, f ); + fclose( f ); + + stream = mem_stream_make( MPPARM(cGlobals->params->util->mpool) + cGlobals->params->vtMgr, + closure, CHANNEL_NONE, NULL ); + stream_putBytes( stream, buf, statBuf.st_size ); + free( buf ); + + return stream; +} /* streamFromFile */ + +void +writeToFile( XWStreamCtxt* stream, void* closure ) +{ + void* buf; + FILE* file; + XP_U16 len; + CommonGlobals* cGlobals = (CommonGlobals*)closure; + + len = stream_getSize( stream ); + buf = malloc( len ); + stream_getBytes( stream, buf, len ); + + file = fopen( cGlobals->params->fileName, "w" ); + fwrite( buf, 1, len, file ); + fclose( file ); + + free( buf ); +} /* writeToFile */ + +void +catOnClose( XWStreamCtxt* stream, void* XP_UNUSED(closure) ) +{ + XP_U16 nBytes; + char* buffer; + + XP_LOGF( "catOnClose" ); + + nBytes = stream_getSize( stream ); + buffer = malloc( nBytes + 1 ); + stream_getBytes( stream, buffer, nBytes ); + buffer[nBytes] = '\0'; + + fprintf( stderr, "%s", buffer ); + + free( buffer ); +} /* catOnClose */ + +void +catGameHistory( CommonGlobals* cGlobals ) +{ + if ( !!cGlobals->game.model ) { + XP_Bool gameOver = server_getGameIsOver( cGlobals->game.server ); + XWStreamCtxt* stream = + mem_stream_make( MPPARM(cGlobals->params->util->mpool) + cGlobals->params->vtMgr, + NULL, CHANNEL_NONE, catOnClose ); + model_writeGameHistory( cGlobals->game.model, stream, + cGlobals->game.server, gameOver ); + stream_putU8( stream, '\n' ); + stream_destroy( stream ); + } +} /* catGameHistory */ + +XP_UCHAR* +strFromStream( XWStreamCtxt* stream ) +{ + XP_U16 len = stream_getSize( stream ); + XP_UCHAR* buf = (XP_UCHAR*)malloc( len + 1 ); + stream_getBytes( stream, buf, len ); + buf[len] = '\0'; + + return buf; +} /* strFromStream */ + + +static void +usage( char* appName, char* msg ) +{ + if ( msg != NULL ) { + fprintf( stderr, "Error: %s\n\n", msg ); + } + fprintf( stderr, "usage: %s \n" +#if defined PLATFORM_GTK && defined PLATFORM_NCURSES + "\t [-g] # gtk (default)\n" + "\t [-u] # ncurses (for dumb terminal)\n" +#endif +#if defined PLATFORM_GTK + "\t [-k] # ask for parameters via \"new games\" dlg\n" + "\t [-h numRowsHidded] \n" +# ifdef XWFEATURE_SEARCHLIMIT + "\t [-I] # don't support hint rect dragging\n" +# endif +#endif + "\t [-f file] # use this file to save/load game\n" + "\t [-q nSecs] # quit with pause when game over (useful for robot-only)\n" + "\t [-S] # slow robot down \n" + "\t [-i] # print game history when game over\n" + "\t [-U] # call 'Undo' after game ends\n" +#ifdef XWFEATURE_RELAY + "\t [-H] # Don't send heartbeats to relay\n" +#endif + "\t [-r name]* # same-process robot\n" + "\t [-n name]* # same-process player (no network used)\n" + "\t [-w pwd]* # passwd for matching local player\n" + "\t [-v] # put scoreboard in vertical mode\n" + "\t [-m] # make the robot duMb (smart is default)\n" + "\t [-l] # disallow hints\n" + "\t [-c] # explain robot scores after each move\n" + "\t [-C COOKIE] # cookie used to groups games on relay\n" + "\t\t # (max of four players total, local and remote)\n" + "\t [-b boardSize] # number of columns and rows\n" + "\t [-e random_seed] \n" + "\t [-t initial_minutes_on_timer] \n" + "# --------------- choose client or server ----------\n" + "\t -s # be the server\n" + "\t -d xwd_file # provides tile counts & values\n" + "\t\t # list each player as local or remote\n" + "\t [-N]* # remote client (listen for connection)\n" +#ifdef XWFEATURE_RELAY + "\t [-p relay_port] # relay is at this port\n" + "\t [-a relay_addr] # use relay (via port spec'd above)\n" + "" " (default localhost)\n" +#endif +#ifdef XWFEATURE_BLUETOOTH + "\t [-B n:name|a:00:11:22:33:44:55]\n" + "\t\t\t# connect via bluetooth [param ignored if -s]\n" +#endif +#ifdef XWFEATURE_IP_DIRECT + "\t [-D host_addr]\t\t\tConnect directly to host [param ignored if -s]\n" + "\t [-p host_port] # put/look for host on this port\n" +#endif + +/* "# --------------- OR client-only ----------\n" */ +/* "\t [-p client_port] # must != server's port if on same device" */ +#ifdef XWFEATURE_RELAY + "\nrelay example: \n" + "\t host: ./xwords -d dict.xwd -r Eric -s -N -a localhost -p 10999 -C COOKIE\n" + "\tguest: ./xwords -d dict.xwd -r Kati -a localhost -p 10999 -C COOKIE" +#endif +#ifdef XWFEATURE_BLUETOOTH + "\nBluetooth example: \n" + "\t host: ./xwords -d dict.xwd -r Eric -s -N -B ignored\n" + "\tguest: ./xwords -d dict.xwd -r Kati -B n:treo_bt_name (OR b:11:22:33:44:55:66)" +#endif +#ifdef XWFEATURE_IP_DIRECT + "\nDirect example: \n" + "\t host: ./xwords -d dict.xwd -r Eric -s -N -N -D localhost -p 10999\n" + "\tguest: ./xwords -d dict.xwd -r Kati -D localhost -p 10999\n" + "\tguest: ./xwords -d dict.xwd -r Ariynn -D localhost -p 10999" +#endif + + "\n" + , appName ); + fprintf( stderr, "\n(revision: %s)\n", SVN_REV); + exit(1); +} /* usage */ + +#ifdef KEYBOARD_NAV +XP_Bool +linShiftFocus( CommonGlobals* cGlobals, XP_Key key, const BoardObjectType* order, + BoardObjectType* nxtP ) +{ + BoardCtxt* board = cGlobals->game.board; + XP_Bool handled = XP_FALSE; + BoardObjectType nxt = OBJ_NONE; + BoardObjectType cur; + XP_U16 i, curIndex = 0; + + cur = board_getFocusOwner( board ); + if ( cur == OBJ_NONE ) { + cur = order[0]; + } + for ( i = 0; i < 3; ++i ) { + if ( cur == order[i] ) { + curIndex = i; + break; + } + } + XP_ASSERT( curIndex < 3 ); + + curIndex += 3; + if ( key == XP_CURSOR_KEY_DOWN || key == XP_CURSOR_KEY_RIGHT ) { + ++curIndex; + } else if ( key == XP_CURSOR_KEY_UP || key == XP_CURSOR_KEY_LEFT ) { + --curIndex; + } else { + XP_ASSERT(0); + } + curIndex %= 3; + + nxt = order[curIndex]; + handled = board_focusChanged( board, nxt, XP_TRUE ); + + if ( !!nxtP ) { + *nxtP = nxt; + } + + return handled; +} /* linShiftFocus */ +#endif + +#ifndef XWFEATURE_STANDALONE_ONLY +#ifdef XWFEATURE_RELAY +static int +linux_init_relay_socket( CommonGlobals* cGlobals ) +{ + struct sockaddr_in to_sock; + struct hostent* host; + int sock = cGlobals->socket; + if ( sock == -1 ) { + + /* make a local copy of the address to send to */ + sock = socket( AF_INET, SOCK_STREAM, 0 ); + if ( sock == -1 ) { + XP_DEBUGF( "socket returned -1\n" ); + return -1; + } + + to_sock.sin_port = htons(cGlobals->params-> + connInfo.relay.defaultSendPort ); + XP_STATUSF( "1: sending to port %d", + cGlobals->params->connInfo.relay.defaultSendPort ); + host = gethostbyname( cGlobals->params->connInfo.relay.relayName ); + if ( NULL == host ) { + XP_WARNF( "gethostbyname returned -1\n" ); + return -1; + } else { + XP_WARNF( "gethostbyname for %s worked", + cGlobals->defaultServerName ); + } + memcpy( &(to_sock.sin_addr.s_addr), host->h_addr_list[0], + sizeof(struct in_addr)); + to_sock.sin_family = AF_INET; + + errno = 0; + if ( 0 == connect( sock, (const struct sockaddr*)&to_sock, + sizeof(to_sock) ) ) { + cGlobals->socket = sock; + } else { + close( sock ); + sock = -1; + XP_STATUSF( "%s: connect failed: %s (%d)", __func__, strerror(errno), errno ); + } + } + + return sock; +} /* linux_init_relay_socket */ + +static XP_S16 +linux_tcp_send( const XP_U8* buf, XP_U16 buflen, + const CommsAddrRec* XP_UNUSED(addrRec), + CommonGlobals* globals ) +{ + XP_S16 result = 0; + int socket = globals->socket; + + if ( socket == -1 ) { + XP_STATUSF( "%s: socket uninitialized", __func__ ); + socket = linux_init_relay_socket( globals ); + if ( socket != -1 ) { + assert( globals->socket == socket ); + (*globals->socketChanged)( globals->socketChangedClosure, + -1, socket, + &globals->storage ); + } + } + + if ( socket != -1 ) { + XP_U16 netLen = htons( buflen ); + errno = 0; + + result = send( socket, &netLen, sizeof(netLen), 0 ); + if ( result == sizeof(netLen) ) { + result = send( socket, buf, buflen, 0 ); + } + if ( result <= 0 ) { + XP_STATUSF( "closing non-functional socket" ); + close( socket ); + (*globals->socketChanged)( globals->socketChangedClosure, + socket, -1, &globals->storage ); + globals->socket = -1; + } + + XP_STATUSF( "linux_tcp_send: send returned %d of %d (err=%d)", + result, buflen, errno ); + } + + return result; +} /* linux_tcp_send */ +#endif /* XWFEATURE_RELAY */ + +#ifdef XWFEATURE_RELAY +static void +linux_tcp_reset( CommonGlobals* globals ) +{ + LOG_FUNC(); + if ( globals->socket != -1 ) { + (void)close( globals->socket ); + globals->socket = -1; + } +} +#endif + +#ifdef COMMS_HEARTBEAT +void +linux_reset( void* closure ) +{ + CommonGlobals* globals = (CommonGlobals*)closure; + CommsConnType conType = globals->params->conType; + if ( 0 ) { +#ifdef XWFEATURE_BLUETOOTH + } else if ( conType == COMMS_CONN_BT ) { + linux_bt_reset( globals ); +#endif +#ifdef XWFEATURE_IP_DIRECT + } else if ( conType == COMMS_CONN_IP_DIRECT ) { + linux_udp_reset( globals ); +#endif +#ifdef XWFEATURE_RELAY + } else if ( conType == COMMS_CONN_RELAY ) { + linux_tcp_reset( globals ); +#endif + } + +} +#endif + +XP_S16 +linux_send( const XP_U8* buf, XP_U16 buflen, + const CommsAddrRec* addrRec, + void* closure ) +{ + XP_S16 nSent = -1; + CommonGlobals* globals = (CommonGlobals*)closure; + CommsConnType conType; + + if ( !!addrRec ) { + conType = addrRec->conType; + } else { + conType = globals->params->conType; + } + + if ( 0 ) { +#ifdef XWFEATURE_RELAY + } else if ( conType == COMMS_CONN_RELAY ) { + nSent = linux_tcp_send( buf, buflen, addrRec, globals ); +#endif +#if defined XWFEATURE_BLUETOOTH + } else if ( conType == COMMS_CONN_BT ) { + XP_Bool isServer = comms_getIsServer( globals->game.comms ); + linux_bt_open( globals, isServer ); + nSent = linux_bt_send( buf, buflen, addrRec, globals ); +#endif +#if defined XWFEATURE_IP_DIRECT + } else if ( conType == COMMS_CONN_IP_DIRECT ) { + CommsAddrRec addr; + comms_getAddr( globals->game.comms, &addr ); + linux_udp_open( globals, &addr ); + nSent = linux_udp_send( buf, buflen, addrRec, globals ); +#endif + } else { + XP_ASSERT(0); + } + return nSent; +} /* linux_send */ + +#ifdef XWFEATURE_RELAY +static void +linux_close_socket( CommonGlobals* cGlobals ) +{ + int socket = cGlobals->socket; + cGlobals->socket = -1; + + XP_LOGF( "linux_close_socket" ); + close( socket ); +} + +int +linux_relay_receive( CommonGlobals* cGlobals, unsigned char* buf, int bufSize ) +{ + int sock = cGlobals->socket; + unsigned short tmp; + unsigned short packetSize; + ssize_t nRead = recv( sock, &tmp, sizeof(tmp), 0 ); + if ( nRead != 2 ) { + XP_LOGF( "recv => %d, errno=%d", nRead, errno ); + linux_close_socket( cGlobals ); + nRead = -1; + } else { + + packetSize = ntohs( tmp ); + assert( packetSize <= bufSize ); + nRead = recv( sock, buf, packetSize, 0 ); + if ( nRead < 0 ) { + XP_WARNF( "linuxReceive: errno=%d\n", errno ); + } + } + return nRead; +} /* linuxReceive */ +#endif /* XWFEATURE_RELAY */ +#endif /* XWFEATURE_STANDALONE_ONLY */ + +/* Create a stream for the incoming message buffer, and read in any + information specific to our platform's comms layer (return address, say) + */ +XWStreamCtxt* +stream_from_msgbuf( CommonGlobals* globals, unsigned char* bufPtr, + XP_U16 nBytes ) +{ + XWStreamCtxt* result; + result = mem_stream_make( MPPARM(globals->params->util->mpool) + globals->params->vtMgr, + globals, CHANNEL_NONE, NULL ); + stream_putBytes( result, bufPtr, nBytes ); + + return result; +} /* stream_from_msgbuf */ + +#if 0 +static void +streamClosed( XWStreamCtxt* stream, XP_PlayerAddr addr, void* closure ) +{ + fprintf( stderr, "streamClosed called\n" ); +} /* streamClosed */ + +static XWStreamCtxt* +linux_util_makeStreamFromAddr( XW_UtilCtxt* uctx, XP_U16 channelNo ) +{ +#if 1 +/* XWStreamCtxt* stream = linux_mem_stream_make( uctx->closure, channelNo, */ +/* sendOnClose, NULL ); */ +#else + struct sockaddr* returnAddr = (struct sockaddr*)addr; + int newSocket; + int result; + + newSocket = socket( AF_INET, DGRAM_TYPE, 0 ); + fprintf( stderr, "linux_util_makeStreamFromAddr: made socket %d\n", + newSocket ); + /* #define EADDRINUSE 98 */ + result = bind( newSocket, (struct sockaddr*)returnAddr, addrLen ); + fprintf( stderr, "bind returned %d; errno=%d\n", result, errno ); + + return linux_make_socketStream( newSocket ); +#endif +} /* linux_util_makeStreamFromAddr */ +#endif + +void +linuxFireTimer( CommonGlobals* cGlobals, XWTimerReason why ) +{ + XWTimerProc proc = cGlobals->timerProcs[why]; + void* closure = cGlobals->timerClosures[why]; + + cGlobals->timerProcs[why] = NULL; + + (*proc)( closure, why ); +} /* fireTimer */ + +#if defined XWFEATURE_BLUETOOTH || defined XWFEATURE_RELAY +static void +linux_util_addrChange( XW_UtilCtxt* uc, + const CommsAddrRec* XP_UNUSED(oldAddr), + const CommsAddrRec* newAddr ) +{ + if ( 0 ) { +#ifdef XWFEATURE_BLUETOOTH + } else if ( newAddr->conType == COMMS_CONN_BT ) { + CommonGlobals* cGlobals = (CommonGlobals*)uc->closure; + XP_Bool isServer = comms_getIsServer( cGlobals->game.comms ); + linux_bt_open( cGlobals, isServer ); +#endif +#if defined XWFEATURE_IP_DIRECT + } else if ( newAddr->conType == COMMS_CONN_IP_DIRECT ) { + CommonGlobals* cGlobals = (CommonGlobals*)uc->closure; + linux_udp_open( cGlobals, newAddr ); +#endif + } +} +#endif + +static unsigned int +defaultRandomSeed() +{ + /* use kernel device rather than time() so can run multiple times/second + without getting the same results. */ + unsigned int rs; + FILE* rfile = fopen( "/dev/urandom", "ro" ); + fread( &rs, sizeof(rs), 1, rfile ); + fclose( rfile ); + return rs; +} /* defaultRandomSeed */ + +/* This belongs in linuxbt.c */ +#ifdef XWFEATURE_BLUETOOTH +static XP_Bool +nameToBtAddr( const char* name, bdaddr_t* ba ) +{ + XP_Bool success = XP_FALSE; + int id, socket; + LOG_FUNC(); +# define RESPMAX 5 + + id = hci_get_route( NULL ); + socket = hci_open_dev( id ); + if ( id >= 0 && socket >= 0 ) { + long flags = 0L; + inquiry_info inqInfo[RESPMAX]; + inquiry_info* inqInfoP = inqInfo; + int count = hci_inquiry( id, 10, RESPMAX, NULL, &inqInfoP, flags ); + int i; + + for ( i = 0; i < count; ++i ) { + char buf[64]; + if ( 0 >= hci_read_remote_name( socket, &inqInfo[i].bdaddr, + sizeof(buf), buf, 0)) { + if ( 0 == strcmp( buf, name ) ) { + XP_MEMCPY( ba, &inqInfo[i].bdaddr, sizeof(*ba) ); + success = XP_TRUE; + XP_LOGF( "%s: matched %s", __func__, name ); + char addrStr[32]; + ba2str(ba, addrStr); + XP_LOGF( "bt_addr is %s", addrStr ); + break; + } + } + } + } + return success; +} /* nameToBtAddr */ +#endif + +int +main( int argc, char** argv ) +{ + XP_Bool useGTK, useCurses; + int opt; + int totalPlayerCount = 0; + XP_Bool isServer = XP_FALSE; + char* portNum = "10999"; + char* hostName = "localhost"; + unsigned int seed = defaultRandomSeed(); + LaunchParams mainParams; + XP_U16 robotCount = 0; + + CommsConnType conType = COMMS_CONN_NONE; +#ifdef XWFEATURE_BLUETOOTH + const char* btaddr = NULL; +#endif + + XP_LOGF( "main started: pid = %d", getpid() ); +#ifdef DEBUG + syslog( LOG_DEBUG, "main started: pid = %d", getpid() ); +#endif + +#ifdef DEBUG + { + int i; + for ( i = 0; i < argc; ++i ) { + XP_LOGF( "%s", argv[i] ); + } + } +#endif + + memset( &mainParams, 0, sizeof(mainParams) ); + + mainParams.util = malloc( sizeof(*mainParams.util) ); + XP_MEMSET( mainParams.util, 0, sizeof(*mainParams.util) ); + +#ifdef MEM_DEBUG + mainParams.util->mpool = mpool_make(); +#endif + + mainParams.vtMgr = make_vtablemgr(MPPARM_NOCOMMA(mainParams.util->mpool)); + + /* fprintf( stdout, "press to start\n" ); */ + /* (void)fgetc( stdin ); */ + + /* defaults */ +#ifdef XWFEATURE_RELAY + mainParams.connInfo.relay.defaultSendPort = DEFAULT_PORT; + mainParams.connInfo.relay.cookie = "COOKIE"; +#endif +#ifdef XWFEATURE_IP_DIRECT + mainParams.connInfo.ip.port = DEFAULT_PORT; + mainParams.connInfo.ip.hostName = "localhost"; +#endif + mainParams.gi.boardSize = 15; + mainParams.quitAfter = -1; + mainParams.sleepOnAnchor = XP_FALSE; + mainParams.printHistory = XP_FALSE; + mainParams.undoWhenDone = XP_FALSE; + mainParams.gi.timerEnabled = XP_FALSE; + mainParams.gi.robotSmartness = SMART_ROBOT; + mainParams.noHeartbeat = XP_FALSE; + mainParams.nHidden = 0; +#ifdef XWFEATURE_SEARCHLIMIT + mainParams.allowHintRect = XP_TRUE; +#endif + + /* serverName = mainParams.info.clientInfo.serverName = "localhost"; */ + +#if defined PLATFORM_GTK + useGTK = 1; + useCurses = 0; +#else /* curses is the default if GTK isn't available */ + useGTK = 0; + useCurses = 1; +#endif + + + + do { + short index; + opt = getopt( argc, argv, "?" +#if defined PLATFORM_GTK && defined PLATFORM_NCURSES + "gu" +#endif +#if defined PLATFORM_GTK + "h:I" +#endif + "kKf:ln:Nsd:e:r:b:q:w:Sit:Umvc" +#ifdef XWFEATURE_RELAY + "a:p:C:H" +#endif +#if defined XWFEATURE_RELAY || defined XWFEATURE_IP_DIRECT + "p:" +#endif +#ifdef XWFEATURE_BLUETOOTH + "B:" +#endif +#ifdef XWFEATURE_IP_DIRECT + "D:" +#endif + ); + switch( opt ) { + case '?': + usage(argv[0], NULL); + break; + case 'c': + mainParams.showRobotScores = XP_TRUE; + break; +#ifdef XWFEATURE_RELAY + case 'C': + XP_ASSERT( conType == COMMS_CONN_NONE || + conType == COMMS_CONN_RELAY ); + mainParams.connInfo.relay.cookie = optarg; + conType = COMMS_CONN_RELAY; + break; +#endif + case 'D': + XP_ASSERT( conType == COMMS_CONN_NONE || + conType == COMMS_CONN_IP_DIRECT ); + hostName = optarg; + conType = COMMS_CONN_IP_DIRECT; + break; + case 'd': + mainParams.gi.dictName = copyString( mainParams.util->mpool, + (XP_UCHAR*)optarg ); + break; + case 'e': + seed = atoi(optarg); + break; + case 'f': + mainParams.fileName = optarg; + break; + case 'i': + mainParams.printHistory = 1; + break; +#ifdef XWFEATURE_SEARCHLIMIT + case 'I': + mainParams.allowHintRect = XP_FALSE; + break; +#endif + case 'K': + mainParams.skipWarnings = 1; + break; + case 'w': + mainParams.gi.players[mainParams.nLocalPlayers-1].password + = (XP_UCHAR*)optarg; + break; + case 'm': /* dumb robot */ + mainParams.gi.robotSmartness = DUMB_ROBOT; + break; + case 'l': + mainParams.gi.hintsNotAllowed = XP_TRUE; + break; + case 'n': + index = mainParams.gi.nPlayers++; + ++mainParams.nLocalPlayers; + mainParams.gi.players[index].isRobot = XP_FALSE; + mainParams.gi.players[index].isLocal = XP_TRUE; + mainParams.gi.players[index].name = + copyString( mainParams.util->mpool, (XP_UCHAR*)optarg); + break; + case 'N': + index = mainParams.gi.nPlayers++; + mainParams.gi.players[index].isLocal = XP_FALSE; + ++mainParams.info.serverInfo.nRemotePlayers; + break; + case 'p': + /* could be RELAY or IP_DIRECT */ + portNum = optarg; + break; + case 'r': + ++robotCount; + index = mainParams.gi.nPlayers++; + ++mainParams.nLocalPlayers; + mainParams.gi.players[index].isRobot = XP_TRUE; + mainParams.gi.players[index].isLocal = XP_TRUE; + mainParams.gi.players[index].name = + copyString( mainParams.util->mpool, (XP_UCHAR*)optarg); + break; + case 's': + isServer = XP_TRUE; + break; + case 'S': + mainParams.sleepOnAnchor = XP_TRUE; + break; + case 't': + mainParams.gi.gameSeconds = atoi(optarg) * 60; + mainParams.gi.timerEnabled = XP_TRUE; + break; + case 'U': + mainParams.undoWhenDone = XP_TRUE; + break; + case 'H': + mainParams.noHeartbeat = XP_TRUE; + break; + case 'a': + /* mainParams.info.clientInfo.serverName = */ + XP_ASSERT( conType == COMMS_CONN_NONE || + conType == COMMS_CONN_RELAY ); + conType = COMMS_CONN_RELAY; + hostName = optarg; + break; + case 'q': + mainParams.quitAfter = atoi(optarg); + break; + case 'b': + mainParams.gi.boardSize = atoi(optarg); + break; +#ifdef XWFEATURE_BLUETOOTH + case 'B': + XP_ASSERT( conType == COMMS_CONN_NONE || + conType == COMMS_CONN_BT ); + conType = COMMS_CONN_BT; + btaddr = optarg; + break; +#endif + case 'v': + mainParams.verticalScore = XP_TRUE; + break; +#if defined PLATFORM_GTK && defined PLATFORM_NCURSES + case 'g': + useGTK = 1; + break; + case 'u': + useCurses = 1; + useGTK = 0; + break; +#endif +#if defined PLATFORM_GTK + case 'k': + mainParams.askNewGame = XP_TRUE; + break; + case 'h': + mainParams.nHidden = atoi(optarg); + break; +#endif + } + } while ( opt != -1 ); + + XP_ASSERT( mainParams.gi.nPlayers == mainParams.nLocalPlayers + + mainParams.info.serverInfo.nRemotePlayers ); + + if ( isServer ) { + if ( mainParams.info.serverInfo.nRemotePlayers == 0 ) { + mainParams.gi.serverRole = SERVER_STANDALONE; + } else { + mainParams.gi.serverRole = SERVER_ISSERVER; + } + } else { + mainParams.gi.serverRole = SERVER_ISCLIENT; + } + + /* sanity checks */ + totalPlayerCount = mainParams.nLocalPlayers + + mainParams.info.serverInfo.nRemotePlayers; + if ( !mainParams.fileName ) { + if ( (totalPlayerCount < 1) || + (totalPlayerCount > MAX_NUM_PLAYERS) ) { + usage( argv[0], "Need between 1 and 4 players" ); + } + } + + if ( !!mainParams.gi.dictName ) { + mainParams.dict = linux_dictionary_make( + MPPARM(mainParams.util->mpool) mainParams.gi.dictName ); + XP_ASSERT( !!mainParams.dict ); + } else if ( isServer ) { +#ifdef STUBBED_DICT + mainParams.dict = make_stubbed_dict( + MPPARM_NOCOMMA(mainParams.util->mpool) ); + XP_WARNF( "no dictionary provided: using English stub dict\n" ); +#else + usage( argv[0], "Server needs a dictionary" ); +#endif + } else if ( robotCount > 0 ) { + usage( argv[0], "Client can't have robots without a dictionary" ); + } + + if ( !isServer ) { + if ( mainParams.info.serverInfo.nRemotePlayers > 0 ) { + usage( argv[0], "Client can't have remote players" ); + } + } + + if ( 0 ) { +#ifdef XWFEATURE_RELAY + } else if ( conType == COMMS_CONN_RELAY ) { + mainParams.connInfo.relay.relayName = hostName; + + /* convert strings to whatever */ + if ( portNum != NULL ) { + mainParams.connInfo.relay.defaultSendPort = + atoi( portNum ); + } +#endif +#ifdef XWFEATURE_IP_DIRECT + } else if ( conType == COMMS_CONN_IP_DIRECT ) { + mainParams.connInfo.ip.hostName = hostName; + if ( portNum != NULL ) { + mainParams.connInfo.ip.port = atoi( portNum ); + } +#endif +#ifdef XWFEATURE_BLUETOOTH + } else if ( conType == COMMS_CONN_BT ) { + bdaddr_t ba; + XP_Bool success; + XP_ASSERT( btaddr ); + if ( isServer ) { + success = XP_TRUE; + /* any format is ok */ + } else if ( btaddr[1] == ':' ) { + success = XP_FALSE; + if ( btaddr[0] == 'n' ) { + if ( !nameToBtAddr( btaddr+2, &ba ) ) { + fprintf( stderr, "fatal error: unable to find device %s\n", + btaddr + 2 ); + exit(0); + } + success = XP_TRUE; + } else if ( btaddr[0] == 'a' ) { + success = 0 == str2ba( &btaddr[2], &ba ); + XP_ASSERT( success ); + } + } + if ( !success ) { + usage( argv[0], "bad format for -B param" ); + } + XP_MEMCPY( &mainParams.connInfo.bt.hostAddr, &ba, + sizeof(mainParams.connInfo.bt.hostAddr) ); +#endif + } + mainParams.conType = conType; + + /* mainParams.pipe = linuxCommPipeCtxtMake( isServer ); */ + + /* mainParams.util->vtable->m_util_makeStreamFromAddr = */ + /* linux_util_makeStreamFromAddr; */ + + mainParams.util->gameInfo = &mainParams.gi; + + linux_util_vt_init( MPPARM(mainParams.util->mpool) mainParams.util ); + +#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH + mainParams.util->vtable->m_util_addrChange = linux_util_addrChange; +#endif + + srandom( seed ); /* init linux random number generator */ + XP_LOGF( "seeded srandom with %d", seed ); + + if ( isServer ) { + if ( mainParams.info.serverInfo.nRemotePlayers == 0 ) { + mainParams.serverRole = SERVER_STANDALONE; + } else { + mainParams.serverRole = SERVER_ISSERVER; + } + } else { + mainParams.serverRole = SERVER_ISCLIENT; + } + + if ( mainParams.nLocalPlayers > 0 || !!mainParams.fileName) { + if ( useCurses ) { +#if defined PLATFORM_NCURSES + cursesmain( isServer, &mainParams ); +#endif + } else { +#if defined PLATFORM_GTK + gtkmain( &mainParams, argc, argv ); +#endif + } + } else { + /* run server as faceless process? */ + } + + dict_destroy( mainParams.dict ); + linux_util_vt_destroy( mainParams.util ); + XP_LOGF( "exiting main" ); + return 0; +} /* main */ + diff --git a/xwords4/linux/linuxmain.h b/xwords4/linux/linuxmain.h new file mode 100644 index 000000000..a4751ea8c --- /dev/null +++ b/xwords4/linux/linuxmain.h @@ -0,0 +1,73 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make -k";-*- */ +/* + * Copyright 1997-2008 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef _LINUXMAIN_H_ +#define _LINUXMAIN_H_ + +#include "main.h" +#include "dictnry.h" +#include "mempool.h" +#include "comms.h" +#include "memstream.h" +/* #include "compipe.h" */ + +extern int errno; + +typedef struct LinuxBMStruct { + XP_U8 nCols; + XP_U8 nRows; + XP_U8 nBytes; +} LinuxBMStruct; + +int initListenerSocket( int port ); +XP_S16 linux_send( const XP_U8* buf, XP_U16 buflen, + const CommsAddrRec* addrRec, void* closure ); +#ifndef XWFEATURE_STANDALONE_ONLY +# define LINUX_SEND linux_send +#else +# define LINUX_SEND NULL +#endif + +#ifdef COMMS_HEARTBEAT +void linux_reset( void* closure ); +#endif +int linux_relay_receive( CommonGlobals* cGlobals, unsigned char* buf, + int bufSize ); + +void linuxFireTimer( CommonGlobals* cGlobals, XWTimerReason why ); + + +XWStreamCtxt* stream_from_msgbuf( CommonGlobals* cGlobals, + unsigned char* bufPtr, XP_U16 nBytes ); +XP_UCHAR* strFromStream( XWStreamCtxt* stream ); + +void catGameHistory( CommonGlobals* cGlobals ); +void catOnClose( XWStreamCtxt* stream, void* closure ); +XP_Bool file_exists( const char* fileName ); +XWStreamCtxt* streamFromFile( CommonGlobals* cGlobals, char* name, + void* closure ); +void writeToFile( XWStreamCtxt* stream, void* closure ); + + +#ifdef KEYBOARD_NAV +XP_Bool linShiftFocus( CommonGlobals* cGlobals, XP_Key key, + const BoardObjectType* order, + BoardObjectType* nxtP ); +#endif + +#endif diff --git a/xwords4/linux/linuxserver.c b/xwords4/linux/linuxserver.c new file mode 100644 index 000000000..c018e7d78 --- /dev/null +++ b/xwords4/linux/linuxserver.c @@ -0,0 +1,34 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#include "linuxserver.h" + + +ServerCtxt* +linux_make_server( char* dictName, XP_U16 totalPlayerCount ) +{ + return NULL; +} /* linux_make_server */ + +ServerCtxt* +linux_make_serverProxy( char* serverAddrStr, XP_U16 totalPlayerCount ) +{ + return NULL; +} /* linux_make_serverProxy */ diff --git a/xwords4/linux/linuxserver.h b/xwords4/linux/linuxserver.h new file mode 100644 index 000000000..030898d47 --- /dev/null +++ b/xwords4/linux/linuxserver.h @@ -0,0 +1,31 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _LINUXSERVER_H_ +#define _LINUXSERVER_H_ + +#include "server.h" + +ServerCtxt* linux_make_server( char* dictName, XP_U16 totalPlayerCount ); + +ServerCtxt* linux_make_serverProxy( char* serverAddrStr, + XP_U16 totalPlayerCount ); + + +#endif diff --git a/xwords4/linux/linuxudp.c b/xwords4/linux/linuxudp.c new file mode 100644 index 000000000..c40117f5a --- /dev/null +++ b/xwords4/linux/linuxudp.c @@ -0,0 +1,305 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE";-*- */ +/* + * Copyright 2007 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef XWFEATURE_IP_DIRECT + +#include +#include +#include +#include +#include +#ifdef XWFEATURE_BLUETOOTH +# include +# if defined BT_USE_L2CAP +# include +# elif defined BT_USE_RFCOMM +# include +# endif +# include +# include +#endif + +#include "linuxudp.h" +#include "comms.h" +#include "strutils.h" + +/* Conecting: Expectation is that the client initiates the connection to a + * known port on the server and that the server uses return addresses to reach + * the client. This works for games started from scratch. But when we start + * from saved games it doesn't: the server knows whatever ephemeral port the + * client was on before, but that's all. The client needs to ping the server + * immediately, perhaps in a way analogous to btStartup. + */ + + +typedef struct LinUDPStuff { + CommonGlobals* globals; + CommsAddrRec addr; + XP_Bool isServer; + void* storage; + int knownSocks[MAX_NUM_PLAYERS]; + int nKnownSocks; + int socket; /* host opens to receive; guest opens to send */ +} LinUDPStuff; + +void +linux_udp_open( CommonGlobals* globals, const CommsAddrRec* newAddr ) +{ + LOG_FUNC(); + LinUDPStuff* stuff = globals->udpStuff; + if ( !stuff ) { + struct sockaddr_in saddr; + int err; + + stuff = XP_MALLOC( globals->params->util->mpool, sizeof(*stuff) ); + XP_MEMSET( stuff, 0, sizeof(*stuff) ); + XP_MEMCPY( &stuff->addr, newAddr, sizeof(stuff->addr) ); + + globals->udpStuff = stuff; + stuff->globals = globals; + stuff->socket = -1; + + stuff->isServer = comms_getIsServer( globals->game.comms ); + + if ( stuff->isServer ) { + int listenSock; + listenSock = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); + saddr.sin_family = PF_INET; + saddr.sin_addr.s_addr = htonl(INADDR_ANY); + saddr.sin_port = htons(stuff->addr.u.ip.port_ip); + XP_LOGF( "binding listen socket" ); + err = bind( listenSock, (struct sockaddr *)&saddr, sizeof(saddr) ); + if ( 0 != err ) { + XP_LOGF( "bind()=>%s", strerror(errno) ); + listenSock = -1; + } + stuff->socket = listenSock; + (*globals->socketChanged)( globals->socketChangedClosure, -1, + listenSock, &stuff->storage ); + } + + } + LOG_RETURN_VOID(); +} /* linux_udp_open */ + +void +linux_udp_reset( CommonGlobals* globals ) +{ + CommsAddrRec addr; + LinUDPStuff* stuff = globals->udpStuff; + LOG_FUNC(); + XP_ASSERT( stuff ); + if ( !!stuff ) { + XP_MEMCPY( &addr, &stuff->addr, sizeof(addr) ); + linux_udp_close( globals ); + } + sleep( 1 ); + linux_udp_open( globals, &addr ); + LOG_RETURN_VOID(); +} + +void +linux_udp_close( CommonGlobals* globals ) +{ + LinUDPStuff* stuff = globals->udpStuff; + LOG_FUNC(); + if ( !!stuff ) { + if ( stuff->socket != -1 ) { + XP_LOGF( "closed socket %d", stuff->socket ); + (*globals->socketChanged)( globals->socketChangedClosure, stuff->socket, -1, + &stuff->storage ); + close( stuff->socket ); + stuff->socket = -1; + } else { + XP_LOGF( "no socket to close" ); + } + XP_FREE( globals->params->util->mpool, stuff ); + globals->udpStuff = NULL; + } + LOG_RETURN_VOID(); +} + +static XP_U32 +addrForHost( const CommsAddrRec* addr ) +{ + struct hostent* host; + XP_U32 ip = addr->u.ip.ipAddr_ip; + + if ( 0L == ip ) { + host = gethostbyname( addr->u.ip.hostName_ip ); + if ( NULL == host ) { + XP_WARNF( "gethostbyname returned -1\n" ); + } else { + XP_MEMCPY( &ip, host->h_addr_list[0], sizeof(ip) ); + ip = ntohl(ip); + } + XP_LOGF( "%s found %lx for %s", __func__, ip, addr->u.ip.hostName_ip ); + } + return ip; +} /* addrForHost */ + +static void +addressToServer( struct sockaddr_in* to, const CommsAddrRec* addr ) +{ + to->sin_family = PF_INET; + to->sin_addr.s_addr = htonl( addrForHost(addr) ); + to->sin_port = htons(addr->u.ip.port_ip); +} + +static void +remember( LinUDPStuff* stuff, int sock ) +{ + XP_U16 i; + XP_Bool known; + for ( i = 0, known = XP_FALSE; + !known && i < sizeof(stuff->knownSocks)/sizeof(stuff->knownSocks[0]); + ++i ) { + if ( stuff->knownSocks[i] == sock ) { + known = XP_TRUE; + } + } + + if ( !known ) { + XP_ASSERT( stuff->nKnownSocks + < sizeof(stuff->knownSocks)/sizeof(stuff->knownSocks[0])-1 ); + XP_LOGF( "%s recording %d", __func__, sock ); + stuff->knownSocks[stuff->nKnownSocks++] = sock; + } +} + +static XP_Bool +remembered( const LinUDPStuff* stuff, int sock ) +{ + XP_Bool known = XP_FALSE; + XP_U16 i; + for ( i = 0; i < stuff->nKnownSocks; ++i ) { + XP_ASSERT( i < sizeof(stuff->knownSocks)/sizeof(stuff->knownSocks[0]) ); + if ( stuff->knownSocks[i] == sock ) { + known = XP_TRUE; + } + } + LOG_RETURNF( "%d", (int)known ); + return known; +} + +static XP_Bool +addressToClient( LinUDPStuff* stuff, struct sockaddr_in* to, const CommsAddrRec* addr ) +{ + int port = addr->u.ip.port_ip; + XP_Bool known = remembered( stuff, port ); + + if ( known ) { + to->sin_family = PF_INET; + to->sin_addr.s_addr = htonl(addr->u.ip.ipAddr_ip); + to->sin_port = htons(port); + } + return known; +} + +XP_S16 +linux_udp_send( const XP_U8* buf, XP_U16 buflen, const CommsAddrRec* addrp, + CommonGlobals* globals ) +{ + LinUDPStuff* stuff = globals->udpStuff; + ssize_t nSent = -1; + + LOG_FUNC(); + if ( NULL != stuff ) { + XP_Bool haveAddress = XP_TRUE; + CommsAddrRec addr; + if ( !addrp ) { + comms_getAddr( globals->game.comms, &addr ); + addrp = &addr; + } + + struct sockaddr_in to; + XP_MEMSET( &to, 0, sizeof(to) ); + + if ( stuff->isServer ) { + if ( !addressToClient( stuff, &to, addrp ) ) { + haveAddress = XP_FALSE; + } + } else { + if ( stuff->socket == -1 ) { + stuff->socket = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); + XP_LOGF( "%s: client made socket = %d", __func__, stuff->socket ); + (*stuff->globals->socketChanged)( stuff->globals->socketChangedClosure, + -1, stuff->socket, &stuff->storage ); + } + addressToServer( &to, addrp ); + } + + if ( haveAddress ) { + XP_LOGF( "calling sendto: sock=%d; port=%d; ipaddr=%x", + stuff->socket, ntohs(to.sin_port), to.sin_addr.s_addr ); + nSent = sendto( stuff->socket, buf, buflen, 0, + (struct sockaddr*)&to, sizeof(to) ); + if ( nSent != buflen ) { + XP_LOGF( "sendto->%s", strerror(errno) ); + } + } + } + + LOG_RETURNF( "%d", nSent ); + return nSent; +} /* linux_udp_send */ + +XP_S16 +linux_udp_receive( int sock, XP_U8* buf, XP_U16 buflen, CommsAddrRec* addr, + CommonGlobals* globals ) +{ + struct sockaddr_in from; + socklen_t fromlen = buflen; + + XP_LOGF( "%s: calling recvfrom on socket %d", __func__, sock ); + + ssize_t nRead = recvfrom( sock, buf, buflen, 0, /* flags */ + (struct sockaddr*)&from, &fromlen ); + XP_ASSERT( nRead > 0 ); + XP_LOGF( "%s read %d bytes", __func__, nRead ); + + if ( nRead > 0 ) { + int port; + XP_MEMSET( addr, 0, sizeof(*addr) ); + addr->conType = COMMS_CONN_IP_DIRECT; + port = ntohs(from.sin_port); + addr->u.ip.port_ip = port; + addr->u.ip.ipAddr_ip = ntohl(from.sin_addr.s_addr); + + if ( globals->udpStuff->isServer ) { + remember( globals->udpStuff, port ); + } + } + + return nRead; +} + +void +linux_udp_socketclosed( CommonGlobals* globals, int sock ) +{ + LinUDPStuff* stuff = globals->udpStuff; + LOG_FUNC(); + XP_ASSERT( !!stuff ); + XP_ASSERT( stuff->socket == sock ); + stuff->socket = -1; + LOG_RETURN_VOID(); +} + +#endif /* XWFEATURE_IP_DIRECT */ diff --git a/xwords4/linux/linuxudp.h b/xwords4/linux/linuxudp.h new file mode 100644 index 000000000..3be85a1a0 --- /dev/null +++ b/xwords4/linux/linuxudp.h @@ -0,0 +1,40 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE";-*- */ +/* + * Copyright 2007 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef _LINUXUDP_H_ +#define _LINUXUD_H_ + +#ifdef XWFEATURE_IP_DIRECT + +#include "main.h" + +void linux_udp_open( CommonGlobals* globals, const CommsAddrRec* newAddr ); +void linux_udp_reset( CommonGlobals* globals ); +void linux_udp_close( CommonGlobals* globals ); + +XP_S16 linux_udp_send( const XP_U8* buf, XP_U16 buflen, + const CommsAddrRec* addrRec, + CommonGlobals* globals ); +XP_S16 linux_udp_receive( int sock, XP_U8* buf, XP_U16 buflen, CommsAddrRec* addr, + CommonGlobals* globals ); + +void linux_udp_socketclosed( CommonGlobals* globals, int sock ); + +#endif /* XWFEATURE_IP_DIRECT */ +#endif /* #ifndef _LINUXUD_H_ */ diff --git a/xwords4/linux/linuxutl.c b/xwords4/linux/linuxutl.c new file mode 100644 index 000000000..135d0e4f0 --- /dev/null +++ b/xwords4/linux/linuxutl.c @@ -0,0 +1,297 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE"; -*- */ +/* + * Copyright 2000-2008 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include + +#include "linuxutl.h" +#include "LocalizedStrIncludes.h" + +#ifdef DEBUG +void +linux_debugf( const char* format, ... ) +{ + char buf[1000]; + va_list ap; + // time_t tim; + struct tm* timp; + struct timeval tv; + struct timezone tz; +/* pthread_t me = pthread_self(); */ + + gettimeofday( &tv, &tz ); + timp = localtime( &tv.tv_sec ); + + sprintf( buf, /* "<%p>" */ "%d:%d:%d: ", /* (void*)me, */ + timp->tm_hour, timp->tm_min, timp->tm_sec ); + + va_start(ap, format); + + vsprintf(buf+strlen(buf), format, ap); + + va_end(ap); + + fprintf( stderr, "%s\n", buf ); +} +#endif + +static DictionaryCtxt* +linux_util_makeEmptyDict( XW_UtilCtxt* uctx ) +{ + XP_DEBUGF( "linux_util_makeEmptyDict called\n" ); + return linux_dictionary_make( MPPARM(uctx->mpool) NULL ); +} /* linux_util_makeEmptyDict */ + +#define EM BONUS_NONE +#define DL BONUS_DOUBLE_LETTER +#define DW BONUS_DOUBLE_WORD +#define TL BONUS_TRIPLE_LETTER +#define TW BONUS_TRIPLE_WORD + +static XWBonusType +linux_util_getSquareBonus( XW_UtilCtxt* XP_UNUSED(uc), + const ModelCtxt* XP_UNUSED(model), + XP_U16 col, XP_U16 row ) +{ + XP_U16 index; + /* This must be static or won't compile under multilink (for Palm). + Fix! */ + static char scrabbleBoard[8*8] = { + TW,EM,EM,DL,EM,EM,EM,TW, + EM,DW,EM,EM,EM,TL,EM,EM, + + EM,EM,DW,EM,EM,EM,DL,EM, + DL,EM,EM,DW,EM,EM,EM,DL, + + EM,EM,EM,EM,DW,EM,EM,EM, + EM,TL,EM,EM,EM,TL,EM,EM, + + EM,EM,DL,EM,EM,EM,DL,EM, + TW,EM,EM,DL,EM,EM,EM,DW, + }; /* scrabbleBoard */ + + if ( col > 7 ) col = 14 - col; + if ( row > 7 ) row = 14 - row; + index = (row*8) + col; + if ( index >= 8*8 ) { + return (XWBonusType)EM; + } else { + return (XWBonusType)scrabbleBoard[index]; + } +} /* linux_util_getSquareBonus */ + +static XP_U32 +linux_util_getCurSeconds( XW_UtilCtxt* XP_UNUSED(uc) ) +{ + return (XP_U32)time(NULL);//tv.tv_sec; +} /* gtk_util_getCurSeconds */ + +static const XP_UCHAR* +linux_util_getUserString( XW_UtilCtxt* XP_UNUSED(uc), XP_U16 code ) +{ + switch( code ) { + case STRD_REMAINING_TILES_ADD: + return (XP_UCHAR*)"+ %d [all remaining tiles]"; + case STRD_UNUSED_TILES_SUB: + return (XP_UCHAR*)"- %d [unused tiles]"; + case STR_COMMIT_CONFIRM: + return (XP_UCHAR*)"Are you sure you want to commit the current move?\n"; + case STRD_TURN_SCORE: + return (XP_UCHAR*)"Score for turn: %d\n"; + case STR_BONUS_ALL: + return (XP_UCHAR*)"Bonus for using all tiles: 50\n"; + case STR_LOCAL_NAME: + return (XP_UCHAR*)"%s"; + case STR_NONLOCAL_NAME: + return (XP_UCHAR*)"%s (remote)"; + case STRD_TIME_PENALTY_SUB: + return (XP_UCHAR*)" - %d [time]"; + /* added.... */ + case STRD_CUMULATIVE_SCORE: + return (XP_UCHAR*)"Cumulative score: %d\n"; + case STRS_TRAY_AT_START: + return (XP_UCHAR*)"Tray at start: %s\n"; + case STRS_MOVE_DOWN: + return (XP_UCHAR*)"move (from %s down)\n"; + case STRS_MOVE_ACROSS: + return (XP_UCHAR*)"move (from %s across)\n"; + case STRS_NEW_TILES: + return (XP_UCHAR*)"New tiles: %s\n"; + case STRSS_TRADED_FOR: + return (XP_UCHAR*)"Traded %s for %s."; + case STR_PASS: + return (XP_UCHAR*)"pass\n"; + case STR_PHONY_REJECTED: + return (XP_UCHAR*)"Illegal word in move; turn lost!\n"; + + case STRD_ROBOT_TRADED: + return (XP_UCHAR*)"%d tiles traded this turn."; + case STR_ROBOT_MOVED: + return (XP_UCHAR*)"The robot moved:\n"; + case STR_REMOTE_MOVED: + return (XP_UCHAR*)"Remote player moved:\n"; + + case STR_PASSED: + return (XP_UCHAR*)"Passed"; + case STRSD_SUMMARYSCORED: + return (XP_UCHAR*)"%s:%d"; + case STRD_TRADED: + return (XP_UCHAR*)"Traded %d"; + case STR_LOSTTURN: + return (XP_UCHAR*)"Lost turn"; + +#ifndef XWFEATURE_STANDALONE_ONLY + case STR_LOCALPLAYERS: + return (XP_UCHAR*)"Local players"; + case STR_REMOTE: + return (XP_UCHAR*)"Remote"; +#endif + case STR_TOTALPLAYERS: + return (XP_UCHAR*)"Total players"; + + case STRS_VALUES_HEADER: + return (XP_UCHAR*)"%s counts/values:\n"; + + default: + return (XP_UCHAR*)"unknown code to linux_util_getUserString"; + } +} /* linux_util_getUserString */ + +void +linux_util_vt_init( MPFORMAL XW_UtilCtxt* util ) +{ + util->vtable = XP_MALLOC( mpool, sizeof(UtilVtable) ); + + util->vtable->m_util_makeEmptyDict = linux_util_makeEmptyDict; + util->vtable->m_util_getSquareBonus = linux_util_getSquareBonus; + util->vtable->m_util_getCurSeconds = linux_util_getCurSeconds; + util->vtable->m_util_getUserString = linux_util_getUserString; + +} + +void +linux_util_vt_destroy( XW_UtilCtxt* util ) +{ + XP_FREE( util->mpool, util->vtable ); +} + +const XP_UCHAR* +linux_getErrString( UtilErrID id, XP_Bool* silent ) +{ + *silent = XP_FALSE; + const char* message = NULL; + + switch( id ) { + case ERR_TILES_NOT_IN_LINE: + message = "All tiles played must be in a line."; + break; + case ERR_NO_EMPTIES_IN_TURN: + message = "Empty squares cannot separate tiles played."; + break; + + case ERR_TOO_FEW_TILES_LEFT_TO_TRADE: + message = "Too few tiles left to trade."; + break; + + case ERR_TWO_TILES_FIRST_MOVE: + message = "Must play two or more pieces on the first move."; + break; + case ERR_TILES_MUST_CONTACT: + message = "New pieces must contact others already in place (or " + "the middle square on the first move)."; + break; + case ERR_NOT_YOUR_TURN: + message = "You can't do that; it's not your turn!"; + break; + case ERR_NO_PEEK_ROBOT_TILES: + message = "No peeking at the robot's tiles!"; + break; + +#ifndef XWFEATURE_STANDALONE_ONLY + case ERR_NO_PEEK_REMOTE_TILES: + message = "No peeking at remote players' tiles!"; + break; + case ERR_REG_UNEXPECTED_USER: + message = "Refused attempt to register unexpected user[s]."; + break; + case ERR_SERVER_DICT_WINS: + message = "Conflict between Host and Guest dictionaries; Host wins."; + XP_WARNF( "GTK may have problems here." ); + break; + case ERR_REG_SERVER_SANS_REMOTE: + message = "At least one player must be marked remote for a game " + "started as Host."; + break; +#endif + + case ERR_CANT_TRADE_MID_MOVE: + message = "Remove played tiles before trading."; + break; + + case ERR_CANT_UNDO_TILEASSIGN: + message = "Tile assignment can't be undone."; + break; + + case ERR_CANT_HINT_WHILE_DISABLED: + message = "The hint feature is disabled for this game. Enable " + "it for a new game using the Preferences dialog."; + break; + +/* case INFO_REMOTE_CONNECTED: */ +/* message = "Another device has joined the game"; */ +/* break; */ + + case ERR_RELAY_BASE + XWRELAY_ERROR_LOST_OTHER: + *silent = XP_TRUE; + message = "XWRELAY_ERROR_LOST_OTHER"; + break; + + case ERR_RELAY_BASE + XWRELAY_ERROR_TIMEOUT: + message = "The relay timed you out; other players " + "have left or never showed up."; + break; + + case ERR_RELAY_BASE + XWRELAY_ERROR_HEART_YOU: + message = "You were disconnected from relay because it didn't " + "hear from you in too long."; + break; + case ERR_RELAY_BASE + XWRELAY_ERROR_HEART_OTHER: +/* *silent = XP_TRUE; */ + message = "The relay has lost contact with a device in this game."; + break; + + case ERR_RELAY_BASE + XWRELAY_ERROR_OLDFLAGS: + message = "You need to upgrade your copy of Crosswords."; + break; + + case ERR_RELAY_BASE + XWRELAY_ERROR_SHUTDOWN: + message = "Relay disconnected you to shut down (and probably reboot)."; + break; + + default: + XP_LOGF( "no code for error: %d", id ); + message = ""; + } + + return (XP_UCHAR*)message; +} /* linux_getErrString */ diff --git a/xwords4/linux/linuxutl.h b/xwords4/linux/linuxutl.h new file mode 100644 index 000000000..b963dfa4e --- /dev/null +++ b/xwords4/linux/linuxutl.h @@ -0,0 +1,41 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make MEMDEBUG=TRUE"; -*- */ +/* + * Copyright 2000-2008 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef _LINUXUTL_H_ +#define _LINUXUTL_H_ + +#include "xptypes.h" +#include "dictnry.h" +#include "util.h" + +#ifdef DEBUG +void linux_debugf(const char*, ...) + __attribute__ ((format (printf, 1, 2))); +#endif + +DictionaryCtxt* linux_dictionary_make( MPFORMAL const char* dictFileName ); + +void linux_util_vt_init( MPFORMAL XW_UtilCtxt* util ); +void linux_util_vt_destroy( XW_UtilCtxt* util ); + +const XP_UCHAR* linux_getErrString( UtilErrID id, XP_Bool* silent ); + +#endif diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h new file mode 100644 index 000000000..d30161041 --- /dev/null +++ b/xwords4/linux/main.h @@ -0,0 +1,140 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001-2007 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _MAIN_H_ +#define _MAIN_H_ + +#ifdef XWFEATURE_BLUETOOTH +# include /* for bdaddr_t, which should move */ +#endif + +#include "comtypes.h" +#include "util.h" +#include "game.h" +#include "vtabmgr.h" + +typedef struct ServerInfo { + XP_U16 nRemotePlayers; +/* CommPipeCtxt* pipe; */ +} ServerInfo; + +typedef struct ClientInfo { +} ClientInfo; + +typedef struct LinuxUtilCtxt { + UtilVtable* vtable; +} LinuxUtilCtxt; + +typedef struct LaunchParams { +/* CommPipeCtxt* pipe; */ + XW_UtilCtxt* util; + DictionaryCtxt* dict; + CurGameInfo gi; + char* fileName; + VTableMgr* vtMgr; + XP_U16 nLocalPlayers; + XP_U16 nHidden; + XP_Bool askNewGame; + XP_S16 quitAfter; + XP_Bool sleepOnAnchor; + XP_Bool printHistory; + XP_Bool undoWhenDone; + XP_Bool verticalScore; + // XP_Bool mainParams; + XP_Bool skipWarnings; + XP_Bool showRobotScores; + XP_Bool noHeartbeat; +#ifdef XWFEATURE_SEARCHLIMIT + XP_Bool allowHintRect; +#endif + + DeviceRole serverRole; + + CommsConnType conType; + struct { +#ifdef XWFEATURE_RELAY + struct { + char* relayName; + char* cookie; + short defaultSendPort; + } relay; +#endif +#ifdef XWFEATURE_BLUETOOTH + struct { + bdaddr_t hostAddr; /* unused if a host */ + } bt; +#endif +#ifdef XWFEATURE_IP_DIRECT + struct { + const char* hostName; + int port; + } ip; +#endif + } connInfo; + + union { + ServerInfo serverInfo; + ClientInfo clientInfo; + } info; + +} LaunchParams; + +typedef struct CommonGlobals CommonGlobals; + +typedef void (*SocketChangedFunc)(void* closure, int oldsock, int newsock, + void** storage ); +typedef XP_Bool (*Acceptor)( int sock, void* ctxt ); +typedef void (*AddAcceptorFunc)(int listener, Acceptor func, + CommonGlobals* globals, void** storage ); + +struct CommonGlobals { + LaunchParams* params; + + XWGame game; + XP_U16 lastNTilesToUse; + + SocketChangedFunc socketChanged; + void* socketChangedClosure; + + /* Allow listener sockets to be installed in either gtk or ncurses' + * polling mechanism.*/ + AddAcceptorFunc addAcceptor; + Acceptor acceptor; + +#ifdef XWFEATURE_RELAY + int socket; /* relay */ + void* storage; + char* defaultServerName; +#endif + +#if defined XWFEATURE_BLUETOOTH + struct LinBtStuff* btStuff; +#endif +#if defined XWFEATURE_IP_DIRECT + struct LinUDPStuff* udpStuff; +#endif + + XWTimerProc timerProcs[NUM_TIMERS_PLUS_ONE]; + void* timerClosures[NUM_TIMERS_PLUS_ONE]; + + MPSLOT +}; + +#endif diff --git a/xwords4/linux/scripts/playnum.sh b/xwords4/linux/scripts/playnum.sh new file mode 100755 index 000000000..a153a3370 --- /dev/null +++ b/xwords4/linux/scripts/playnum.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# I use this thing this way: playnum.sh 10 2>&1 | ./wordlens.pl + +NUM=$1 +echo "NUM=$NUM" + +while :; do + + ../linux/xwords -u -s -r Eric -d ../linux/dicts/OSPD2to15.xwd -q -i + + NUM=$(( NUM - 1 )); + + if (( $NUM <= 0 )); then exit 0; fi +done \ No newline at end of file diff --git a/xwords4/linux/scripts/times.pl b/xwords4/linux/scripts/times.pl new file mode 100755 index 000000000..e15b0f20c --- /dev/null +++ b/xwords4/linux/scripts/times.pl @@ -0,0 +1,57 @@ +#!/usr/bin/perl + +# Taking the output of time on stdin, sum all of the entries and print +# out averages. + +use strict; + +#my %nSeen; +my %nSecs; +my $nDiscard = 0; + +use constant PATS => ("real", "sys", "user"); + +map { $nSecs{$_} = [];} PATS; + +my $parm = shift( @ARGV ); +if ( $parm eq "--discard" ) { + $nDiscard = shift( @ARGV ); +} elsif ( defined $parm ) { + print STDERR "usage: $0 [--discard num_high_and_low] < output_from_time\n"; + exit 0; +} + +while ( <> ) { + chomp; + foreach my $pat (PATS) { + tryOne( $pat, $_ ); + } +} + +print "results:\n"; +foreach my $pat (PATS) { + my $ref = $nSecs{$pat}; + my @locList = sort { $a <=> $b; } @$ref; + + # discard first and last from sorted list + splice @locList, 0, $nDiscard; + splice @locList, -$nDiscard; + + my $count = @locList; + if ( $count > 0 ) { + my $sum; + map { $sum += $_ } @locList; + printf "$pat: average for $count runs: %.3f\n", $sum/$count; + } +} + + +sub tryOne($$) { + my ( $pat, $str ) = @_; + + if ( $str =~ m|^$pat\s+(\d+)m(\d+\.\d+)s| ) { + push @{$nSecs{$pat}}, ($1*60) + $2; + } + + +} diff --git a/xwords4/linux/scripts/wordlens.pl b/xwords4/linux/scripts/wordlens.pl new file mode 100755 index 000000000..2d93911cb --- /dev/null +++ b/xwords4/linux/scripts/wordlens.pl @@ -0,0 +1,23 @@ +#!/usr/bin/perl + +use strict; + +my @counts; +my $nGames = 0; + +while ( <> ) { + chomp; + if ( m|^([A-Z]+) \[| ) { + my $len = length($1); + ++$counts[$len]; + } elsif ( m|^1:1| ) { + ++$nGames; + } +} + +print "****** out of $nGames games: *****\n"; +print "length num played\n"; +for ( my $i = 2; $i <= 15; ++$i ) { + printf( "%3d %8d\n", $i, $counts[$i] ); +} + diff --git a/xwords4/linux/value.xpm b/xwords4/linux/value.xpm new file mode 100644 index 000000000..1bd40e6bd --- /dev/null +++ b/xwords4/linux/value.xpm @@ -0,0 +1,18 @@ +/* XPM */ +static char * value_xpm[] = { +"8 10 5 1", +" c None", +". c #0F0F0F", +"+ c #2F2F2F", +"@ c #000000", +"# c #FFFFFF", +".+@@@@@@", +"+##@@@#@", +"@.#@@#@@", +"@###@#@@", +"@@@@#@@@", +"@@@#@#@@", +"@@#@#@#@", +"@@#@###@", +"@#@@#@#@", +"@@@@@@@@"}; diff --git a/xwords4/linux/xptypes.h b/xwords4/linux/xptypes.h new file mode 100644 index 000000000..4b8cfc4ae --- /dev/null +++ b/xwords4/linux/xptypes.h @@ -0,0 +1,120 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1997 - 2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _XPTYPES_H_ +#define _XPTYPES_H_ + +#include +#include +#include /* memset */ +#include /* memset */ +#include +#include + +#ifdef PLATFORM_GTK +# include +# include +# include +#endif + +#define XP_TRUE ((XP_Bool)(1==1)) +#define XP_FALSE ((XP_Bool)(1==0)) + +typedef unsigned char XP_U8; +typedef signed char XP_S8; +typedef char XP_UCHAR; + +typedef unsigned short XP_U16; +typedef signed short XP_S16; + +typedef unsigned long XP_U32; +typedef signed long XP_S32; + +typedef signed short XP_FontCode; /* not sure how I'm using this yet */ +typedef unsigned char XP_Bool; + +#ifdef PLATFORM_GTK +typedef guint32 XP_Time; +#else +typedef unsigned long XP_Time; +#endif + +#define XP_CR "\n" + +#define XP_STATUSF XP_DEBUGF +#define XP_LOGF XP_DEBUGF + +#ifdef DEBUG +extern void linux_debugf(const char*, ...) + __attribute__ ((format (printf, 1, 2))); +#define XP_DEBUGF(...) linux_debugf(__VA_ARGS__) + +#else +#define XP_DEBUGF(ch,...) +#endif + +#define XP_WARNF XP_DEBUGF + +#ifdef MEM_DEBUG + +# define XP_PLATMALLOC(nbytes) malloc(nbytes) +# define XP_PLATREALLOC(p,s) realloc((p),(s)) +# define XP_PLATFREE(p) free(p) + +#else + +# define XP_MALLOC(pool,nbytes) malloc(nbytes) +# define XP_REALLOC(pool,p,s) realloc((p),(s)) +# define XP_FREE(pool,p) free(p) +#endif + +#define XP_MEMSET(src, val, nbytes) memset( (src), (val), (nbytes) ) +#define XP_MEMCPY(d,s,l) memcpy((d),(s),(l)) +#define XP_MEMCMP( a1, a2, l ) memcmp((a1),(a2),(l)) +#define XP_STRLEN(s) strlen(s) +#define XP_STRCAT(d,s) strcat((d),(s)) +#define XP_STRNCMP(s1,s2,len) strncmp((s1),(s2),(len)) +#define XP_STRNCPY(s1,s2,len) strncpy((s1),(s2),(len)) +#define XP_STRCMP(s1,s2) strcmp((s1),(s2)) +#define XP_RANDOM() random() +#define XP_SNPRINTF snprintf + +#define XP_MIN(a,b) ((a)<(b)?(a):(b)) +#define XP_MAX(a,b) ((a)>(b)?(a):(b)) +#define XP_ABS(a) ((a)>=0?(a):-(a)) + +#ifdef DEBUG +# define XP_ASSERT(b) assert(b) +#else +# define XP_ASSERT(b) +#endif + +#define DGRAM_TYPE SOCK_DGRAM +/* #define DGRAM_TYPE SOCK_STREAM */ + +#define XP_NTOHL(l) ntohl(l) +#define XP_NTOHS(s) ntohs(s) +#define XP_HTONL(l) htonl(l) +#define XP_HTONS(s) htons(s) + +#define XP_LD "%ld" +#define XP_P "%p" + +#endif + diff --git a/xwords4/palm/.cvsignore b/xwords4/palm/.cvsignore new file mode 100644 index 000000000..2191c21a5 --- /dev/null +++ b/xwords4/palm/.cvsignore @@ -0,0 +1,12 @@ +*.out +*.bin +LocalizedStrIncludes.h +app.gdb +gdbload +palm +fnavgen +pace_gen.h +pace_gen.c +PNOC_bin.pre +PALM_PNO +_dirList diff --git a/xwords4/palm/Makefile b/xwords4/palm/Makefile new file mode 100644 index 000000000..81cb5dfb0 --- /dev/null +++ b/xwords4/palm/Makefile @@ -0,0 +1,337 @@ +# -*- mode: Makefile; compile-command: "make ARCH=68K_ONLY MEMDEBUG=TRUE"; -*- +# Copyright 2002-2006 by Eric House (xwords@eehouse.org). All rights +# reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +PLATFORM=obj_palm +SVNREV = $(shell svnversion -n .. | tr ':' '_') + +ifeq ($(MEMDEBUG),TRUE) +DEBUG = -DMEM_DEBUG -DDEBUG -DENABLE_LOGGING +PLATFORM := $(PLATFORM)_memdebug +CFLAGS += $(DEBUG) -g -O0 +CSFLAGS += -O0 -g +NAME = "CrossDbg" +ICONTEXT = "CrossDbg" +APPID ?= Xwdb +else +PLATFORM := $(PLATFORM)_rel +CFLAGS += -O2 -fomit-frame-pointer +# The -Os flag breaks multilink, at least as I'm using it +CSFLAGS += -OS -fomit-frame-pointer +NAME = "Crosswords" +ICONTEXT = "Crosswords" +APPID ?= Xwr4 +endif + +LANG=en_US +ROOTNAME = $(NAME) +BITMAPS = ./bmps +TYPE = appl +MLPREFIX = /usr +DICT = $(shell echo $$BASENG_PATH) + +ARCH ?= ARM_ONLY + +ifeq ($(ARCH),ARM_ONLY) +PLATFORM := $(PLATFORM)_armonly +BUILD_AS_PNOLET = 1 +TARGET=$(PLATFORM)/xw4_ARM_$(SVNREV).prc +else +ifeq ($(ARCH),68K_ONLY) +PLATFORM := $(PLATFORM)_68konly +TARGET=$(PLATFORM)/xw4_68K_$(SVNREV).prc +else +ifeq ($(ARCH),SONY) +# No highres in sony build case +PLATFORM := $(PLATFORM)_sony +BUILD_AS_PNOLET = 1 +FEATURE_PNOAND68K = -DFEATURE_PNOAND68K +TARGET=$(PLATFORM)/xw4_SONY_$(SVNREV).prc +else +ifeq ($(ARCH),COMBINED) +PLATFORM := $(PLATFORM)_combined +BUILD_AS_PNOLET = 1 +FEATURE_PNOAND68K = -DFEATURE_PNOAND68K +TARGET=$(PLATFORM)/xw4_COMB_$(SVNREV).prc +endif +endif +endif +endif + +PLATFORM := $(PLATFORM)_$(shell echo $(LANG) | tr [A-Z] [a-z]) + +APPNAME = "$(NAME)" + +PALM_TOOLS_PREFIX = $(shell echo $$PALM_TOOLS_PREFIX) + +ifeq (x$(PALM_TOOLS_PREFIX)x, xx) + PALM_TOOLS_PREFIX = m68k-palmos- +endif +#PALM_TOOLS_PREFIX=m68k-palmos-coff- + +CC = $(PALM_TOOLS_PREFIX)gcc +AR = $(PALM_TOOLS_PREFIX)ar +MULTILINK = $(PALM_TOOLS_PREFIX)multilink +############################################################################### +# par is a utility for manipulating .prc and .pdb files. See +# http://djw.org/product/palm/par/index.html to download source. +############################################################################### +PAR = par +PILRC = pilrc +BUILD-PRC = build-prc +OBJ-RES = m68k-palmos-obj-res + +MULTILINK_OPTIONS = -basename $(ROOTNAME) -segmentsize 27k -g \ + -deadstrip -verbose -gdb-script app.gdb + +MYDEFS_COMMON = $(PASSTHRU) -DXW_FEATURE_UTILS -DPOINTER_SUPPORT \ + -DKEY_SUPPORT -DOVERRIDE_EDGE_FOR_INDEX -DCOLOR_SUPPORT \ + -DSHOW_PROGRESS $(HASHDEF) -DNO_REG_REQUIRED -DPERIMETER_FOCUS + +# turn on letting users pick tiles "face-up" +MYDEFS_COMMON += -DFEATURE_TRAY_EDIT + +# turn on limits to searches on board and in tray (number of tiles to +# use) +MYDEFS_COMMON += -DXWFEATURE_SEARCHLIMIT + +#MYDEFS_COMMON += -DXWFEATURE_HINT_CONFIG + +# Turn on network play over IP via cellular modem. Very much +# experimental at this point! +# MYDEFS_COMMON += -DXWFEATURE_RELAY + +# turn on bluetooth comms option for 68K and ARM +BLUETOOTH = -DXWFEATURE_BLUETOOTH -DBT_USE_L2CAP +# -DCOMMS_HEARTBEAT +#BLUETOOTH = -DXWFEATURE_BLUETOOTH -DBT_USE_RFCOMM +MYDEFS_COMMON += $(BLUETOOTH) + +# Add menu allowing to choose to run 68K or ARM +ifeq ($(ARCH),COMBINED) +MYDEFS_COMMON += -DFEATURE_DUALCHOOSE +endif + +# For Danish and perhaps other languages, custom-measure glyph height +# so that overtall letters have a chance of fitting. +MYDEFS_COMMON += -DTALL_FONTS + +# Turn on ability to handle 4-byte-node DAWG files. These are of type +# Xwr4, not Xwr3 like the old ones. Currently this option means the +# binary won't be able to read or even find the old dictionaries. +# Don't release with this on until there's a UI decision and code on +# how to help users transition. +MYDEFS_COMMON += -DNODE_CAN_4 + +MYDEFS_COMMON += -DSVN_REV=\"$(shell svnversion -n .)\" + +ifdef XWFEATURE_STANDALONE_ONLY +MYDEFS_COMMON += -DXWFEATURE_STANDALONE_ONLY +else +MYDEFS_COMMON += -DXWFEATURE_IR +endif + +MYDEFS_ARM = -D__LITTLE_ENDIAN -DXW_TARGET_PNO $(MYDEFS_COMMON) +MYDEFS_68K = -DPLATFORM_PALM -D__BIG_ENDIAN $(MYDEFS_COMMON) \ + -DAPPNAME=\"$(APPNAME)\" \ + $(BLUETOOTH) + + +BITMAP_RSRCS = \ + $(BITMAPS)/rightarrow.pbitm \ + $(BITMAPS)/downarrow.pbitm \ + $(BITMAPS)/flipbutton.pbitm \ + $(BITMAPS)/valuebutton.pbitm \ + $(BITMAPS)/lightbulb.pbitm \ + $(BITMAPS)/traybuttons.pbitm \ + $(BITMAPS)/showtray.pbitm \ + $(BITMAPS)/xwords4.pbitm \ + +# INCLUDES += -I/usr/local/share/palmdev/sdk-5r3/Extensions/ExpansionMgr +ifdef BLUETOOTH +INCLUDES += -I/usr/local/share/palmdev/sdk-5r3/include/Extensions/Bluetooth +endif + +XWFEATURE_FIVEWAY = -DXWFEATURE_FIVEWAY -DKEYBOARD_NAV +XWFEATURE_FIVEWAY += -DDO_TUNGSTEN_FIVEWAY + +ifneq (x$(XWFEATURE_FIVEWAY)x, xx) + INCLUDES += -I/usr/local/share/palmdev/Handspring5 + INCLUDES += -I/usr/local/share/palmdev/Handspring5/68K +# INCLUDES += -I/usr/local/share/palmdev/duoIncs +# INCLUDES += -I/usr/local/share/palmdev/duoIncs/68K +# INCLUDES += -I/usr/local/share/palmdev/duoIncs/68K/System +# INCLUDES += -I/usr/local/share/palmdev/duoIncs/Common/System + + MYDEFS_68K += $(XWFEATURE_FIVEWAY) + MYDEFS_ARM += $(XWFEATURE_FIVEWAY) + +endif + +INCLUDES += -I/usr/local/share/palmdev/sdk-5r3/include/SonyIncs +INCLUDES += -I/usr/local/share/palmdev/sdk-5r3/include/SonyIncs/System +INCLUDES += -I/usr/local/share/palmdev/sdk-5r3/include/SonyIncs/Libraries +MYDEFS_68K += -DCPU_TYPE=CPU_68K + +include ../common/config.mk + +OBJS_68K = $(PLATFORM)/palmmain.o \ + $(PLATFORM)/palmsavg.o \ + $(PLATFORM)/gameutil.o \ + $(PLATFORM)/newgame.o \ + $(PLATFORM)/palmdict.o \ + $(PLATFORM)/palmdraw.o \ + $(PLATFORM)/palmutil.o \ + $(PLATFORM)/dictui.o \ + $(PLATFORM)/dictlist.o \ + $(PLATFORM)/palmir.o \ + $(PLATFORM)/palmip.o \ + $(PLATFORM)/palmbt.o \ + $(PLATFORM)/prefsdlg.o \ + $(PLATFORM)/connsdlg.o \ + $(PLATFORM)/palmdbg.o \ + $(COMMONOBJ) + +# if BUILD_AS_PNOLET is defined and FEATURE_PNOAND68K isn't, exclude +# all but enter68k.o from the 68K part of the binary (code resources). +ifdef BUILD_AS_PNOLET + OBJS = $(PLATFORM)/enter68k.o + PNOLET = pnolet +endif +ifndef BUILD_AS_PNOLET + OBJS += $(OBJS_68K) +else +ifdef FEATURE_PNOAND68K + OBJS += $(OBJS_68K) +endif +endif + +MYDEFS_68K += $(FEATURE_PNOAND68K) +MYDEFS_ARM += $(FEATURE_PNOAND68K) + +include ../common/rules.mk + +CSFLAGS += -S -Wall -DAPPID=\'$(APPID)\' $(MYDEFS_68K) $(INCLUDES) +CFLAGS += -Wall -DAPPID=\'$(APPID)\' $(MYDEFS_68K) $(INCLUDES) +# In the non-debug mode (for which DONT_OMIT is undefined) build without +# the frame pointer. + +$(TARGET): $(PLATFORM)/objs.prc $(PLATFORM)/res.prc + $(PAR) -c -a 'resource|backup' -v 4 $@ $(NAME) $(TYPE) $(APPID) $^ +ifdef XW_UPLOAD_SCRIPT + $(XW_UPLOAD_SCRIPT) $@ +endif + +solo: + $(MAKE) XWFEATURE_STANDALONE_ONLY=1 + +gremlins: + $(MAKE) PASSTHRU="-DDEBUG -DMEM_DEBUG -DFOR_GREMLINS" DONT_OMIT=true + +# +REL=405 +REL_PATH=public_html/xwords/4.0.5 +ship-all: + make clean; make; \ + make; (cd xwconfig && make); \ + for l in fr_FR en_US es_ES es_CT sv_SE de_DE ; do \ + make clean; \ + make LANG=$$l; \ + zip -j xw$(REL)_$$l.zip xwconfig/xwconfig.prc $(TARGET); \ + done + +.S.o: + $(CC) $(TARGETFLAGS) -c $< + +.c.s: + $(CC) $(CSFLAGS) $< + +$(BITMAPS)/%.pbitm: $(BITMAPS)/%.bmp + bmtoa $< > $@ + +$(PLATFORM)/objs.prc: $(PLATFORM)/LocalizedStrIncludes.h $(OBJS) gdbload + @rm -f *.grc *.bin +ifeq ($(ARCH),ARM_ONLY) + $(CC) $(OBJS) -o tmp.o + $(OBJ-RES) tmp.o + rm -f tmp.o +else + $(MULTILINK) $(MULTILINK_OPTIONS) $(OBJS) +endif + $(PAR) -c -a resource $(PLATFORM)/objs.prc Code rsrc rsrc *.grc + @rm -f *.grc *.bin + + +$(PLATFORM)/res.prc: xwords4.rcp $(HEADERS) StrL03e8.bin $(FNAVS) $(PNOLET) + $(PILRC) $< >/dev/null + $(PAR) -c -a 'resource' $@ Rsrc rsrc rsrc *.bin + rm -f $< *.bin + +xwords4.rcp: l10n/xwords4_$(LANG).rcp.pre xwords4defines.h $(BITMAP_RSRCS) + gcc -x c -E -P $(INCLUDES) $(MYDEFS_68K) $(DEBUG) \ + -DICONTEXT=\"$(ICONTEXT)\" $< > $@ + +# LocalizedStrIncludes.h: SVN_REV can change, but I don't have a way +# to express a dependency on it. If it does change, strings can wind +# up with offsets out of sync with the .h file. Not sure what to do +# about this.... Clean builds are probably a safe fallback. + +$(PLATFORM)/LocalizedStrIncludes.h StrL03e8.bin: \ + ./l10n/StrRes_$(LANG).pre ./l10n/mkstrsres.c + gcc $(CFLAGS) $(FORMATDEFINES) \ + -DLANGSTRFILE=\"$<\" ./l10n/mkstrsres.c \ + -o mkstrsres + mkdir -p $(PLATFORM) + ./mkstrsres StrL03e8.bin $(PLATFORM)/LocalizedStrIncludes.h + rm -f mkstrsres + +ifneq (x$(BUILD_AS_PNOLET)x, xx) +.PHONY : pnolet + +pnolet: $(PLATFORM)/LocalizedStrIncludes.h + $(MAKE) -f Makefile.PNO MEMDEBUG=$(MEMDEBUG) PLATFORM=$(PLATFORM) \ + MYDEFS="$(MYDEFS_ARM) -DAPPID=\'$(APPID)\' -DAPPNAME=\\\"$(APPNAME)\\\"" +endif + +# GDB seems confused by relative paths these days. So generate the +# file rather than trying to keep in in cvs. +gdbload: + echo "source app.gdb" > $@ + echo "load-segments" >> $@ + echo "dir $(shell pwd)" >> $@ + echo "dir $(shell pwd)/../common" >> $@ + +clean: + cd ../common && $(MAKE) PLATFORM=$(PLATFORM) $@ + rm -rf $(PLATFORM)/*.[oa] xwords4 *.bin *.stamp *.[pg]rc \ + xwords4.rcp *.btxt $(PLATFORM)/* $(PLATFORM)/LocalizedStrIncludes.h \ + gdbload +ifneq (x$(BUILD_AS_PNOLET)x, xx) + $(MAKE) -f Makefile.PNO PLATFORM=$(PLATFORM) clean +endif + +#cmod03E8.bin: palmdraw.c Makefile +# $(CC) -O2 -nostartfiles $(INCLUDES) $(MYDEFS) -o tmp $< +# $(OBJRES) tmp +# mv code0001.tmp.grc $@ ; rm *.tmp.grc + +help: + @echo "make [ARCH=(ARM_ONLY|68K_ONLY|SONY|COMBINED)] [MEMDEBUG=TRUE] [LANG=fr_FR|en_US|es_ES|es_CT|sv_SE|de_DE] [clean]" + @echo OR + @echo "make ship-all" + @echo OR + @echo "make gremlins" diff --git a/xwords4/palm/Makefile.PNO b/xwords4/palm/Makefile.PNO new file mode 100644 index 000000000..f6d3b4cf9 --- /dev/null +++ b/xwords4/palm/Makefile.PNO @@ -0,0 +1,127 @@ +# -*- mode: Makefile; compile-command: "make ARCH=ARM_ONLY"; -*- + +PALMDIR = /usr/local/share/palmdev/sdk-5r3/include + +# Which arm suite to use? Well, the suite in prc-tools-arm is broken +# on debian at the moment: builds code that doesn't work. But older +# versions, e.g. on current debian stable, are ok. For current debian +# the workaround is to build your own crosscompilation toolchain, +# which should put arm-elf-gcc in your path. We'll use that if we +# find it. Otherwise we fall back to the prc-tools-arm chain. +WHICH = $(shell if which arm-elf-gcc > /dev/null ; then echo elf; else echo palmos; fi) + +CC_ARM = arm-$(WHICH)-gcc +OC_ARM= arm-$(WHICH)-objcopy +OD_ARM = arm-$(WHICH)-objdump + +OPT = -Os +MINUS_G = -g + +ifeq ($(MEMDEBUG),TRUE) +DEBUG = -DMEM_DEBUG -DDEBUG +endif + +INCLUDES += \ + -I$(PALMDIR) \ + -I$(PALMDIR)/Libraries \ + -I$(PALMDIR)/Dynamic \ + -I$(PALMDIR)/Core \ + -I$(PALMDIR)/Extensions/ExpansionMgr \ + -I$(PALMDIR)/Core/UI \ + -I$(PALMDIR)/Core/System \ + -I$(PALMDIR)/Core/System/Unix \ + -I$(PALMDIR)/Core/Hardware \ + -I$(PALMDIR)/Extensions/Bluetooth \ + +ifneq (, $(findstring XWFEATURE_FIVEWAY,$(MYDEFS))) +INCLUDES += -I/usr/local/share/palmdev/Handspring5 +INCLUDES += -I/usr/local/share/palmdev/Handspring5/68K +GENDEFS += -DXWFEATURE_FIVEWAY +endif + +ifneq (, $(findstring XWFEATURE_BLUETOOTH,$(MYDEFS))) +GENDEFS += -DXWFEATURE_BLUETOOTH +endif + +# NOTE!!!! Added the -w flag to supress all warnings since arm-elf-gcc +# does so much bitching about the Palm headers. Need to fix that, or +# use the arm-palmos-gcc, or something. But for now, -w.... +CCFLAGS = $(OPT) $(MINUS_G) -Wall -Wunused-parameter -D__PALMOS__ -D__palmos__ -Asystem=palmos +ARMCCFS = $(CCFLAGS) -DNATIVE $(DEBUG) \ + -nostartfiles -mshort-load-bytes -nodefaultlibs -ffixed-r9 \ + -mpic-register=r10 -msingle-pic-base -fpic \ + -Wno-multichar \ + $(INCLUDES) + +ARMLDFS = -Xlinker --script=./ldscript.arm -lgcc + +# ARM-specific +ARM_DEFINES = $(MYDEFS) -DXW_TARGET_PNO -D__LITTLE_ENDIAN -include undef_hack.h + +# turn show progress off for now +# ARM_DEFINES += -USHOW_PROGRESS + +include ../common/config.mk + +OBJS = $(PLATFORM)/pnolet.o \ + $(PLATFORM)/palmmain.o \ + $(PLATFORM)/palmsavg.o \ + $(PLATFORM)/gameutil.o \ + $(PLATFORM)/newgame.o \ + $(PLATFORM)/palmdict.o \ + $(PLATFORM)/palmdraw.o \ + $(PLATFORM)/palmutil.o \ + $(PLATFORM)/dictui.o \ + $(PLATFORM)/dictlist.o \ + $(PLATFORM)/palmir.o \ + $(PLATFORM)/palmip.o \ + $(PLATFORM)/palmbt.o \ + $(PLATFORM)/prefsdlg.o \ + $(PLATFORM)/connsdlg.o \ + $(PLATFORM)/palmdbg.o \ + $(PLATFORM)/pace_gen.o \ + $(PLATFORM)/pace_man.o \ + $(COMMONOBJ) + +# The target of this makefile is the .bin files that comprise the +# pnolet. There will be more than just PNOC0000.bin, but the others +# get made as a matter of course and as long as they all get included +# in the .prc we're fine. + +all: Pnoc0000.bin Pnog0000.bin + +$(PLATFORM)/%.o : %.c $(MAKEFILE) + mkdir -p $(PLATFORM) + $(CC_ARM) -o $@ -c $(ARM_DEFINES) -DHERE $(ARMCCFS) $< + +$(COMMONOBJDIR)/%.o : $(COMMONDIR)/%.c $(MAKEFILE) + mkdir -p $(COMMONOBJDIR) + $(CC_ARM) -o $@ -c $(ARM_DEFINES) -DHERE $(ARMCCFS) $< + +Pnoc_bin.pre Pnog0000.bin: pace_gen.h $(OBJS) + $(CC_ARM) $(OBJS) $(ARMCCFS) $(ARMLDFS) -Wl,-Map,Pnoc_bin.map -o tmp.out +# @$(OD_ARM) --disassemble-all tmp.out + $(OC_ARM) -j .text -O binary tmp.out Pnoc_bin.pre + $(OC_ARM) -j .got -O binary tmp.out Pnog0000.bin +# @rm -f tmp.out + +# create not only Pnoc0000.bin but any others required to fit the +# pnolet into 50K chunks (for hotsyncing...) +Pnoc0000.bin: Pnoc_bin.pre + rm -f Pnoc????.bin + split -a 4 -b 50k -d $< Pnoc + for f in $$(ls Pnoc????); do mv $$f $$f.bin; done + +pace_gen.c pace_gen.h: ./gen_pace.pl funcfile.txt + rm -f $@ _dirList + ./gen_pace.pl -func funcfile.txt \ + -file $(PALMDIR)/Core/UI \ + -file $(PALMDIR)/Core/System \ + -file $(PALMDIR)/Extensions/ExpansionMgr \ + -file $(PALMDIR)/Extensions/Bluetooth \ + -file /usr/local/share/palmdev/Handspring5/68K/System \ + -oc pace_gen.c -oh pace_gen.h $(GENDEFS) + rm -f _dirList + +clean: + rm -rf $(OBJS) pace_gen.c pace_gen.h *.bin Pnoc_bin.pre diff --git a/xwords4/palm/bmps/bts_conn.bmp b/xwords4/palm/bmps/bts_conn.bmp new file mode 100644 index 0000000000000000000000000000000000000000..306ec55fc3dc2354674c8a49aec1a681802be1fa GIT binary patch literal 774 zcmb7=T@FA%41|lsiw6=fj^G3i;N9Kbh|$6RHFj;Z$u=`z$F5u3h@J&~Mh&PQRmp@d zvezPG$I=1XfrDQ7`QpoIc)_x16o(c{zw}J y?Xb8QBr}7>htgOPDfOB{<1K!e?E5ehm;d7XThwmkf3b;}{~IK?G=YG$hj{{|do>sU literal 0 HcmV?d00001 diff --git a/xwords4/palm/bmps/bts_conn.pbitm b/xwords4/palm/bmps/bts_conn.pbitm new file mode 100644 index 000000000..6dbb118b7 --- /dev/null +++ b/xwords4/palm/bmps/bts_conn.pbitm @@ -0,0 +1,7 @@ +###-#### +##--#### +#------# +######## +#------# +####--## +####-### diff --git a/xwords4/palm/bmps/bts_listen.bmp b/xwords4/palm/bmps/bts_listen.bmp new file mode 100644 index 0000000000000000000000000000000000000000..64d5bf6fab3ec6ca384b5ca1f149930ab323afea GIT binary patch literal 774 zcmb7=(G7zz3`7k`{PcrL{4fF&bb$Ud6DzV&xcDI0jn&c!Tj0DqpLj2QCwgA!GwO*N zP>oFJB3mmmK3FFF&d3#+UNcCjy5rBEL%yhV z_#?lExgC-#2A!E9@&~!7k KIN)5AhbislYCz}! literal 0 HcmV?d00001 diff --git a/xwords4/palm/bmps/bts_listen.pbitm b/xwords4/palm/bmps/bts_listen.pbitm new file mode 100644 index 000000000..fccd40fc6 --- /dev/null +++ b/xwords4/palm/bmps/bts_listen.pbitm @@ -0,0 +1,7 @@ +###-#### +##--#### +#------# +######## +######## +######## +######## diff --git a/xwords4/palm/bmps/bts_off.bmp b/xwords4/palm/bmps/bts_off.bmp new file mode 100644 index 0000000000000000000000000000000000000000..279e8f5d8c8c6ed50801185ce5c785918b9b1e38 GIT binary patch literal 774 zcma)#!3_d23YugpL)nxw!3~2j3`N}{^}?Z*xG(|}bO7$mloi>?eDcBABo#Em79{V#1Q@4RBYAw$2h<1k zMm?lQ7nz?TUCYW7Xo`V;^YZYTtYlM4bsOOr0er!`6B71)+o&|Fdd%*RgCWAy9X~$~ zc}wL|CirV`zufy%v=Il Pz`%YUV;5FbwxqfMvMfO8 literal 0 HcmV?d00001 diff --git a/xwords4/palm/bmps/bts_seek.pbitm b/xwords4/palm/bmps/bts_seek.pbitm new file mode 100644 index 000000000..f35875a39 --- /dev/null +++ b/xwords4/palm/bmps/bts_seek.pbitm @@ -0,0 +1,7 @@ +######## +######## +######## +######## +#------# +####--## +####-### diff --git a/xwords4/palm/bmps/downarrow.pbitm b/xwords4/palm/bmps/downarrow.pbitm new file mode 100644 index 000000000..5cce779c3 --- /dev/null +++ b/xwords4/palm/bmps/downarrow.pbitm @@ -0,0 +1,9 @@ +--------- +----#---- +----#---- +----#---- +-#######- +--#####-- +---###--- +----#---- +--------- diff --git a/xwords4/palm/bmps/downarrowhd.pbitm b/xwords4/palm/bmps/downarrowhd.pbitm new file mode 100644 index 000000000..2b9e2c119 --- /dev/null +++ b/xwords4/palm/bmps/downarrowhd.pbitm @@ -0,0 +1,18 @@ +------------------ +------------------ +--------##-------- +--------##-------- +--------##-------- +--------##-------- +--------##-------- +--------##-------- +-################- +--##############-- +---############--- +----##########---- +-----########----- +------######------ +-------####------- +--------##-------- +------------------ +------------------ diff --git a/xwords4/palm/bmps/flipbutton.pbitm b/xwords4/palm/bmps/flipbutton.pbitm new file mode 100644 index 000000000..c5ce87b11 --- /dev/null +++ b/xwords4/palm/bmps/flipbutton.pbitm @@ -0,0 +1,8 @@ +######## +#-###### +#--##### +#---#### +#----### +#-----## +#------# +######## diff --git a/xwords4/palm/bmps/flipbuttonhd.pbitm b/xwords4/palm/bmps/flipbuttonhd.pbitm new file mode 100644 index 000000000..f7ff2c467 --- /dev/null +++ b/xwords4/palm/bmps/flipbuttonhd.pbitm @@ -0,0 +1,16 @@ +################ +#-############## +#--############# +#---############ +#----########### +#-----########## +#------######### +#-------######## +#--------####### +#---------###### +#----------##### +#-----------#### +#------------### +#-------------## +#--------------# +################ diff --git a/xwords4/palm/bmps/lightbulb.pbitm b/xwords4/palm/bmps/lightbulb.pbitm new file mode 100644 index 000000000..37a5a132d --- /dev/null +++ b/xwords4/palm/bmps/lightbulb.pbitm @@ -0,0 +1,11 @@ +-######- +###--### +##-##-## +#-####-# +#-####-# +##-##-## +##-##-## +###--### +###--### +###--### +-######- diff --git a/xwords4/palm/bmps/lightbulbhd.pbitm b/xwords4/palm/bmps/lightbulbhd.pbitm new file mode 100644 index 000000000..f2e13d9a7 --- /dev/null +++ b/xwords4/palm/bmps/lightbulbhd.pbitm @@ -0,0 +1,23 @@ +--############-- +-##############- +######----###### +#####--##--##### +####--####--#### +###--######--### +##--##-#####--## +##--###-####--## +##--##-#####--## +##--########--## +###--######--### +####--####--#### +####--####--#### +####--####--#### +#####------##### +######-#--###### +######--#-###### +######----###### +######-#--###### +######--#-###### +-######--######- +--############-- + \ No newline at end of file diff --git a/xwords4/palm/bmps/rightarrow.pbitm b/xwords4/palm/bmps/rightarrow.pbitm new file mode 100644 index 000000000..ee3b667b7 --- /dev/null +++ b/xwords4/palm/bmps/rightarrow.pbitm @@ -0,0 +1,9 @@ +--------- +----#---- +----##--- +----###-- +-#######- +----###-- +----##--- +----#---- +--------- diff --git a/xwords4/palm/bmps/rightarrowhd.pbitm b/xwords4/palm/bmps/rightarrowhd.pbitm new file mode 100644 index 000000000..5505f89f1 --- /dev/null +++ b/xwords4/palm/bmps/rightarrowhd.pbitm @@ -0,0 +1,18 @@ +------------------ +--------#--------- +--------##-------- +--------###------- +--------####------ +--------#####----- +--------######---- +--------#######--- +--##############-- +--##############-- +--------#######--- +--------######---- +--------#####----- +--------####------ +--------###------- +--------##-------- +--------#--------- +------------------ diff --git a/xwords4/palm/bmps/showtray.pbitm b/xwords4/palm/bmps/showtray.pbitm new file mode 100644 index 000000000..86dd04004 --- /dev/null +++ b/xwords4/palm/bmps/showtray.pbitm @@ -0,0 +1,12 @@ +-######- +######## +#------# +#------# +###--### +###--### +###--### +###--### +###--### +###--### +######## +-######- diff --git a/xwords4/palm/bmps/startmark.pbitm b/xwords4/palm/bmps/startmark.pbitm new file mode 100644 index 000000000..317fc7e8e --- /dev/null +++ b/xwords4/palm/bmps/startmark.pbitm @@ -0,0 +1,8 @@ +-------- +-##---## +-###-### +--#####- +---###-- +--#####- +-###-### +-##---## diff --git a/xwords4/palm/bmps/startmarkhd.pbitm b/xwords4/palm/bmps/startmarkhd.pbitm new file mode 100644 index 000000000..f016b2775 --- /dev/null +++ b/xwords4/palm/bmps/startmarkhd.pbitm @@ -0,0 +1,16 @@ +---------------- +---------------- +--###--------### +--####------#### +--#####----##### +---#####--#####- +----##########-- +-----########--- +------######---- +------######---- +-----########--- +----##########-- +---#####--#####- +--#####----##### +--####------#### +--###--------### diff --git a/xwords4/palm/bmps/traybuttons.pbitm b/xwords4/palm/bmps/traybuttons.pbitm new file mode 100644 index 000000000..8e0448613 --- /dev/null +++ b/xwords4/palm/bmps/traybuttons.pbitm @@ -0,0 +1,20 @@ +------------------ +---#############-- +-################# +-##-###-#######-## +-##-###-#######-## +-##-###-#######-## +-##-----#######-## +-##-###-###-###-## +-##-###-###-###-## +-##-###-####---### +-################# +-##-----###----### +-####-#####-###-## +-####-#####-###-## +-####-#####-###-## +-####-#####-###-## +-####-#####-###-## +-####-#####----### +-################# +---#############-- diff --git a/xwords4/palm/bmps/traybuttonshd.pbitm b/xwords4/palm/bmps/traybuttonshd.pbitm new file mode 100644 index 000000000..c462a48b7 --- /dev/null +++ b/xwords4/palm/bmps/traybuttonshd.pbitm @@ -0,0 +1,32 @@ +---------------------------------- +-################################- +################################## +####--######--##############--#### +####--######--##############--#### +####--######--##############--#### +####--######--##############--#### +####--######--##############--#### +####--######--##############--#### +####----------##############--#### +####--######--##############--#### +####--######--######--######--#### +####--######--######--######--#### +####--######--######--######--#### +####--######--#######--####--##### +####--######--########------###### +################################## +################################## +####----------######--------###### +########--##########--#####--##### +########--##########--######--#### +########--##########--######--#### +########--##########--######--#### +########--##########--######--#### +########--##########--######--#### +########--##########--######--#### +########--##########--######--#### +########--##########--######--#### +########--##########--#####--##### +########--##########--------###### +################################## +-###############################- diff --git a/xwords4/palm/bmps/valuebutton.pbitm b/xwords4/palm/bmps/valuebutton.pbitm new file mode 100644 index 000000000..9741a829e --- /dev/null +++ b/xwords4/palm/bmps/valuebutton.pbitm @@ -0,0 +1,10 @@ +######## +#--###-# +##-##-## +#---#-## +####-### +###-#-## +##-#-#-# +##-#---# +#-##-#-# +######## diff --git a/xwords4/palm/bmps/valuebuttonhd.pbitm b/xwords4/palm/bmps/valuebuttonhd.pbitm new file mode 100644 index 000000000..b7446409d --- /dev/null +++ b/xwords4/palm/bmps/valuebuttonhd.pbitm @@ -0,0 +1,21 @@ +################ +##############-# +####-#########-# +###--########-## +####-########-## +####-#######-### +####-######-#### +####-#####-##### +####-####-###### +###---##-####### +#######-##--#### +######-##-##-### +#####-##-####-## +####-###-####-## +###-####------## +##-#####-####-## +##-#####-####-## +#-######-####-## +#-############## +################ + \ No newline at end of file diff --git a/xwords4/palm/bmps/xwbandwicon.bmp b/xwords4/palm/bmps/xwbandwicon.bmp new file mode 100644 index 0000000000000000000000000000000000000000..fd8b6939d2e25574cfeb9c510f2b15083e33c370 GIT binary patch literal 1550 zcmb`EF|GnJ3`9c#4H5+f;s~681N{EqpWA@tS&s%g2@xVvvi5lV;@$oGKOLH{yPY@N zmu>Ib)%C}|weu0%1v{SO6_(l4y2p$;o)8E`dlqtHZ<#c{DQjfIP#nFbdJGT>}nYS6?XwnIo^M9z^R<{jp9n zLI8qu3|ot_Eb>1FcyES~V?znbrNTMfsu(-BLPROEE}{SzbG3ceMGPa%2+zg)$OMC9 zjU2O%HIE*>V|(ZS=*GY(Bw2u_vag~ssHS5vb8}Ei=Szmux$WFpoXBnG U7RJzOJx^$;h)6@$6(AOZ`cx*zTg SI0J|IKy5(nm;i1#L;(Obn*Hwp literal 0 HcmV?d00001 diff --git a/xwords4/palm/bmps/xwords4.pbitm b/xwords4/palm/bmps/xwords4.pbitm new file mode 100644 index 000000000..6dc969ef6 --- /dev/null +++ b/xwords4/palm/bmps/xwords4.pbitm @@ -0,0 +1,22 @@ +-############--------- +#------------#-------- +#-#----#-----#-------- +#-#----#-----#-------- +#--#--#------#-------- +#--#--#------#-------- +#---##-------#-------- +#--#--#------#-------- +#--#--#--############- +#-#----##------------# +#-#----##-#-----#----# +#-------#-#-----#----# +#-------#-#-----#----# +-########-#--#--#----# +--------#-#--#--#----# +--------#-#--#--#----# +--------#--#-#-#--#--# +--------#---#-#--##--# +--------#-------#-#--# +--------#-------####-# +--------#---------#--# +---------############- diff --git a/xwords4/palm/bmps/xwords4small.pbitm b/xwords4/palm/bmps/xwords4small.pbitm new file mode 100644 index 000000000..290050ac4 --- /dev/null +++ b/xwords4/palm/bmps/xwords4small.pbitm @@ -0,0 +1,9 @@ +---#########--- +---#-------#--- +---#--###--#--- +---#-#-----#--- +---#-#-----#--- +---#-#-----#--- +---#--###--#--- +---#-------#--- +---#########--- \ No newline at end of file diff --git a/xwords4/palm/callback.h b/xwords4/palm/callback.h new file mode 100644 index 000000000..bec41f794 --- /dev/null +++ b/xwords4/palm/callback.h @@ -0,0 +1,27 @@ +/* copied from _Palm Programming_ p. 79*/ + +#ifndef __CALLBACK__ +#define __CALLBACK__ + +#if defined MW_COMPILER +/* these are no-ops for MW as I understand it */ +# define CALLBACK_PROLOGUE() +# define CALLBACK_EPILOGUE() + +#elif defined XW_TARGET_PNO || defined XW_TARGET_X86 + +#define CALLBACK_PROLOGUE() +#define CALLBACK_EPILOGUE() + +#else + +register void *reg_a4 asm("%a4"); + +#define CALLBACK_PROLOGUE() \ + { void* __save_a4 = reg_a4; asm("move.l %%a5,%%a4; sub.l #edata,%%a4" : :); + +#define CALLBACK_EPILOGUE() reg_a4 = __save_a4;} + +#endif /* MW_COMPILER */ + +#endif diff --git a/xwords4/palm/common.rcp.pre b/xwords4/palm/common.rcp.pre new file mode 100644 index 000000000..300be34b1 --- /dev/null +++ b/xwords4/palm/common.rcp.pre @@ -0,0 +1,244 @@ +/* -*-mode: c; fill-column: 78; compile-command: "make ARCH=68K_ONLY MEMDEBUG=TRUE"; -*- */ + +/***************************************************************************** + * Copyright 1999 - 2001 by Eric House. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * These resources are shared among all localized verions of XW4; no + * localization required. + ****************************************************************************/ + +#ifdef COLOR_SUPPORT +/* colors */ +HEX "Clrs" ID 1000 + 0x00 0x00 0x00 /* black */ + 0xFF 0xFF 0xFF /* white */ + 0x00 0x00 0x00 /* player 1 */ + 0xFF 0x00 0x00 + 0x00 0x00 0xFF + 0x00 0xFF 0x00 + 0xAF 0xAF 0x00 /* bonus 1 */ + 0x00 0xAF 0xAF + 0xAF 0x00 0xAF + 0xAF 0xAF 0xAF + 0xFF 0xFF 0xFF /* empty cells */ + 0xFF 0xFF 0x99 /* tile background */ +#endif + +ICONFAMILY "./bmps/xwbandwicon.bmp" "" "" "./bmps/xwcoloricon.bmp" TRANSPARENT 255 255 255 +SMALLICONFAMILY "bmps/xwbandwicon_sm.bmp" "" "" "./bmps/xwcoloricon_sm.bmp" TRANSPARENT 255 255 255 + +BITMAP ID SHOWTRAY_BUTTON_BMP_RES_ID "bmps/showtray.pbitm" AUTOCOMPRESS + +BITMAP ID DOWN_ARROW_RESID AUTOCOMPRESS +BEGIN + BITMAP "bmps/downarrow.pbitm" BPP 1 DENSITY 72 + BITMAP "bmps/downarrowhd.pbitm" BPP 1 DENSITY 144 +END + +BITMAP ID RIGHT_ARROW_RESID AUTOCOMPRESS +BEGIN + BITMAP "bmps/rightarrow.pbitm" BPP 1 DENSITY 72 + BITMAP "bmps/rightarrowhd.pbitm" BPP 1 DENSITY 144 +END + +/* bitmap family for inclding highres version */ +BITMAP ID TRAY_BUTTONS_BMP_RES_ID AUTOCOMPRESS +BEGIN + BITMAP "bmps/traybuttons.pbitm" BPP 1 DENSITY 72 + BITMAP "bmps/traybuttonshd.pbitm" BPP 1 DENSITY 144 +END + +BITMAP ID STAR_BMP_RES_ID AUTOCOMPRESS +BEGIN + BITMAP "bmps/startmark.pbitm" BPP 1 DENSITY 72 + BITMAP "bmps/startmarkhd.pbitm" BPP 1 DENSITY 144 +END + +BITMAP ID FLIP_BUTTON_BMP_RES_ID AUTOCOMPRESS +BEGIN + BITMAP "bmps/flipbutton.pbitm" BPP 1 DENSITY 72 + BITMAP "bmps/flipbuttonhd.pbitm" BPP 1 DENSITY 144 +END + +BITMAP ID HINT_BUTTON_BMP_RES_ID AUTOCOMPRESS +BEGIN + BITMAP "bmps/lightbulb.pbitm" BPP 1 DENSITY 72 + BITMAP "bmps/lightbulbhd.pbitm" BPP 1 DENSITY 144 +END + +BITMAP ID VALUE_BUTTON_BMP_RES_ID AUTOCOMPRESS +BEGIN + BITMAP "bmps/valuebutton.pbitm" BPP 1 DENSITY 72 + BITMAP "bmps/valuebuttonhd.pbitm" BPP 1 DENSITY 144 +END + +#ifdef XWFEATURE_BLUETOOTH +BITMAP ID BTSTATUS_NONE_RES_ID AUTOCOMPRESS +BEGIN + BITMAP "bmps/bts_off.pbitm" BPP 1 DENSITY 72 + BITMAP "bmps/bts_off.bmp" BPP 8 DENSITY 144 +END + +BITMAP ID BTSTATUS_LISTENING_RES_ID AUTOCOMPRESS +BEGIN + BITMAP "bmps/bts_listen.pbitm" BPP 1 DENSITY 72 + BITMAP "bmps/bts_listen.bmp" BPP 8 DENSITY 144 +END + +BITMAP ID BTSTATUS_SEEKING_RES_ID AUTOCOMPRESS +BEGIN + BITMAP "bmps/bts_seek.pbitm" BPP 1 DENSITY 72 + BITMAP "bmps/bts_seek.bmp" BPP 8 DENSITY 144 +END + +BITMAP ID BTSTATUS_CONNECTED_RES_ID AUTOCOMPRESS +BEGIN + BITMAP "bmps/bts_conn.pbitm" BPP 1 DENSITY 72 + BITMAP "bmps/bts_conn.bmp" BPP 8 DENSITY 144 +END +#endif /* XWFEATURE_BLUETOOTH */ + +#define TBH TRAY_BUTTON_HEIGHT_HR +#define TBY TRAY_BUTTONS_Y_HR + +FORM ID XW_MAIN_FORM AT (0 0 160 160) +USABLE +NOFRAME +MENUID XW_MAIN_MENU_ID +BEGIN +#ifdef XWFEATURE_FIVEWAY + GADGET ID XW_SCOREBOARD_GADGET_ID AT (0 0 1 1) USABLE + GADGET ID XW_BOARD_GADGET_ID AT (0 0 1 1) USABLE + GADGET ID XW_TRAY_GADGET_ID AT (0 0 1 1) USABLE +#endif + BUTTON "" XW_MAIN_FLIP_BUTTON_ID + AT (PALM_FLIP_LEFT PALM_BOARD_TOP FLIP_BUTTON_WIDTH + FLIP_BUTTON_HEIGHT) NOFRAME + BUTTON "" XW_MAIN_VALUE_BUTTON_ID + AT (PALM_FLIP_LEFT PREVBOTTOM+2 + FLIP_BUTTON_WIDTH FLIP_BUTTON_HEIGHT+2) NOFRAME + BUTTON "" XW_MAIN_HINT_BUTTON_ID + AT (PALM_FLIP_LEFT PREVBOTTOM+2 FLIP_BUTTON_WIDTH + FLIP_BUTTON_HEIGHT+3) NOFRAME + + SCROLLBAR ID XW_MAIN_SCROLLBAR_ID + AT ( PREVLEFT PREVBOTTOM+5 RECOMMENDED_SBAR_WIDTH + 160-TRAY_HEIGHT_HR-PREVBOTTOM-IR_STATUS_HEIGHT-5) + USABLE VALUE SBAR_START_VALUE MIN SBAR_MIN MAX SBAR_MIN + PAGESIZE SBAR_PAGESIZE + +#ifdef XWFEATURE_BLUETOOTH + GADGET ID XW_BTSTATUS_GADGET_ID AT (PALM_FLIP_LEFT PREVBOTTOM+4 16 15) + NONUSABLE +#endif + + BUTTON "" XW_MAIN_SHOWTRAY_BUTTON_ID + AT (PALM_FLIP_LEFT SHOWTRAY_BUTTON_Y + FLIP_BUTTON_WIDTH FLIP_BUTTON_WIDTH+4) NOFRAME + +#ifndef EIGHT_TILES + BUTTON "" XW_MAIN_HIDE_BUTTON_ID + AT(PALM_TRAY_BUTTON_LEFT TBY TRAY_BUTTON_WIDTH TBH) NOFRAME + BUTTON "" XW_MAIN_JUGGLE_BUTTON_ID + AT(PREVRIGHT PREVTOP TRAY_BUTTON_WIDTH TBH) NOFRAME + BUTTON "" XW_MAIN_TRADE_BUTTON_ID + AT(PALM_TRAY_BUTTON_LEFT PREVBOTTOM TRAY_BUTTON_WIDTH + TBH) NOFRAME + BUTTON "" XW_MAIN_DONE_BUTTON_ID + AT(PREVRIGHT PREVTOP TRAY_BUTTON_WIDTH TBH) NOFRAME +#endif +#ifdef FOR_GREMLINS + GADGET GREMLIN_BOARD_GADGET_IDAUTOID AT ( 0 5 152 135 ) + GADGET GREMLIN_TRAY_GADGET_IDAUTOID AT ( 0 140 145 20 ) +#endif +END /* XW_MAIN_FORM */ + +#ifdef XWFEATURE_FIVEWAY +NAVIGATION ID XW_MAIN_FORM +INITIALSTATE kFrmNavHeaderFlagsObjectFocusStartState +/* INITIALOBJECTID: the OS arbitrarily sends a focusTake event for some button + * on opening the form if this isn't set, otherwise it sends for this. That's + * easier to test for and drop, so set it is. */ +INITIALOBJECTID XW_SCOREBOARD_GADGET_ID +NAVIGATIONMAP + ROW XW_SCOREBOARD_GADGET_ID + ROW XW_BOARD_GADGET_ID + XW_MAIN_FLIP_BUTTON_ID + XW_MAIN_VALUE_BUTTON_ID + XW_MAIN_HINT_BUTTON_ID + ROW XW_TRAY_GADGET_ID + XW_MAIN_SHOWTRAY_BUTTON_ID + XW_MAIN_HIDE_BUTTON_ID + XW_MAIN_JUGGLE_BUTTON_ID + XW_MAIN_TRADE_BUTTON_ID + XW_MAIN_DONE_BUTTON_ID +END + +NAVIGATION ID XW_PASSWORD_DIALOG_ID +INITIALSTATE kFrmNavHeaderFlagsObjectFocusStartState +INITIALOBJECTID XW_PASSWORD_PASS_FIELD +NAVIGATIONMAP + ROW XW_PASSWORD_PASS_FIELD + ROW XW_PASSWORD_OK_BUTTON XW_PASSWORD_CANCEL_BUTTON +END +#endif + +STRING ID 1000 "/palm/programs/Crosswords/" + +APPLICATIONICONNAME ID 1000 ICONTEXT + +/* force hotsync into games category */ +LAUNCHERCATEGORY ID 1000 "Games" + +HEX "Xbrd" ID 1000 +#if 1 /* the "scrabble" pattern */ + 0x40 0x01 0x00 0x04 + 0x02 0x00 0x03 0x00 + 0x00 0x20 0x00 0x10 + 0x10 0x02 0x00 0x01 + 0x00 0x00 0x20 0x00 + 0x03 0x00 0x03 0x00 + 0x00 0x10 0x00 0x10 + 0x40 0x01 0x00 0x02 +#else + 0x04 0x00 0x10 0x04 + 0x40 0x00 0x03 0x00 + 0x00 0x00 0x20 0x10 + 0x00 0x03 0x00 0x01 + 0x10 0x20 0x00 0x30 + 0x03 0x00 0x01 0x00 + 0x00 0x10 0x30 0x00 + 0x40 0x01 0x00 0x02 +#endif + +/* for 13x13 board */ +HEX "Xbrd" ID 1001 /* This is 7x7, so one line is two rows */ + 0x20 0x03 0x00 0x30 0x40 0x00 0x10 + 0x00 0x10 0x00 0x03 0x00 0x30 0x00 + 0x00 0x00 0x10 0x00 0x10 0x00 0x30 + 0x30 0x00 0x00 0x20 + +/* for 11x11 board */ +HEX "Xbrd" ID 1002 /* This is 6x6 */ + 0x04 0x00 0x03 + 0x40 0x03 0x00 + 0x00 0x10 0x01 + 0x03 0x01 0x00 + 0x00 0x00 0x30 + 0x30 0x10 0x02 + +VERSION ID 1000 XW_PALM_VERSION_STRING diff --git a/xwords4/palm/connsdlg.c b/xwords4/palm/connsdlg.c new file mode 100644 index 000000000..a5e224b03 --- /dev/null +++ b/xwords4/palm/connsdlg.c @@ -0,0 +1,397 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; compile-command: "make ARCH=ARM_ONLY MEMDEBUG=TRUE"; -*- */ +/* + * Copyright 2003-2008 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#if defined XWFEATURE_BLUETOOTH || defined XWFEATURE_RELAY + +#include + +#include "callback.h" + +#include "connsdlg.h" +#include "strutils.h" +#include "palmmain.h" +#include "palmutil.h" +#include "palmir.h" +#include "palmbt.h" +#include "LocalizedStrIncludes.h" + +/* When user pops up via Host gadget, we want to get the port to listen on. + * When pops up via the Guest gadget, we want to get the port and IP address + * of the host AS WELL AS the local port that we'll tell it we're listening + * on. We need local state for both, since user can switch between them and + * expect state to live as long as the parent dialog isn't exited. + */ + +typedef struct XportEntry { + XP_U16 resID; + CommsConnType conType; +} XportEntry; + +#define MAX_XPORTS 3 + +typedef struct ConnsDlgState { + ListPtr connTypesList; + ListData sLd; + XportEntry xports[MAX_XPORTS]; + XP_U16 nXports; + XP_U16 serverRole; + XP_Bool isNewGame; + CommsConnType conType; + CommsAddrRec* addr; + XP_BtAddr btAddr; /* since there's no field, save it here */ + char hostName[PALM_BT_NAME_LEN]; +} ConnsDlgState; + +static void +ctlsFromState( PalmAppGlobals* globals ) +{ + ConnsDlgState* state = globals->connState; + CommsAddrRec* addr = state->addr; + XP_Bool isNewGame = state->isNewGame; + state->conType = addr->conType; + + if ( 0 ) { +#ifdef XWFEATURE_RELAY + } else if ( addr->conType == COMMS_CONN_RELAY ) { + XP_UCHAR buf[16]; + setFieldStr( XW_CONNS_RELAY_FIELD_ID, addr->u.ip_relay.hostName ); + setFieldEditable( XW_CONNS_RELAY_FIELD_ID, isNewGame ); + + StrPrintF( buf, "%d", addr->u.ip_relay.port ); + setFieldStr( XW_CONNS_PORT_FIELD_ID, buf ); + setFieldEditable( XW_CONNS_PORT_FIELD_ID, isNewGame ); + + setFieldStr( XW_CONNS_COOKIE_FIELD_ID, addr->u.ip_relay.cookie ); + setFieldEditable( XW_CONNS_COOKIE_FIELD_ID, isNewGame ); +#endif +#ifdef XWFEATURE_BLUETOOTH + } else if ( addr->conType == COMMS_CONN_BT + /* It's ok to load the controls even if we won't use them */ + /* && state->serverRole == SERVER_ISCLIENT */ ) { + ControlPtr ctrl = getActiveObjectPtr( XW_CONNS_BT_HOSTTRIGGER_ID ); + + XP_MEMCPY( &state->btAddr, &state->addr->u.bt.btAddr, + sizeof(state->btAddr) ); + XP_MEMCPY( &state->hostName, &state->addr->u.bt.hostName, + sizeof(state->hostName) ); + + if ( '\0' != addr->u.bt.hostName[0] ) { + CtlSetLabel( ctrl, state->hostName ); + } + CtlSetEnabled( ctrl, isNewGame ); + +#endif + } + +#ifdef XWFEATURE_BLUETOOTH + /* Set up any controls that are not based on union fields regardless of + conType, as user may change and we want the defaults to be right. + This is a case where using a union breaks down: it'd be better to have + access to all the defaults, but they may be corrupt if conType is + wrong. */ + XP_ASSERT( !!globals->prefsDlgState ); + setBooleanCtrl( XW_CONNS_BTCONFIRM_CHECKBOX_ID, + globals->prefsDlgState->confirmBTConnect ); +#endif + +} /* ctlsFromState */ + +static XP_Bool +stateFromCtls( PalmAppGlobals* globals, XP_Bool* prefsChanged ) +{ + ConnsDlgState* state = globals->connState; + CommsAddrRec addr; + XP_Bool addrOk; + + *prefsChanged = XP_FALSE; + + XP_MEMCPY( &addr, state->addr, sizeof(addr) ); + addr.conType = state->conType; + + if ( 0 ) { +#ifdef XWFEATURE_RELAY + } else if ( addr.conType == COMMS_CONN_RELAY ) { + XP_UCHAR buf[16]; + getFieldStr( XW_CONNS_RELAY_FIELD_ID, addr.u.ip_relay.hostName, + sizeof(addr.u.ip_relay.hostName) ); + + getFieldStr( XW_CONNS_PORT_FIELD_ID, buf, sizeof(buf) ); + addr.u.ip_relay.port = StrAToI( buf ); + + getFieldStr( XW_CONNS_COOKIE_FIELD_ID, addr.u.ip_relay.cookie, + sizeof(addr.u.ip_relay.cookie) ); +#endif +#ifdef XWFEATURE_BLUETOOTH + } else if ( addr.conType == COMMS_CONN_BT + && state->serverRole == SERVER_ISCLIENT ) { + XP_Bool confirmBTConnect; + /* Not exactly from a control... */ + /* POSE is flagging this as reading from a bad address, but it + looks ok inside the debugger */ + XP_MEMCPY( addr.u.bt.hostName, state->hostName, + sizeof(addr.u.bt.hostName) ); + XP_MEMCPY( &addr.u.bt.btAddr, &state->btAddr, + sizeof(addr.u.bt.btAddr) ); + LOG_HEX( &addr.u.bt.btAddr, sizeof(addr.u.bt.btAddr), __func__ ); + + confirmBTConnect = getBooleanCtrl( XW_CONNS_BTCONFIRM_CHECKBOX_ID ); + XP_ASSERT( !!globals->prefsDlgState ); + if ( confirmBTConnect != globals->prefsDlgState->confirmBTConnect ) { + globals->prefsDlgState->confirmBTConnect = confirmBTConnect; + *prefsChanged = XP_TRUE; + } +#endif + } + + addrOk = comms_checkAddr( state->serverRole, &addr, &globals->util ); + if ( addrOk ) { + XP_MEMCPY( state->addr, &addr, sizeof( *state->addr) ); + } + + return addrOk; +} /* stateFromCtls */ + +static void +updateFormCtls( FormPtr form, ConnsDlgState* state ) +{ + const XP_U16 relayCtls[] = { +#ifdef XWFEATURE_RELAY + XW_CONNS_RELAY_LABEL_ID, + XW_CONNS_RELAY_FIELD_ID, + XW_CONNS_PORT_LABEL_ID, + XW_CONNS_PORT_FIELD_ID, + XW_CONNS_COOKIE_LABEL_ID, + XW_CONNS_COOKIE_FIELD_ID, +#endif + 0 + }; + const XP_U16 btGuestCtls[] = { +#ifdef XWFEATURE_BLUETOOTH + XW_CONNS_BT_HOSTNAME_LABEL_ID, + XW_CONNS_BT_HOSTTRIGGER_ID, + XW_CONNS_BTCONFIRM_CHECKBOX_ID, +#endif + 0 + }; + const XP_U16* allCtls[] = { + relayCtls, btGuestCtls + }; + const XP_U16* on; + XP_U16 i; + + if ( state->conType == COMMS_CONN_RELAY ) { + on = relayCtls; + } else if ( state->conType == COMMS_CONN_BT +#ifdef XWFEATURE_BLUETOOTH + && state->serverRole == SERVER_ISCLIENT +#endif + ) { + on = btGuestCtls; + } else { + on = NULL; + } + + for ( i = 0; i < VSIZE(allCtls); ++i ) { + const XP_U16* cur = allCtls[i]; + if ( cur != on ) { + disOrEnableSet( form, cur, XP_FALSE ); + } + } + if ( on != NULL ) { + disOrEnableSet( form, on, XP_TRUE ); + } + +} /* updateFormCtls */ + +static CommsConnType +selToConType( const ConnsDlgState* state, XP_U16 sel ) +{ + XP_ASSERT( sel < state->nXports ); + return state->xports[sel].conType; +} /* selToConType */ + +#ifdef XWFEATURE_BLUETOOTH +static void +browseForDeviceName( PalmAppGlobals* globals ) +{ + ConnsDlgState* state = globals->connState; + XP_BtAddr btAddr; + if ( palm_bt_browse_device( globals, &btAddr, + state->hostName, sizeof(state->hostName) ) ) { + CtlSetLabel( getActiveObjectPtr( XW_CONNS_BT_HOSTTRIGGER_ID ), + state->hostName ); + XP_MEMCPY( &state->btAddr, &btAddr, sizeof(state->btAddr) ); + LOG_HEX( &state->btAddr, sizeof(state->btAddr), __func__ ); + } +} /* browseForDeviceName */ +#endif + +static void +setupXportList( PalmAppGlobals* globals ) +{ + ConnsDlgState* state = globals->connState; + ListData* sLd = &state->sLd; + XP_U16 i; + XP_S16 selSel = -1; + const XP_UCHAR* selName = NULL; + + if ( state->nXports >= 2 ) { + state->connTypesList = getActiveObjectPtr( XW_CONNS_TYPE_LIST_ID ); + + initListData( MPPARM(globals->mpool) sLd, state->nXports ); + for ( i = 0; i < state->nXports; ++i ) { + XportEntry* xports = &state->xports[i]; + const XP_UCHAR* xname = getResString( globals, xports->resID ); + addListTextItem( MPPARM(globals->mpool) sLd, xname ); + if ( state->conType == xports->conType ) { + selName = xname; + selSel = i; + } + } + + XP_ASSERT( !!selName ); + setListSelection( sLd, selName ); + setListChoices( sLd, state->connTypesList, NULL ); + + setSelectorFromList( XW_CONNS_TYPE_TRIGGER_ID, state->connTypesList, + selSel ); + } +} /* setupXportList */ + +static void +buildXportData( ConnsDlgState* state ) +{ + XportEntry* xports = state->xports; + XP_ASSERT( 0 == state->nXports ); + +#ifdef XWFEATURE_IR + xports->conType = COMMS_CONN_IR; + xports->resID = STR_IR_XPORTNAME; + ++xports; +#endif +#ifdef XWFEATURE_BLUETOOTH + xports->conType = COMMS_CONN_BT; + xports->resID = STR_BT_XPORTNAME; + ++xports; +#endif +#ifdef XWFEATURE_RELAY + xports->conType = COMMS_CONN_RELAY; + xports->resID = STR_RELAY_XPORTNAME; + ++xports; +#endif + state->nXports = xports - state->xports; + XP_ASSERT( state->nXports >= 2 ); /* no need for dropdown otherwise!! */ +} /* buildXportData */ + +Boolean +ConnsFormHandleEvent( EventPtr event ) +{ + Boolean result; + PalmAppGlobals* globals; + ConnsDlgState* state; + FormPtr form; + XP_S16 chosen; + + CALLBACK_PROLOGUE(); + + globals = getFormRefcon(); + state = globals->connState; + if ( !state ) { + state = globals->connState = XP_MALLOC( globals->mpool, + sizeof(*state) ); + XP_MEMSET( state, 0, sizeof(*state) ); + } + + form = FrmGetActiveForm(); + + switch ( event->eType ) { + case frmOpenEvent: + state->serverRole = + (DeviceRole)globals->dlgParams[CONNS_PARAM_ROLE_INDEX]; + state->addr = + (CommsAddrRec*)globals->dlgParams[CONNS_PARAM_ADDR_INDEX]; + state->isNewGame = globals->isNewGame; + + ctlsFromState( globals ); + + /* setup connection popup */ + buildXportData( state ); + setupXportList( globals ); + + updateFormCtls( form, state ); + + case frmUpdateEvent: + FrmDrawForm( form ); + result = true; + break; + + case ctlSelectEvent: + result = true; + switch ( event->data.ctlSelect.controlID ) { + +#ifdef XWFEATURE_BLUETOOTH + case XW_CONNS_BT_HOSTTRIGGER_ID: + if ( state->isNewGame ) { + browseForDeviceName( globals ); + } + break; +#endif + + case XW_CONNS_TYPE_TRIGGER_ID: + if ( state->isNewGame ) { + chosen = LstPopupList( state->connTypesList ); + if ( chosen >= 0 ) { + setSelectorFromList( XW_CONNS_TYPE_TRIGGER_ID, + state->connTypesList, chosen ); + state->conType = selToConType( state, chosen ); + updateFormCtls( form, state ); + } + } + break; + + case XW_CONNS_OK_BUTTON_ID: + if ( !state->isNewGame ) { + /* do nothing; same as cancel */ + } else { + XP_Bool prefsChanged; + if ( !stateFromCtls( globals, &prefsChanged ) ) { + break; /* refuse to exit */ + } + if ( prefsChanged ) { + postEmptyEvent( prefsChangedEvent ); + } + } + /* FALLTHRU */ + case XW_CONNS_CANCEL_BUTTON_ID: + freeListData( MPPARM(globals->mpool) &globals->connState->sLd ); + XP_FREE( globals->mpool, globals->connState ); + globals->connState = NULL; + FrmReturnToForm( 0 ); + break; + } + break; + default: + result = false; + } + + CALLBACK_EPILOGUE(); + return result; +} /* ConnsFormHandleEvent */ + +#endif /* #if defined XWFEATURE_BLUETOOTH || defined XWFEATURE_RELAY */ diff --git a/xwords4/palm/connsdlg.h b/xwords4/palm/connsdlg.h new file mode 100644 index 000000000..30b300c9d --- /dev/null +++ b/xwords4/palm/connsdlg.h @@ -0,0 +1,37 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2003 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CONNSDLG_H_ +#define _CONNSDLG_H_ + +#include + + +Boolean ConnsFormHandleEvent( EventPtr event ); + +#define CONNS_PARAM_ROLE_INDEX 0 +#define CONNS_PARAM_ADDR_INDEX 1 + +#define PopupConnsForm( g, h, addrP ) { \ + (g)->dlgParams[CONNS_PARAM_ROLE_INDEX] = (XP_U32)(h); \ + (g)->dlgParams[CONNS_PARAM_ADDR_INDEX] = (XP_U32)(addrP); \ + FrmPopupForm( XW_CONNS_FORM ); \ +} + +#endif diff --git a/xwords4/palm/dictlist.c b/xwords4/palm/dictlist.c new file mode 100644 index 000000000..6bd244373 --- /dev/null +++ b/xwords4/palm/dictlist.c @@ -0,0 +1,600 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/**************************************************************************** + * + * Copyright 1999 - 2003 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + ****************************************************************************/ + +#include +#include +#include +#include + +#include "callback.h" +#include "dictlist.h" +#include "palmmain.h" +#include "palmutil.h" +#include "palmdict.h" +#include "strutils.h" +#include "xwords4defines.h" +#include "LocalizedStrIncludes.h" + +#define TYPE_DAWG 'DAWG' +#ifdef NODE_CAN_4 +# define TYPE_XWRDICT 'XwrD' +#else +# define TYPE_XWRDICT 'Xwr3' +#endif + +////////////////////////////////////////////////////////////////////////////// +// typedef and #defines +////////////////////////////////////////////////////////////////////////////// + +struct PalmDictList { + XP_U16 nDicts; + DictListEntry dictArray[1]; +}; + +////////////////////////////////////////////////////////////////////////////// +// Prototypes +////////////////////////////////////////////////////////////////////////////// +static PalmDictList* dictListMakePriv( MPFORMAL XP_U32 creatorSought, + XP_U16 versSought ); + + +XP_Bool +getNthDict( const PalmDictList* dl, short n, DictListEntry** dle ) +{ + XP_Bool exists = !!dl && (dl->nDicts > n); + if ( exists ) { + *dle = (DictListEntry*)&dl->dictArray[n]; + } + return exists; +} /* getNthDict */ + +XP_Bool +getDictWithName( const PalmDictList* dl, const XP_UCHAR* name, + DictListEntry** dlep ) +{ + XP_Bool result = XP_FALSE; + + if ( !!dl ) { + XP_U16 i; + XP_UCHAR* extName; + XP_UCHAR oldChName = '\0'; /* shut compiler up */ + DictListEntry* dle = (DictListEntry*)dl->dictArray; + + extName = (XP_UCHAR*)StrStr((const char*)name, + (const char*)".pdb" ); + + if ( !!extName ) { + oldChName = *extName; + *extName = '\0'; + } + + for ( i = 0; !result && i < dl->nDicts; ++i ) { + XP_UCHAR* extCand; + XP_UCHAR oldChCand = '\0'; + + extCand = (XP_UCHAR*)StrStr((const char*)dle->baseName, ".pdb" ); + if ( !!extCand ) { + oldChCand = *extCand; + *extCand = '\0'; + } + + if ( 0 == XP_STRCMP( (const char*)name, + (const char*)dle->baseName ) ) { + + *dlep = dle; + result = XP_TRUE; + } + + if ( !!extCand ) { + *extCand = oldChCand; + } + + ++dle; + } + + if ( !!extName ) { + *extName = oldChName; + } + + } + return result; +} /* getDictWithName */ + +void +cacheDictForName( PalmDictList* dl, const XP_UCHAR* dictName, + DictionaryCtxt* dict ) +{ + DictListEntry* dle; + (void)getDictWithName(dl, dictName, &dle ); + XP_ASSERT( getDictWithName(dl, dictName, &dle ) ); + XP_ASSERT( !dle->dict ); + + dle->dict = dict; +} /* cacheDictForName */ + +void +removeFromDictCache( PalmDictList* dl, XP_UCHAR* dictName ) +{ + DictListEntry* dle; + (void)getDictWithName( dl, dictName, &dle ); + XP_ASSERT( getDictWithName( dl, dictName, &dle ) ); + XP_ASSERT( !!dle->dict ); + + dle->dict = NULL; +} /* removeFromDictCache */ + +static XP_Bool +addEntry( MPFORMAL PalmDictList** dlp, DictListEntry* dle ) +{ + XP_Bool isNew; + PalmDictList* dl = *dlp; + DictListEntry* ignore; + + if ( !dl ) { + dl = (PalmDictList*)XP_MALLOC( mpool, sizeof(*dl) ); + XP_MEMSET( dl, 0, sizeof(*dl) ); + } + + isNew = !getDictWithName( dl, dle->baseName, &ignore ); + if ( isNew ) { + XP_U16 size = sizeof(*dl); + size += dl->nDicts * sizeof( dl->dictArray[0] ); + + dl = (PalmDictList*)XP_REALLOC( mpool, (XP_U8*)dl, size ); + + dle->dict = NULL; + XP_MEMCPY( &dl->dictArray[dl->nDicts++], dle, + sizeof( dl->dictArray[0] ) ); + } + + *dlp = dl; + return isNew; +} /* addEntry */ + +static void +searchDir( MPFORMAL PalmDictList** dlp, UInt16 volNum, + unsigned char XP_UNUSED(separator), + unsigned char* path, XP_U16 pathSize, XP_U32 creatorSought, + XP_U16 versSought ) +{ + Err err; + FileRef dirRef; + XP_U16 pathLen = XP_STRLEN( (const char*)path ); + + err = VFSFileOpen( volNum, (const char*)path, vfsModeRead, &dirRef ); + if ( err == errNone ) { + UInt32 dEnum = vfsIteratorStart; + FileInfoType fit; + + fit.nameP = (char*)path + pathLen; + + while ( dEnum != vfsIteratorStop ) { + XP_UCHAR* ext; + fit.nameBufLen = pathSize - pathLen; + err = VFSDirEntryEnumerate( dirRef, &dEnum, &fit ); + + if ( err != errNone ) { + break; + } + + if ( (fit.attributes & vfsFileAttrDirectory) != 0 ) { +#ifdef RECURSIVE_VFS_SEARCH + XP_U16 len = XP_STRLEN((const char*)path); + path[len] = separator; + path[len+1] = '\0'; + searchDir( MPPARM(mpool) dlp, volNum, separator, + path, pathSize, creatorSought, versSought ); +#endif + } else if ( (ext = (XP_UCHAR*)StrStr( (const char*)path, ".pdb" )) + != NULL ) { + + /* find out if it's a crosswords dict. */ + FileRef fileRef; + UInt32 type, creator; + + err = VFSFileOpen( volNum, (const char*)path, vfsModeRead, + &fileRef ); + if ( err == errNone ) { + XP_U16 vers; + err = VFSFileDBInfo( fileRef, NULL, /* name */ + NULL, /* attributes */ + &vers, /* versionP */ + NULL, /* crDateP */ + NULL, NULL, /*UInt32 *modDateP, UInt32 *bckUpDateP,*/ + NULL, NULL, /*UInt32 *modNumP, MemHandle *appInfoHP,*/ + NULL, /*MemHandle *sortInfoHP, */ + &type, &creator, + NULL ); /* nRecords */ + VFSFileClose( fileRef ); + + if ( (err == errNone) && (type == TYPE_DAWG) && + (creator == creatorSought) && (vers == versSought) ) { + DictListEntry dl; + + dl.path = copyString( mpool, path ); + dl.location = DL_VFS; + dl.u.vfsData.volNum = volNum; + dl.baseName = dl.path + pathLen; + + if ( !addEntry( MPPARM(mpool) dlp, &dl ) ) { + XP_FREE( mpool, dl.path ); + } + } + } + } + } + + path[pathLen] = '\0'; + VFSFileClose( dirRef ); + } + +} /* searchDir */ + +static void +tryVFSSearch( MPFORMAL PalmDictList** dlp, XP_U32 creatorSought, + XP_U16 versSought ) +{ + Err err; + UInt16 volNum; + UInt32 vEnum; + + vEnum = vfsIteratorStart; + while ( vEnum != vfsIteratorStop ) { + unsigned char pathStr[265]; + MemHandle h; + UInt16 bufLen; + + err = VFSVolumeEnumerate( &volNum, &vEnum ); + if ( err != errNone ) { + break; + } + + /* Search from the default (/palm/Launcher, normally) */ + bufLen = sizeof(pathStr); + err = VFSGetDefaultDirectory( volNum, ".pdb", (char*)pathStr, + &bufLen ); + if ( err == errNone ) { + searchDir( MPPARM(mpool) dlp, volNum, pathStr[0], + pathStr, sizeof(pathStr), creatorSought, versSought ); + } + + h = DmGetResource( 'tSTR', 1000 ); + if ( !!h ) { + pathStr[0] = '\0'; + XP_STRCAT( pathStr, MemHandleLock(h) ); + MemHandleUnlock( h ); + DmReleaseResource( h ); + searchDir( MPPARM(mpool) dlp, volNum, pathStr[0], + pathStr, sizeof(pathStr), creatorSought, versSought ); + } + } + +} /* tryVFSSearch */ + +/* if we've allocated extra space in the array as an optimization now's when + * we pull back */ +static void +cleanList( PalmDictList** XP_UNUSED(dl) ) +{ +} /* cleanList */ + +PalmDictList* +DictListMake( MPFORMAL_NOCOMMA ) +{ + return dictListMakePriv( MPPARM(mpool) TYPE_XWRDICT, 1 ); +} + +static PalmDictList* +dictListMakePriv( MPFORMAL XP_U32 creatorSought, XP_U16 versSought ) +{ + Err err; + DmSearchStateType stateType; + UInt32 vers; + PalmDictList* dl = NULL; + UInt16 cardNo; + LocalID dbID; + Boolean newSearch = true; + XP_Bool found = false; + + /* first the DM case */ + while ( !found ) { + err = DmGetNextDatabaseByTypeCreator( newSearch, &stateType, TYPE_DAWG, + creatorSought, + false,// onlyLatestVers, + &cardNo, &dbID ); + if ( err != errNone ) { + break; + } else { + XP_UCHAR nameBuf[33]; + XP_U16 nameLen; + DictListEntry dle; + XP_U16 vers; + + err = DmDatabaseInfo( cardNo, dbID, (char*)nameBuf, NULL, &vers, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL ); + if ( (err == errNone) && (vers == versSought) ) { + nameLen = XP_STRLEN( (const char*)nameBuf ) + 1; + + dle.location = DL_STORAGE; + dle.u.dmData.cardNo = cardNo; + dle.u.dmData.dbID = dbID; + dle.path = dle.baseName = XP_MALLOC( mpool, nameLen ); + XP_MEMCPY( dle.path, nameBuf, nameLen ); + + addEntry( MPPARM(mpool) &dl, &dle ); + } + } + + newSearch = false; + } + + /* then the VFS case */ + err = FtrGet( sysFileCVFSMgr, vfsFtrIDVersion, &vers ); + if ( err == errNone ) { + tryVFSSearch( MPPARM(mpool) &dl, creatorSought, versSought ); + } + + cleanList( &dl ); + + return dl; +} /* dictListMakePriv */ + +void +DictListFree( MPFORMAL PalmDictList* dl ) +{ + if ( !!dl ) { + DictListEntry* dle = dl->dictArray; + XP_U16 i; + + for ( i = 0; i < dl->nDicts; ++i, ++dle ) { + XP_FREE( mpool, dle->path ); + } + + XP_FREE( mpool, dl ); + } +} /* dictListFree */ + +XP_U16 +DictListCount( PalmDictList* dl ) +{ + XP_U16 result; + if ( !dl ) { + result = 0; + } else { + result = dl->nDicts; + } + return result; +} /* dictListCount */ + +#ifdef NODE_CAN_4 + +/***************************************************************************** + * Conversion from old Crosswords DAWG format to new. It should be possible + * for this to go away once the new format's been out for a while. + *****************************************************************************/ + +static XP_Bool +convertOneRecord( DmOpenRef ref, XP_U16 index ) +{ + XP_Bool success = XP_FALSE; + MemHandle h = DmGetRecord( ref, index ); + XP_U16 siz = MemHandleSize( h ); + XP_U8* recPtr; + XP_U16 i; + Err err; + + XP_U16 nRecs = siz / 3; + XP_U8* tmp = MemPtrNew( siz ); + + XP_ASSERT( !!tmp ); + XP_ASSERT( (siz % 3) == 0 ); + + recPtr = MemHandleLock(h); + XP_MEMCPY( tmp, recPtr, siz ); + + for ( i = 0; i < nRecs; ++i ) { + array_edge_old* edge = (array_edge_old*)&tmp[i * 3]; + XP_U8 oldBits = edge->bits; + XP_U8 newBits = 0; + + XP_ASSERT( LETTERMASK_OLD == LETTERMASK_NEW_3 ); + XP_ASSERT( LASTEDGEMASK_OLD == LASTEDGEMASK_NEW ); + newBits |= (oldBits & (LETTERMASK_OLD | LASTEDGEMASK_OLD) ); + + if ( (oldBits & ACCEPTINGMASK_OLD) != 0 ) { + newBits |= ACCEPTINGMASK_NEW; + } + + if ( (oldBits & EXTRABITMASK_OLD) != 0 ) { + newBits |= EXTRABITMASK_NEW; + } + + edge->bits = newBits; + } + + err = DmWrite( recPtr, 0, tmp, siz ); + XP_ASSERT( err == errNone ); + success = err == errNone; + + MemPtrFree( tmp ); + MemHandleUnlock( h ); + DmReleaseRecord( ref, index, true ); + return success; +} /* convertOneRecord */ + +static XP_Bool +convertOneDict( UInt16 cardNo, LocalID dbID ) +{ + Err err; + UInt32 creator; + DmOpenRef ref; + MemHandle h; + dawg_header* header; + dawg_header tmp; + XP_U16 siz; + unsigned char charTableRecNum, firstEdgeRecNum; + XP_U16 nChars; + + /* now modify the flags */ + ref = DmOpenDatabase( cardNo, dbID, dmModeReadWrite ); + XP_ASSERT( ref != 0 ); + h = DmGetRecord( ref, 0 ); + siz = MemHandleSize( h ); + if ( siz < sizeof(*header) ) { + MemHandleResize( h, sizeof(*header) ); + } + + tmp.flags = XP_HTONS(0x0002); + XP_ASSERT( sizeof(tmp.flags) == 2 ); + header = (dawg_header*)MemHandleLock(h); + charTableRecNum = header->charTableRecNum; + firstEdgeRecNum = header->firstEdgeRecNum; + DmWrite( header, OFFSET_OF(dawg_header,flags), &tmp.flags, + sizeof(tmp.flags) ); + MemHandleUnlock(h); + DmReleaseRecord( ref, 0, true ); + + /* Now convert to 16-bit psuedo-unicode */ + h = DmGetRecord( ref, charTableRecNum ); + XP_ASSERT( !!h ); + nChars = MemHandleSize( h ); + err = MemHandleResize( h, nChars * 2 ); + XP_ASSERT( err == errNone ); + + if ( err == errNone ) { + XP_U8 buf[(MAX_UNIQUE_TILES+1)*2]; + XP_S16 i; + XP_U8* ptr = (XP_U8*)MemHandleLock( h ); + + XP_MEMSET( buf, 0, sizeof(buf) ); + + for ( i = 0; i < nChars; ++i ) { + buf[(i*2)+1] = ptr[i]; + } + DmWrite( ptr, 0, buf, nChars * 2 ); + MemHandleUnlock(h); + } + err = DmReleaseRecord( ref, charTableRecNum, true ); + XP_ASSERT( err == errNone ); + + /* Now transpose the accepting and extra bits for every node. */ + if ( err == errNone ) { + XP_U32 nRecords = DmNumRecords(ref); + XP_U16 i; + + for ( i = firstEdgeRecNum; i < nRecords; ++i ) { + convertOneRecord( ref, i ); + } + } + + err = DmCloseDatabase( ref ); + XP_ASSERT( err == errNone ); + + if ( err == errNone ) { + XP_U16 newVers = 1; + creator = TYPE_XWRDICT; + err = DmSetDatabaseInfo( cardNo, dbID, NULL, + NULL, &newVers, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + &creator ); + XP_ASSERT( err == errNone ); + } + return err == errNone; +} /* convertOneDict */ + +static XP_Bool +confirmDictConvert( PalmAppGlobals* globals, const XP_UCHAR* name ) +{ + XP_UCHAR buf[128]; + const XP_UCHAR *fmt = getResString( globals, STRS_CONFIRM_ONEDICT ); + XP_ASSERT( !!fmt ); + XP_SNPRINTF( buf, sizeof(buf), fmt, name ); + return palmask( globals, buf, NULL, -1 ); +} /* confirmDictConvert */ + +void +offerConvertOldDicts( PalmAppGlobals* globals ) +{ + PalmDictList* dl = dictListMakePriv( MPPARM(globals->mpool) 'Xwr3', 0 ); + XP_U16 count = DictListCount(dl); + Err err; + + if ( count > 0 && palmaskFromStrId( globals, STR_CONFIRM_CONVERTDICT, + -1 ) ) { + + XP_U16 i; + for ( i = 0; i < count; ++i ) { + DictListEntry* dle; + if ( getNthDict( dl, i, &dle ) ) { + + if ( dle->location == DL_STORAGE ) { + + if ( confirmDictConvert( globals, dle->baseName ) ) { + convertOneDict( dle->u.dmData.cardNo, + dle->u.dmData.dbID ); + } + + } else { + + if ( confirmDictConvert( globals, dle->baseName ) ) { + + UInt16 cardNo; + LocalID dbID; + UInt16 volRefNum = dle->u.vfsData.volNum; + + XP_ASSERT( dle->location == DL_VFS ); + + XP_LOGF( "trying %s", dle->path ); + + /* copy from SD card to storage, convert, copy back */ + err = VFSImportDatabaseFromFile( volRefNum, + (const char*)dle->path, + &cardNo, &dbID ); + XP_LOGF( "VFSImportDatabaseFromFile => %d", err ); + if ( err == errNone && convertOneDict( cardNo, dbID ) ) { + + err = VFSFileDelete( volRefNum, dle->path ); + XP_LOGF( "VFSFileDelete=>%d", err ); + if ( err == errNone ) { + + err = VFSExportDatabaseToFile( volRefNum, + (const char*)dle->path, + cardNo, dbID ); + + XP_LOGF( "VFSExportDatabaseToFile => %d", err ); + + XP_ASSERT( err == errNone ); + err = DmDeleteDatabase( cardNo, dbID ); + XP_ASSERT( err == errNone ); + } + } + } + } + } + } + } + + DictListFree( MPPARM(globals->mpool) dl ); +} /* offerConvertOldDicts */ +#endif diff --git a/xwords4/palm/dictlist.h b/xwords4/palm/dictlist.h new file mode 100644 index 000000000..f6aa1d27a --- /dev/null +++ b/xwords4/palm/dictlist.h @@ -0,0 +1,55 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 1999 - 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef _DICTLIST_H_ +#define _DICTLIST_H_ + +#include "palmmain.h" +#include "palmdict.h" + +enum { DL_STORAGE, DL_VFS }; + +typedef struct DictListEntry { + XP_UCHAR* path; + XP_UCHAR* baseName; /* points into, or ==, path */ + DictionaryCtxt* dict; /* cache so can refcount */ + XP_UCHAR location; /* Storage RAM or VFS */ + union { + struct { + UInt16 cardNo; + LocalID dbID; + } dmData; + struct { + UInt16 volNum; + } vfsData; + } u; +} DictListEntry; + +PalmDictList* DictListMake( MPFORMAL_NOCOMMA ); +void DictListFree( MPFORMAL PalmDictList* dl ); +XP_U16 DictListCount( PalmDictList* dl ); + +XP_Bool getDictWithName( const PalmDictList* dl, const unsigned char* name, + DictListEntry** dle ); +void cacheDictForName( PalmDictList* dl, const XP_UCHAR* dictName, + DictionaryCtxt* ctxt ); +void removeFromDictCache( PalmDictList* dl, XP_UCHAR* dictName ); + +XP_Bool getNthDict( const PalmDictList* dl, short n, DictListEntry** dle ); + +#endif diff --git a/xwords4/palm/dictui.c b/xwords4/palm/dictui.c new file mode 100644 index 000000000..f8b5080df --- /dev/null +++ b/xwords4/palm/dictui.c @@ -0,0 +1,286 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/**************************************************************************** + * + * Copyright 1999 - 2003 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + ****************************************************************************/ + +#include +#include +#include +#include + +#include "callback.h" +#include "dictui.h" +#include "palmmain.h" +#include "palmutil.h" +#include "palmdict.h" +#include "dictlist.h" +#include "strutils.h" +#include "xwords4defines.h" + +#define TYPE_DAWG 'DAWG' +#define TYPE_XWR3 'Xwr3' + +////////////////////////////////////////////////////////////////////////////// +// Prototypes +////////////////////////////////////////////////////////////////////////////// +static XP_U16 populateDictionaryList( MPFORMAL ListData* sLd, + const XP_UCHAR* curDictName, + ListPtr list, Int16 triggerID, + PalmDictList* dl ); +static Boolean beamDict( const PalmDictList* dl, XP_UCHAR* dictName ); + +/***************************************************************************** + * Handler for dictionary info form. + ****************************************************************************/ +#define USE_POPULATE 1 +Boolean +dictFormHandleEvent( EventPtr event ) +{ + FormPtr form; + Boolean result; + Int16 chosen; + DictionaryCtxt* dict; + PalmAppGlobals* globals; + XP_UCHAR* dictName; + + CALLBACK_PROLOGUE(); + + result = false; + globals = getFormRefcon(); + + switch ( event->eType ) { + case frmOpenEvent: { + const XP_UCHAR* curName; + XP_U16 width; + RectangleType rect; + + form = FrmGetActiveForm(); + + /* we're either a beam dlg or a dict picker; disable a button here. */ + disOrEnable( form, + globals->dictuiForBeaming ? + XW_DICTINFO_DONE_BUTTON_ID:XW_DICTINFO_BEAM_BUTTON_ID, + false ); + + /* dictionary list setup */ + globals->dictState.dictList = + getActiveObjectPtr( XW_DICTINFO_LIST_ID ); + + dict = !!globals->game.model? + model_getDictionary(globals->game.model) : NULL; + if ( dict ) { + curName = dict_getName( dict ); + } else { + curName = NULL; + } + width = populateDictionaryList( MPPARM(globals->mpool) + &globals->dictState.sLd, + curName, globals->dictState.dictList, + XW_DICTINFO_TRIGGER_ID, + globals->dictList ); + getObjectBounds( XW_DICTINFO_LIST_ID, &rect ); + rect.extent.x = width; + setObjectBounds( XW_DICTINFO_LIST_ID, &rect ); + + FrmDrawForm( form ); + break; + } + + case ctlSelectEvent: + switch ( event->data.ctlEnter.controlID ) { + + case XW_DICTINFO_TRIGGER_ID: + // don't let change dict except first time + if ( globals->dictuiForBeaming || globals->isNewGame ) { + chosen = LstPopupList( globals->dictState.dictList ); + if ( chosen >= 0 ) { + setSelectorFromList( XW_DICTINFO_TRIGGER_ID, + globals->dictState.dictList, + chosen ); + } + } + result = true; + break; +/* case XW_PHONIES_TRIGGER_ID: */ +/* chosen = LstPopupList( sPhoniesList ); */ +/* if ( chosen >= 0 ) { */ +/* setTriggerFromList( XW_PHONIES_TRIGGER_ID, sPhoniesList, */ +/* chosen ); */ +/* } */ +/* result = true; */ +/* break; */ + + case XW_DICTINFO_DONE_BUTTON_ID: + case XW_DICTINFO_BEAM_BUTTON_ID: + /* discard the const */ + dictName = (XP_UCHAR*)CtlGetLabel( + getActiveObjectPtr( XW_DICTINFO_TRIGGER_ID) ); + + if ( globals->dictuiForBeaming ) { + if ( !beamDict( globals->dictList, dictName ) ) { + break; /* don't cancel dialog yet */ + } + } else { + EventType eventToPost; + + XP_ASSERT( dictName != NULL ); + + eventToPost.eType = dictSelectedEvent; + ((DictSelectedData*)&eventToPost.data.generic)->dictName + = copyString( globals->mpool, dictName ); + EvtAddEventToQueue( &eventToPost ); + } + + case XW_DICTINFO_CANCEL_BUTTON_ID: + result = true; + freeListData( MPPARM(globals->mpool) &globals->dictState.sLd ); + FrmReturnToForm( 0 ); + break; + + } // switch ( event->data.ctlEnter.controlID ) + break; + + default: + break; + } // switch + + CALLBACK_EPILOGUE(); + return result; +} /* dictFormHandleEvent */ + +/***************************************************************************** + * + ****************************************************************************/ +static XP_U16 +populateDictionaryList( MPFORMAL ListData* sLd, const XP_UCHAR* curDictName, + ListPtr list, Int16 triggerID, PalmDictList* dl ) +{ + XP_U16 i; + XP_U16 maxWidth = 0; + XP_U16 nDicts; + + initListData( MPPARM(mpool) sLd, 16 ); /* PENDING: MAX_DICTS or count */ + nDicts = DictListCount( dl ); + + for ( i = 0; i < nDicts; ++i ) { + DictListEntry* dle; + XP_UCHAR* name; + XP_U16 width; + + getNthDict( dl, i, &dle ); + name = dle->baseName; + + addListTextItem( MPPARM(mpool) sLd, name ); + width = FntCharsWidth( (const char*)name, XP_STRLEN((const char*)name) ); + if ( width > maxWidth ) { + maxWidth = width; + } + } + + sortList( sLd ); + setListSelection( sLd, (char*)curDictName ); + setListChoices( sLd, list, NULL ); + + setSelectorFromList( triggerID, list, LstGetSelection(list) ); + + return maxWidth + 3; /* 3: for white space */ +} /* populateDictionaryList */ + +/*********************************************************************** + * The rest of this file mostly stolen from palmos.com. I've only modified + * beamDict + ************************************************************************/ +static Err +WriteDBData(const void* dataP, UInt32* sizeP, void* userDataP) +{ + Err err; + + /* Try to send as many bytes as were requested by the caller */ + *sizeP = ExgSend((ExgSocketPtr)userDataP, (void*)dataP, *sizeP, &err); + return err; +} /* WriteDBData */ + +Err +sendDatabase( UInt16 cardNo, LocalID dbID, XP_UCHAR* nameP, + XP_UCHAR* descriptionP ) +{ + ExgSocketType exgSocket; + Err err; + + /* Create exgSocket structure */ + XP_MEMSET( &exgSocket, 0, sizeof(exgSocket) ); + exgSocket.description = (char*)descriptionP; + exgSocket.name = (char*)nameP; + + /* Start an exchange put operation */ + err = ExgPut(&exgSocket); + if ( !err ) { + err = ExgDBWrite( WriteDBData, &exgSocket, NULL, dbID, cardNo ); + /* Disconnect Exg and pass error */ + err = ExgDisconnect(&exgSocket, err); + } + return err; +} /* sendDatabase */ + +static Boolean +beamDict( const PalmDictList* dl, XP_UCHAR* dictName ) +{ + Err err; + UInt16 cardNo; + LocalID dbID; + Boolean found; + XP_Bool shouldDispose = XP_FALSE; + DictListEntry* dle; + + found = getDictWithName( dl, dictName, &dle ); + + /* Find our app using its internal name */ + XP_ASSERT( found ); + + if ( found ) { + if ( dle->location == DL_VFS ) { + err = VFSImportDatabaseFromFile( dle->u.vfsData.volNum, + (const char*)dle->path, + &cardNo, &dbID ); + if ( err == dmErrAlreadyExists ) { + } else if ( err == errNone ) { + shouldDispose = XP_TRUE; + } else { + found = XP_FALSE; + } + } else { + cardNo = dle->u.dmData.cardNo; + dbID = dle->u.dmData.dbID; + } + } + + if ( found ) { /* send it giving external name and description */ + XP_UCHAR prcName[40]; + XP_SNPRINTF( prcName, sizeof(prcName), (XP_UCHAR*)"%s.pdb", dictName ); + err = sendDatabase( cardNo, dbID, prcName, dictName ); + found = err == 0; + + if ( shouldDispose ) { + DmDeleteDatabase( cardNo, dbID ); + } + } + + return found; +} /* beamDict */ + diff --git a/xwords4/palm/dictui.h b/xwords4/palm/dictui.h new file mode 100644 index 000000000..5f6ce3369 --- /dev/null +++ b/xwords4/palm/dictui.h @@ -0,0 +1,34 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 1999 - 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef _DICTUI_H_ +#define _DICTUI_H_ + +#include "palmmain.h" +#include "palmdict.h" + +typedef struct DictSelectedData { + MemPtr dictName; +} DictSelectedData; + +Boolean dictFormHandleEvent( EventPtr event ); + +/* export for beamBoard */ +Err sendDatabase( UInt16 cardNo, LocalID dbID, XP_UCHAR* nameP, + XP_UCHAR* descriptionP ); +#endif diff --git a/xwords4/palm/enter68k.c b/xwords4/palm/enter68k.c new file mode 100644 index 000000000..8505e234d --- /dev/null +++ b/xwords4/palm/enter68k.c @@ -0,0 +1,250 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2004 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "pnostate.h" +#include "palmmain.h" /* for Ftr enum */ + +static void +write_byte32( void* dest, UInt32 val ) +{ + MemMove( dest, &val, sizeof(val) ); +} + +static UInt32 +byte_swap32( UInt32 in ) +{ + UInt32 tmp = 0L; + tmp |= (in & 0x000000FF) << 24; + tmp |= (in & 0x0000FF00) << 8; + tmp |= (in & 0x00FF0000) >> 8; + tmp |= (in & 0xFF000000) >> 24; + return tmp; +} + +static void +alertUser( char* str ) +{ + (void)FrmCustomAlert( XW_ERROR_ALERT_ID, + str, " ", " " ); +} + +typedef struct PNOFtrHeader { + UInt32* gotTable; +} PNOFtrHeader; + +void +storageCallback( void/*PnoletUserData*/* _dataP ) +{ + PnoletUserData* dataP = (PnoletUserData*)_dataP; + UInt32 offset; + PNOFtrHeader* ftrBase; + + if ( dataP->recursive ) { + WinDrawChars( "ERROR: overwriting", 13, 5, 60 ); +#ifdef DEBUG + for ( ; ; ); /* make sure we see it. :-) */ +#endif + } + + ftrBase = (PNOFtrHeader*)dataP->pnoletEntry; + --ftrBase; /* back up over header */ + offset = (char*)dataP->stateDest - (char*)ftrBase; + DmWrite( ftrBase, offset, dataP->stateSrc, sizeof(PNOState) ); +} + +static void +countOrLoadPNOCs( UInt32* pnoSizeP, UInt8* base, UInt32 offset ) +{ + DmResID id; + + for ( id = 0; ; ++id ) { + UInt32 size; + MemHandle h = DmGetResource( 'Pnoc', id ); + + if ( !h ) { + break; + } + size = MemHandleSize( h ); + if ( !!base ) { + Err err = DmWrite( base, offset, MemHandleLock(h), size ); + if ( err != errNone ) { + alertUser( "error from DmWrite" ); + } + MemHandleUnlock(h); + } + DmReleaseResource(h); + offset += size; + } + + if ( !!pnoSizeP ) { + *pnoSizeP = offset; + } +} /* countOrLoadPNOCs */ + +/* Return true if we had to load the pnolet. If we didn't, then we're being + * called recursively (probably because of ExgMgr activity); in that case the + * caller better not unload! + */ +static Boolean +setupPnolet( UInt32** entryP, UInt32** gotTableP ) +{ + PNOFtrHeader* ftrBase; + Err err = FtrGet( APPID, PNOLET_STORE_FEATURE, (UInt32*)&ftrBase ); + XP_Bool mustLoad = err != errNone; + + if ( mustLoad ) { + UInt32* gotTable; + UInt32 pnoSize, gotSize, pad; + UInt32 ftrSize = sizeof( PNOFtrHeader ); + UInt32* pnoCode; + PNOFtrHeader header; + + // LOAD: GOT table + MemHandle h = DmGetResource( 'Pnog', 0 ); + if ( !h ) { + gotSize = 0; + gotTable = NULL; + } else { + gotSize = MemHandleSize( h ); + gotTable = (UInt32*)MemPtrNew( gotSize ); + MemMove( gotTable, MemHandleLock(h), gotSize ); + MemHandleUnlock( h ); + DmReleaseResource( h ); + } + ftrSize += gotSize; + + countOrLoadPNOCs( &pnoSize, NULL, 0 ); + ftrSize += pnoSize; + pad = (4 - (pnoSize & 3)) & 3; + ftrSize += pad; + + FtrPtrNew( APPID, PNOLET_STORE_FEATURE, ftrSize, (void**)&ftrBase ); + pnoCode = (UInt32*)&ftrBase[1]; + + countOrLoadPNOCs( NULL, (UInt8*)ftrBase, sizeof(PNOFtrHeader) ); + + if ( gotSize > 0 ) { + UInt32 cnt = gotSize >> 2; + UInt32 i; + for ( i = 0; i < cnt; ++i ) { + write_byte32(&gotTable[i], + byte_swap32(byte_swap32(gotTable[i]) + + (UInt32)pnoCode)); + } + + DmWrite( ftrBase, sizeof(PNOFtrHeader) + pnoSize + pad, + gotTable, gotSize ); + MemPtrFree( gotTable ); + + header.gotTable = (UInt32*)(((char*)pnoCode) + pnoSize + pad); + DmWrite( ftrBase, 0, &header, sizeof(header) ); + } + } + + *gotTableP = ftrBase->gotTable; + *entryP = (UInt32*)&ftrBase[1]; + + return mustLoad; +} /* setupPnolet */ + +static Boolean +shouldRunPnolet() +{ + UInt32 value; + Boolean runArm = false; + Err err = FtrGet( sysFtrCreator, sysFtrNumProcessorID, &value ); + + if ( ( err == errNone ) && sysFtrNumProcessorIsARM( value ) ) { + runArm = true; + } +#ifdef FEATURE_DUALCHOOSE + if ( runArm ) { + err = FtrGet( APPID, FEATURE_WANTS_68K, &value ); + if ( (err == errNone) && (value == WANTS_68K) ) { + runArm = false; + } + } +#endif + return runArm; +} /* shouldRunPnolet */ + +UInt32 +PilotMain( UInt16 cmd, MemPtr cmdPBP, UInt16 launchFlags) +{ + UInt32 result = 0; + if ( ( cmd == sysAppLaunchCmdNormalLaunch ) +#ifdef XWFEATURE_IR + || ( cmd == sysAppLaunchCmdExgAskUser ) + || ( cmd == sysAppLaunchCmdSyncNotify ) + || ( cmd == sysAppLaunchCmdExgReceiveData ) +#endif + ) { + + /* This is the entry point for both an ARM-only app and one capable + of doing both. If the latter, and ARM's an option, we want to use + it unless the user's said not to. */ + + if ( shouldRunPnolet() ) { + UInt32* gotTable; + PnoletUserData* dataP; + UInt32* pnoCode; + UInt32 result; + Boolean loaded; + +#ifdef DEBUG + WinDrawChars( "Loading ARM code...", 19, 5, 25 ); +#endif + + loaded = setupPnolet( &pnoCode, &gotTable ); + dataP = (PnoletUserData*)MemPtrNew( sizeof(PnoletUserData) ); + dataP->recursive = !loaded; + + dataP->pnoletEntry = pnoCode; + dataP->gotTable = gotTable; + dataP->storageCallback = storageCallback; + + dataP->cmdPBP = cmdPBP; + dataP->cmd = cmd; + dataP->launchFlags = launchFlags; + + result = PceNativeCall((NativeFuncType*)pnoCode, (void*)dataP ); + MemPtrFree( dataP ); + + if ( loaded ) { + FtrPtrFree( APPID, PNOLET_STORE_FEATURE ); + } + } else { +#ifdef FEATURE_PNOAND68K + result = PM2(PilotMain)( cmd, cmdPBP, launchFlags); +#else + alertUser( "This copy of Crosswords runs only on ARM-based Palms. " + "Get the right version at xwords.sf.net." ); +#endif + } + } + + return result; +} /* PilotMain */ diff --git a/xwords4/palm/fnavgen.c b/xwords4/palm/fnavgen.c new file mode 100644 index 000000000..07f53ee9b --- /dev/null +++ b/xwords4/palm/fnavgen.c @@ -0,0 +1,186 @@ +/* -*- mode: c; -*- */ + +#include +#include + +#include "xwords4defines.h" + +typedef struct fnavElem { + unsigned short objectID; + unsigned short objectFlags; + unsigned short objectAbove; + unsigned short objectBelow; +} fnavElem; + +typedef struct fnavHeader { + unsigned short formID; /* not part of resource!!!! */ + + unsigned short version; /* always 1 */ + unsigned short objcount; + unsigned short headerSizeInBytes; /* always 20 */ + unsigned short listSizeInBytes; /* always 8 */ + unsigned short navflags; + unsigned short initialHint; + unsigned short jumpToHint; + unsigned short bottomLeftHint; +} fnavHeader; + + +fnavHeader gamesHeader = { + XW_NEWGAMES_FORM, + 0, /* fill this in */ + 0, /* fill this in */ + 0, /* fill this in */ + 0, /* fill this in */ + 0x0001, /* force obj focus mode */ + 0, + 0, + 0, +}; + +fnavElem gamesElems[] = { + { XW_ROBOT_1_CHECKBOX_ID, 0, 0, XW_ROBOT_2_CHECKBOX_ID }, + { XW_ROBOT_2_CHECKBOX_ID, 0, XW_ROBOT_1_CHECKBOX_ID, XW_ROBOT_3_CHECKBOX_ID }, + { XW_ROBOT_3_CHECKBOX_ID, 0, XW_ROBOT_2_CHECKBOX_ID, XW_ROBOT_4_CHECKBOX_ID }, + { XW_ROBOT_4_CHECKBOX_ID, 0, XW_ROBOT_3_CHECKBOX_ID, XW_SOLO_GADGET_ID }, + + { XW_SOLO_GADGET_ID, 0, XW_ROBOT_4_CHECKBOX_ID, XW_SERVER_GADGET_ID }, + { XW_SERVER_GADGET_ID, 0, XW_SOLO_GADGET_ID, XW_CLIENT_GADGET_ID }, + { XW_CLIENT_GADGET_ID, 0, XW_SERVER_GADGET_ID, 0 } +}; + +unsigned short gamesElemsShort[] = { + +#ifndef XWFEATURE_STANDALONE_ONLY + XW_SOLO_GADGET_ID, + XW_SERVER_GADGET_ID, + XW_CLIENT_GADGET_ID, +#endif + + XW_NPLAYERS_SELECTOR_ID, + XW_PREFS_BUTTON_ID, + + XW_REMOTE_1_CHECKBOX_ID, + XW_PLAYERNAME_1_FIELD_ID, + XW_ROBOT_1_CHECKBOX_ID, + XW_PLAYERPASSWD_1_TRIGGER_ID, + + XW_REMOTE_2_CHECKBOX_ID, + XW_PLAYERNAME_2_FIELD_ID, + XW_ROBOT_2_CHECKBOX_ID, + XW_PLAYERPASSWD_2_TRIGGER_ID, + + XW_REMOTE_3_CHECKBOX_ID, + XW_PLAYERNAME_3_FIELD_ID, + XW_ROBOT_3_CHECKBOX_ID, + XW_PLAYERPASSWD_3_TRIGGER_ID, + + XW_REMOTE_4_CHECKBOX_ID, + XW_PLAYERNAME_4_FIELD_ID, + XW_ROBOT_4_CHECKBOX_ID, + XW_PLAYERPASSWD_4_TRIGGER_ID, + + XW_DICT_SELECTOR_ID, + XW_CANCEL_BUTTON_ID, + XW_OK_BUTTON_ID + +}; + +static void +usage( char* name ) +{ + fprintf( stderr, "usage: %s outfile\n", name ); + exit( 1 ); +} /* */ + +static void +write_network_short( FILE* fil, unsigned short s ) +{ + unsigned short tmp = htons( s ); + fwrite( &tmp, sizeof(tmp), 1, fil ); +} /* write_network_short */ + +write_fnav( fnavHeader* header, unsigned short* idArray, + fnavElem* elems, int count ) +{ + char nameBuf[32]; + FILE* fil; + int i; + + assert( !idArray || !elems ); + + sprintf( nameBuf, "fnav%.4x.bin", header->formID ); + fil = fopen( nameBuf, "w" ); + fprintf( stderr, "created file %s\n", nameBuf ); + + write_network_short( fil, 1 ); + write_network_short( fil, count ); + write_network_short( fil, 20 ); + write_network_short( fil, 8 ); + write_network_short( fil, header->navflags ); + write_network_short( fil, header->initialHint ); + write_network_short( fil, header->jumpToHint ); + write_network_short( fil, header->bottomLeftHint ); + + /* Two words of padding. Docs disagree, but Blazer's resources have + 'em */ + write_network_short( fil, 0 ); + write_network_short( fil, 0 ); + + if ( !!elems ) { + + for ( i = 0; i < count; ++i ) { + write_network_short( fil, elems->objectID ); + write_network_short( fil, elems->objectFlags ); + write_network_short( fil, elems->objectAbove ); + write_network_short( fil, elems->objectBelow ); + + ++elems; + } + + } else { + unsigned short prevID = 0; + unsigned short id = *idArray++; + + while ( count-- ) { + unsigned short nextID; + + if ( count == 0 ) { + nextID = 0; + } else { + nextID = *idArray++; + } + + write_network_short( fil, id ); + write_network_short( fil, 0 ); + write_network_short( fil, prevID ); + write_network_short( fil, nextID ); + + if ( !nextID ) { + break; + } + + prevID = id; + id = nextID; + } + } + + fclose(fil); +} /* write_fnav */ + +int +main( int argc, char** argv ) +{ + int i; + char* outFName; + + if ( argc != 1 ) { + usage( argv[0] ); + } + + write_fnav( &gamesHeader, gamesElemsShort, NULL, + VSIZE(gamesElemsShort) ); +/* sizeof(gamesElems)/sizeof(gamesElems[0]) ); */ + + +} /* main */ diff --git a/xwords4/palm/funcfile.txt b/xwords4/palm/funcfile.txt new file mode 100644 index 000000000..a8ce437cb --- /dev/null +++ b/xwords4/palm/funcfile.txt @@ -0,0 +1,254 @@ +# PalmOS functions listed here will have PACE-interface bodies +# generated for them. For most, that's enough. Some will require +# manual work beyond that. +# +# Just to have a record, list the ones that must be hand-coded here. +# StrVPrintF +# StrPrintF +# SysHandleEvent +# FrmSetEventHandler +# LstSetListChoices +# SysNotifyRegister +# LstSetDrawFunction +# ExgDBWrite +# +################################################################# +CtlEnabled +CtlGetLabel +CtlGetValue +CtlSetEnabled +CtlSetLabel +CtlSetValue +DateToAscii +DmCloseDatabase +DmCreateDatabase +DmDatabaseInfo +DmDeleteDatabase +DmFindDatabase +DmFindResource +DmFindResourceType +DmGetNextDatabaseByTypeCreator +DmGetRecord +DmGetResource +DmGetResourceIndex +DmNewRecord +DmNewResource +DmNumRecords +DmOpenDatabase +DmQueryRecord +DmReleaseRecord +DmReleaseResource +DmRemoveRecord +DmWrite +DmWriteCheck +EvtSysEventAvail +ExgAccept +ExgDisconnect +ExgPut +ExgReceive +ExgRegisterData +ExgSend +FldCalcFieldHeight +FldCopy +FldDrawField +FldGetAttributes +FldGetScrollValues +FldGetTextLength +FldGetTextPtr +FldInsert +FldRecalculateField +FldScrollField +FldScrollable +FldSetAttributes +FldSetSelection +FldSetTextPtr +FntCharsWidth +FntGetFont +FntLineHeight +FntSetFont +FrmCloseAllForms +FrmCustomAlert +FrmDeleteForm +FrmDoDialog +FrmDrawForm +FrmGetActiveForm +FrmGetActiveFormID +FrmGetFirstForm +FrmGetFocus +FrmGetFormBounds +FrmGetFormPtr +FrmGetGadgetData +FrmGetNumberOfObjects +FrmGetObjectBounds +FrmGetObjectId +FrmGetObjectIndex +FrmGetObjectPosition +FrmGetObjectPtr +FrmGetObjectType +FrmGetWindowHandle +FrmGotoForm +FrmHideObject +FrmInitForm +FrmPopupForm +FrmReturnToForm +FrmSetActiveForm +FrmSetFocus +FrmSetGadgetData +FrmSetObjectBounds +FrmSetObjectPosition +FrmSetTitle +FrmShowObject +FrmUpdateForm +FtrGet +FtrSet +FtrUnregister +GrfSetState +LstDrawList +LstEraseList +LstGetNumberOfItems +LstGetSelection +LstGetSelectionText +LstMakeItemVisible +LstPopupList +LstSetHeight +LstSetSelection +MemChunkFree +MemHandleLock +MemHandleLockCount +MemHandleResize +MemHandleSize +MemHandleUnlock +MemMove +MemPtrNew +MemPtrRecoverHandle +MemPtrSize +MemPtrUnlock +MemSet +MenuEraseStatus +PrefGetAppPreferences +PrefGetAppPreferencesV10 +PrefSetAppPreferences +RctGetIntersection +RctPtInRectangle +SclSetScrollBar +SndPlaySystemSound +StrAToI +StrCat +StrCompare +StrCopy +StrIToA +StrLen +StrNCompare +StrStr +SysLibFind +SysLibLoad +SysNotifyUnregister +SysRandom +SysTicksPerSecond +TimGetSeconds +TimGetTicks +TimeToAscii +VFSDirEntryEnumerate +VFSFileClose +VFSFileDBInfo +VFSFileOpen +VFSGetDefaultDirectory +VFSImportDatabaseFromFile +VFSVolumeEnumerate +WinDeleteWindow +WinDrawBitmap +WinDrawChars +WinDrawLine +WinDrawPixel +WinDrawRectangle +WinDrawRectangleFrame +WinEraseRectangle +WinEraseRectangleFrame +WinEraseWindow +WinFillRectangle +WinGetClip +WinGetDrawWindowBounds +WinInvertRectangle +WinPopDrawState +WinPushDrawState +WinRGBToIndex +WinRestoreBits +WinSaveBits +WinScreenMode +WinScrollRectangle +WinSetBackColor +WinSetBounds +WinSetClip +WinSetDrawWindow +WinSetForeColor +WinSetPattern +WinSetTextColor +WinEraseLine +SclDrawScrollBar +DmNumRecordsInCategory +DmSeekRecordInCategory +DmResizeRecord +VFSExportDatabaseToFile +VFSFileDelete +DmSetDatabaseInfo +DmGetLastErr +WinRestoreBits +WinSetCoordinateSystem +WinGetDisplayExtent +WinScreenGetAttribute +TimSecondsToDateTime +EvtGetEvent +FrmDispatchEvent +MenuHandleEvent +EvtAddEventToQueue +FtrPtrFree +SysUIAppSwitch +FntBaseLine +WinSetScalingMode +WinGetDrawWindow +############ These only needed if XWFEATURE_RELAY defined +NetLibOpen +NetLibSocketOpen +NetLibClose +NetLibSocketClose +NetLibSend +NetLibReceive +NetLibSelect +NetLibSocketConnect +NetLibSocketOptionSet +# +# These are currently needed only when TALL_FONTS is #defined +# +BmpCreate +BmpDelete +WinCreateBitmapWindow +WinSetDrawMode +WinGetPixel +# +# Needed for five-way navigation +# +UIColorGetTableEntryIndex +#HsNavDrawFocusRing +#ifdef XWFEATURE_BLUETOOTH +BtLibOpen +BtLibAddrBtdToA +BtLibSocketSend +BtLibSocketClose +BtLibLinkConnect +BtLibDiscoverSingleDevice +BtLibLinkDisconnect +BtLibSocketRespondToConnection +BtLibClose +BtLibSocketConnect +BtLibSocketListen +#BtLibRegisterManagementNotification +#BtLibUnregisterManagementNotification +#BtLibSocketCreate +BtLibGetRemoteDeviceName +BtLibSdpServiceRecordCreate +BtLibSdpServiceRecordStopAdvertising +BtLibSdpGetPsmByUuid +BtLibSdpServiceRecordSetAttributesForSocket +BtLibSdpServiceRecordStartAdvertising +BtLibSdpServiceRecordDestroy +#endif diff --git a/xwords4/palm/gameutil.c b/xwords4/palm/gameutil.c new file mode 100644 index 000000000..b1bca3744 --- /dev/null +++ b/xwords4/palm/gameutil.c @@ -0,0 +1,206 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 1999 - 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include + +#include "comtypes.h" +#include "comms.h" +#include "strutils.h" +#include "gameutil.h" +#include "xwords4defines.h" +#include "xwstream.h" +#include "palmmain.h" + +XP_U16 +countGameRecords( PalmAppGlobals* globals ) +{ + LocalID id; + DmOpenRef dbP; + UInt16 numRecs; + + id = DMFINDDATABASE( globals, CARD_0, XW_GAMES_DBNAME ); + dbP = DMOPENDATABASE( globals, CARD_0, id, dmModeWrite ); + numRecs = DmNumRecords( dbP ); + DMCLOSEDATABASE( dbP ); + return numRecs; +} + +void +deleteGameRecord( PalmAppGlobals* globals, XP_S16 index ) +{ + LocalID id; + DmOpenRef dbP; + + id = DMFINDDATABASE( globals, CARD_0, XW_GAMES_DBNAME ); + dbP = DMOPENDATABASE( globals, CARD_0, id, dmModeWrite ); + DmRemoveRecord( dbP, index ); + DMCLOSEDATABASE( dbP ); +} /* deleteGameRecord */ + +XP_S16 +duplicateGameRecord( PalmAppGlobals* globals, XP_S16 fromIndex ) +{ + LocalID id; + DmOpenRef dbP; + MemHandle newRecord, curRecord; + XP_U16 size; + XP_S16 newIndex = fromIndex + 1; + + id = DMFINDDATABASE( globals, CARD_0, XW_GAMES_DBNAME ); + dbP = DMOPENDATABASE( globals, CARD_0, id, dmModeWrite ); + XP_ASSERT( fromIndex < countGameRecords(globals) ); + curRecord = DmQueryRecord( dbP, fromIndex ); + size = MemHandleSize( curRecord ); + newRecord = DmNewRecord( dbP, (XP_U16*)&newIndex, size ); + + DmWrite( MemHandleLock(newRecord), 0, MemHandleLock(curRecord), size ); + + MemHandleUnlock( curRecord ); + MemHandleUnlock( newRecord ); + + DmReleaseRecord( dbP, newIndex, true ); + + DMCLOSEDATABASE( dbP ); + + return newIndex; +} /* duplicateGameRecord */ + +void +streamToGameRecord( PalmAppGlobals* globals, XWStreamCtxt* stream, + XP_S16 index ) +{ + LocalID id; + DmOpenRef dbP; + MemHandle handle; + MemPtr tmpPtr, ptr; + Err err; + + XP_U32 size = stream_getSize( stream ); + + id = DMFINDDATABASE( globals, CARD_0, XW_GAMES_DBNAME ); + dbP = DMOPENDATABASE( globals, CARD_0, id, dmModeWrite ); + XP_ASSERT( !!dbP ); + + if ( index == DmNumRecords(dbP) ) { + handle = DmNewRecord( dbP, (XP_U16*)&index, size ); + } else { + XP_ASSERT( index < countGameRecords(globals) ); + handle = DmGetRecord( dbP, index ); + MemHandleResize( handle, size ); + } + + tmpPtr = MemPtrNew(size); + stream_getBytes( stream, tmpPtr, size ); + ptr = MemHandleLock( handle ); + err = DmWrite( ptr, 0, tmpPtr, size ); + XP_ASSERT( err == 0 ); + MemPtrFree( tmpPtr ); + + MemHandleUnlock(handle); + err = DmReleaseRecord( dbP, index, true ); + XP_ASSERT( err == 0 ); + DMCLOSEDATABASE( dbP ); +} /* streamToGameRecord */ + +void +writeNameToGameRecord( PalmAppGlobals* globals, XP_S16 index, + char* newName, XP_U16 len ) +{ + LocalID id; + DmOpenRef dbP; + MemHandle handle; + char name[MAX_GAMENAME_LENGTH]; + + XP_ASSERT( len == XP_STRLEN(newName) ); + XP_ASSERT( len > 0 ); + XP_MEMSET( name, 0, sizeof(name) ); + if ( len >= MAX_GAMENAME_LENGTH ) { + len = MAX_GAMENAME_LENGTH - 1; + } + XP_MEMCPY( name, newName, len+1 ); + + id = DMFINDDATABASE( globals, CARD_0, XW_GAMES_DBNAME ); + dbP = DMOPENDATABASE( globals, CARD_0, id, dmModeWrite ); + + if ( index == DmNumRecords(dbP) ) { + handle = DmNewRecord(dbP, (XP_U16*)&index, MAX_GAMENAME_LENGTH); + } else { + XP_ASSERT( index < DmNumRecords(dbP) ); + handle = DmGetRecord( dbP, index ); + } + XP_ASSERT( !!handle ); + DmWrite( MemHandleLock(handle), 0, name, MAX_GAMENAME_LENGTH ); + MemHandleUnlock( handle ); + (void)DmReleaseRecord( dbP, index, true ); + DMCLOSEDATABASE( dbP ); +} /* writeNameToGameRecord */ + +void +nameFromRecord( PalmAppGlobals* globals, XP_S16 index, char* buf ) +{ + LocalID id; + DmOpenRef dbP; + MemHandle handle; + + buf[0] = '\0'; /* init to empty string */ + + if ( index < countGameRecords(globals) ) { + + id = DMFINDDATABASE( globals, CARD_0, XW_GAMES_DBNAME ); + if ( id != 0 ) { + dbP = DMOPENDATABASE( globals, CARD_0, id, dmModeWrite ); + if ( dbP != 0 ) { + handle = DmQueryRecord( dbP, index ); + + XP_MEMCPY( buf, MemHandleLock(handle), MAX_GAMENAME_LENGTH ); + buf[MAX_GAMENAME_LENGTH-1] = '\0'; + + MemHandleUnlock( handle ); + DMCLOSEDATABASE( dbP ); + } + } + } +} /* nameFromRecord */ + +/***************************************************************************** + * Later this will provide a default name based on a timestamp. + *****************************************************************************/ +#ifndef TIME_FORMAT +#define TIME_FORMAT tfColonAMPM +#endif +#ifndef DATE_FORMAT +#define DATE_FORMAT dfMDYLongWithComma +#endif +void +makeDefaultGameName( char* buf ) +{ + char timeBuf[timeStringLength+1]; /* add 1 to be safe */ + char dateBuf[longDateStrLength+1]; + DateTimeType timeType; + + TimSecondsToDateTime( TimGetSeconds(), &timeType ); + TimeToAscii( timeType.hour, timeType.minute, TIME_FORMAT, timeBuf ); + + DateToAscii( timeType.month, timeType.day, timeType.year, + DATE_FORMAT, dateBuf ); + + StrPrintF( buf, "%s, %s", dateBuf, timeBuf ); +} /* makeDefaultGameName */ + diff --git a/xwords4/palm/gameutil.h b/xwords4/palm/gameutil.h new file mode 100644 index 000000000..599df574f --- /dev/null +++ b/xwords4/palm/gameutil.h @@ -0,0 +1,35 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 1999 - 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _GAMEUTIL_H_ +#define _GAMEUTIL_H_ + +#include "comtypes.h" +#include "memstream.h" +#include "palmmain.h" + +XP_U16 countGameRecords( PalmAppGlobals* globals ); +void deleteGameRecord( PalmAppGlobals* globals, XP_S16 index ); +XP_S16 duplicateGameRecord( PalmAppGlobals* globals, XP_S16 fromIndex ); +void nameFromRecord( PalmAppGlobals* globals, XP_S16 index, char* buf ); +void streamToGameRecord( PalmAppGlobals* globals, XWStreamCtxt* stream, + XP_S16 index ); +void makeDefaultGameName( char* buf ); + +#endif diff --git a/xwords4/palm/gen_pace.pl b/xwords4/palm/gen_pace.pl new file mode 100755 index 000000000..0bbd17245 --- /dev/null +++ b/xwords4/palm/gen_pace.pl @@ -0,0 +1,715 @@ +#!/usr/bin/perl +# Copyright 2004-2007 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +use strict; +use File::Basename; + +my %funcInfo; +my %fileNames; +my %contents; +my @ifdefs; +my @minusDs; + +sub usage() { + print STDERR "$0 \\\n" . + "\t[-oh out.h] \\\n" . + "\t[-oc out.c] \\\n" . + "\t[-os out.s] \\\n" . + "\t[-D ... -D] \\\n" . + "\t[-file palmheader.h|palm_header_dir] (can repeat) \\\n" . + "\t[-func func_name_or_list_file] (can repeat)\n"; + + exit 1; +} + +# Given the name of a palm function, or of a file containing a list of +# such, return a list of function names. In the first case it'll be a +# singleton. +sub makeFuncList($) { + my ( $nameOrPath ) = @_; + my @result; + + if ( open LIST, "< $nameOrPath" ) { + while ( ) { + chomp; + + # ifdef/endif pairs + if ( m/^\#\s*ifdef\s+(\w+)\s*$/ ) { + # print STDERR "looking for $1 in ", join( ",", @minusDs), "\n"; + if ( 0 == grep( {$1 eq $_} @minusDs ) ) { + # print STDERR "adding $1 to skippers\n"; + push( @ifdefs, $1 ); + } else { + # print STDERR "NOT adding $1 to skippers\n"; + } + next; + } elsif ( m/^\#\s*endif\s+(\w+)\s*$/ ) { + if ( 0 == grep( {$1 eq $_} @minusDs ) ) { + die "$1 not most recently defined" if $1 ne pop(@ifdefs); + } + next; + } + next if @ifdefs > 0; + + # comments? + s/\#.*$//; + # white space + s/^\s*//; + s/\s*$//; + + if ( m,^(\w+)\s+(-?\d+)\s+(0x\w+)$, ) { + push( @result, [ $1, $2, $3 ] ); + } elsif ( m,^(\w+)$, ) { + push( @result, [ $1 ] ); + } else { + next; + } + } + close LIST; + } else { + # must be a simple function name + push( @result, $nameOrPath ); + } + return @result; +} # makeFuncList + +sub makeFileList($) { + my ( $fileOrDir ) = @_; + my @result; + push @result, $fileOrDir; + return @result; +} # makeFileList + +my @funcList; +my @pathList; +my $dot_c; +my $dot_s; +my $dot_h; + +# A list of types seen in the header files. The idea is to use these +# to determine the size of a type and whether it's returned in d0 or +# a0. The user function should print out types it doesn't find here +# and they should be added manually; + +my %typeInfo = ( + "UInt32" => { "size" => 4, "a0" => 0 }, + "UInt16" => { "size" => 2, "a0" => 0 }, + "void*" => { "size" => 4, "a0" => 1 }, + "void**" => { "size" => 4, "a0" => 1, "autoSwap" => 4 }, + "void" => { "size" => 0, "a0" => 0 }, + "BitmapPtr" => { "size" => 4, "a0" => 1 }, + "Boolean" => { "size" => 1, "a0" => 0 }, + "Boolean*" => { "size" => 4, "a0" => 0, "autoSwap" => 1 }, + "Char const*" => { "size" => 4, "a0" => 1 }, + "Char*" => { "size" => 4, "a0" => 1 }, + "Char**" => { "size" => 4, "a0" => 1 }, + "ControlType*" => { "size" => 4, "a0" => 1 }, + "Coord" => { "size" => 2, "a0" => 0 }, # ??? + "Coord*" => { "size" => 4, "a0" => 0, "autoSwap" => 2 }, + "DateFormatType" => { "size" => 1, "a0" => 0 }, # enum + "DateTimeType*" => { "size" => 4, "a0" => 0, "autoSwap" => -1 }, + "DmOpenRef" => { "size" => 4, "a0" => 1 }, + "DmResID" => { "size" => 2, "a0" => 0 }, + "DmResType" => { "size" => 4, "a0" => 0 }, + "DmSearchStatePtr" => { "size" => 4, "a0" => 0 }, + "Err" => { "size" => 2, "a0" => 0 }, + "Err*" => { "size" => 4, "a0" => 1 }, + "EventPtr" => { "size" => 4, "a0" => 1 }, + "EventType*" => { "size" => 4, "a0" => 1, "autoSwap" => -1 }, + "ExgDBWriteProcPtr" => { "size" => 4, "a0" => 1 }, + "ExgSocketType*" => { "size" => 4, "a0" => 1, "autoSwap" => -1 }, + "FieldAttrPtr" => { "size" => 4, "a0" => 1 }, + "FieldType*" => { "size" => 4, "a0" => 1 }, + "FileInfoType*" => { "size" => 4, "a0" => 1, "autoSwap" => -1 }, + "FileRef" => { "size" => 4, "a0" => 0 }, # UInt32 + "FileRef*" => { "size" => 4, "a0" => 1, "autoSwap" => 4 }, + "FontID" => { "size" => 1, "a0" => 0 }, # enum + "FormEventHandlerType*" => { "size" => 4, "a0" => 1 }, + "FormType*" => { "size" => 4, "a0" => 1 }, + "FrameType" => { "size" => 2, "a0" => 0 }, + "IndexedColorType" => { "size" => 1, "a0" => 0 }, # UInt8 + "Int16" => { "size" => 2, "a0" => 0 }, + "Int32" => { "size" => 4, "a0" => 0 }, + "Int8" => { "size" => 1, "a0" => 0 }, + "ListDrawDataFuncPtr" => { "size" => 4, "a0" => 1 }, + "ListType*" => { "size" => 4, "a0" => 1 }, + "LocalID" => { "size" => 4, "a0" => 0 }, + "LocalID*" => { "size" => 4, "a0" => 1, "autoSwap" => 4 }, + "MemHandle" => { "size" => 4, "a0" => 1 }, + "MemHandle*" => { "size" => 4, "a0" => 1 }, + "MemPtr" => { "size" => 4, "a0" => 1 }, + "MenuBarType*" => { "size" => 4, "a0" => 1 }, + "RectangleType*" => { "size" => 4, "a0" => 1, + "autoSwap" => -1 }, + "ScrollBarType*" => { "size" => 4, "a0" => 1 }, + "SndSysBeepType" => { "size" => 1, "a0" => 0 }, + "SysNotifyProcPtr" => { "size" => 4, "a0" => 1 }, + "TimeFormatType" => { "size" => 1, "a0" => 0 }, # enum + "UInt16*" => { "size" => 4, "a0" => 1, "autoSwap" => 2 }, + "UInt32*" => { "size" => 4, "a0" => 1, "autoSwap" => 4 }, + "UInt8" => { "size" => 1, "a0" => 0 }, + "UInt8*" => { "size" => 4, "a0" => 1 }, + "WinDirectionType" => { "size" => 1, "a0" => 0 }, # enum + "WinDrawOperation" => { "size" => 1, "a0" => 0 }, # enum + "WinHandle" => { "size" => 4, "a0" => 1 }, + "WinScreenModeOperation" => { "size" => 1, "a0" => 0 }, # enum + "WindowFormatType" => { "size" => 1, "a0" => 0 }, # enum + "char*" => { "size" => 4, "a0" => 1 }, + "const Char*" => { "size" => 4, "a0" => 1 }, + "const ControlType*" => { "size" => 4, "a0" => 1 }, + # CustomPatternType is UInt8[8]; no need to translate + "const CustomPatternType*" => { "size" => 4, "a0" => 1 }, + "const EventType*" => { "size" => 4, "a0" => 1, + "autoSwap" => -1 }, + "const FieldAttrType*" => { "size" => 4, "a0" => 1 }, + "const FieldType*" => { "size" => 4, "a0" => 1 }, + "const FormType*" => { "size" => 4, "a0" => 1 }, + "const ListType*" => { "size" => 4, "a0" => 1 }, + "const RGBColorType*" => { "size" => 4, "a0" => 1 }, + "const RectangleType*" => { "size" => 4, "a0" => 1, + "autoSwap" => -1 }, + "const char*" => { "size" => 4, "a0" => 1 }, + "const void*" => { "size" => 4, "a0" => 1 }, + "FormObjectKind" => { "size" => 1, "a0" => 0 }, # enum + # it's a char*, likely never returned + "_Palm_va_list" => { "size" => 4, "a0" => 1 }, + "WinScreenAttrType" => { "size" => 1, "a0" => 0 }, # enum + "NetSocketRef" => { "size" => 2, "a0" => 0 }, + "NetSocketAddrType*" => { "size" => 4, "a0" => 1 }, + "NetFDSetType*" => { "size" => 4, "a0" => 1, "autoSwap" => 4 }, + "NetSocketAddrEnum" => { "size" => 1, "a0" => 0 }, + "NetSocketTypeEnum" => { "size" => 1, "a0" => 0 }, + "BitmapType*" => { "size" => 4, "a0" => 1 }, + "ColorTableType*" => { "size" => 4, "a0" => 1 }, + "UIColorTableEntries" => { "size" => 1, "a0" => 0 }, # enum + + "BtLibClassOfDeviceType*" => { "size" => 4, "a0" => 1, + "autoSwap" => 4}, # uint32 + "BtLibDeviceAddressType*" => { "size" => 4, "a0" => 1 }, + "BtLibDeviceAddressTypePtr" => { "size" => 4, "a0" => 1 }, + "BtLibSocketRef" => { "size" => 2, "a0" => 0 }, + "BtLibSocketRef*" => { "size" => 4, "a0" => 1, + "autoSwap" => 2 }, + "BtLibProtocolEnum" => { "size" => 1, "a0" => 0 }, # enum + "BtLibSocketConnectInfoType*" => { "size" => 4, "a0" => 1, + "autoSwap" => -1 }, + "BtLibSocketListenInfoType*" => { "size" => 4, "a0" => 1, + "autoSwap" => -1 }, + "BtLibSdpRecordHandle" => { "size" => 4, "a0" => 1 }, + "BtLibSdpRecordHandle*"=> { "size" => 4, "a0" => 1, + "autoSwap" => 4 }, + "BtLibSdpUuidType*" => { "size" => 4, "a0" => 1, + "autoSwap" => -1 }, + "BtLibFriendlyNameType*" => { "size" => 4, "a0" => 1, + "autoSwap" => -1 }, + "BtLibGetNameEnum" => { "size" => 1, "a0" => 0 }, # enum + + ); + +sub name_compact($) { + my ( $name ) = @_; + + $name =~ s/^\s*//; + $name =~ s/\s*$//; + return $name; +} + +sub type_compact($) { + my ( $type ) = @_; + #print STDERR "1. $type\n"; + $type = name_compact( $type ); + $type =~ s/\s*(\*+)$/$1/; # remove spaces before any trailing * + #print STDERR "2. $type\n"; + return $type; +} + +# Split a string like "( type foo, type* bar, type *bazz, const type +# buzz )" into a list of hashes each with "type" and "name" +sub params_parse($) { + my ( $params ) = @_; + my @plist; + + # strip leading and training ws and params + $params =~ s/^\s*\(//; + $params =~ s/\)\s*$//; + $params =~ s/^void$//; # if just (void), nuke it + + #split based on commas + my @params = split ",", $params; + + @params = map { + s/^\s*//; # leading ws + s/\s*$//; # trailing ws + m|^([\w\s]+[\s\*]+)(\w+)$|; # split on space-or-star + { "type" => type_compact($1), "name" => name_compact($2) }; + } @params; + + return \@params; +} + +sub clean_type($) { + my ( $type ) = @_; + # trip white space off the type + $type =~ s/\s*\*/\*/; + $type =~ s/^\s*//; + $type =~ s/\s*$//; + $type =~ s/^\s*(\S*)\s*$/$1/; + + my @out; + my @terms = split( '\s+', $type ); + foreach my $term (@terms) { + if ( defined( $typeInfo{$term} ) ) { + } elsif ( "const" eq $term ) { + } elsif ( "extern" eq $term ) { + } else { + next; + } + push( @out, $term ); + } + $type = join( " ", @out ); + # print STDERR "clean_type => $type\n"; + + return $type; +} + + +sub searchOneFile($$) { + my ( $file, $function ) = @_; +# print STDERR "searching $file for $function\n"; + + my $base = basename($file); + my $contents = $contents{$base}; + + if ( ! $contents ) { + + # read the file into a single long line, stripping + # (imperfectly) comments. + open FILE, "< $file"; + # print STDERR "looking at file $file\n"; + while( ) { + chomp; + # remove // to eol comments + s|//.*$||; + $contents .= "$_ "; + } + + close FILE; + # strip /*....*/ comments + $contents =~ s|/\*[^*]*\*+([^/*][^*]*\*+)*/||g; + $contents{$base} = $contents; + # print STDERR "$contents\n"; + } + + # a word-token, followed by at least ' ' or *, and another token. + + # This one strips 'const' in 'const type func(params)' + # $contents =~ m/(\w+)([\s\*]+)$function\s*(\([^)]*\))/; + + if ( $contents =~ m/([\w\s]+)([\s\*]+)$function\s*(\([^)]*\))[^V]*(VFSMGR_TRAP)\(([\w]+)\);/ + || $contents =~ m/([\w\s]+)([\s\*]+)$function\s*(\([^)]*\))[^S]*(SYS_TRAP)\s*\(([\w]+)\);/ + || $contents =~ m/([\w\s]+)([\s\*]+)$function\s*(\([^)]*\))[^B]*(BTLIB_TRAP)\s*\(([\w]+)\);/ + || $contents =~ m/([\w\s]+)([\s\*]+)$function\s*(\([^)]*\))[^S]*(SYS_SEL_TRAP)\s*\(([\w]+)\s*,\s*([\w]+)\);/ + || $contents =~ m/([\w\s]+)([\s\*]+)$function\s*(\([^)]*\))[^H]*(HIGH_DENSITY_TRAP)\(([\w]+)\);/ + || $contents =~ m/([\w\s]+)([\s\*]+)$function\s*(\([^)]*\))[^G]*(GRF_TRAP)\(([\w]+)\);/ ) { + + # print STDERR "found something\n"; + + my ( $type, $params, $trapType, $trapSel, $trapSubSel ) = ( "$1$2", $3, $4, $5, $6 ); + if ( $type && $params ) { + + $type = clean_type($type); + +# my $found = "type: $type\nfunction: $function\nparams: $params\n" +# . " trapSel: $trapSel\ntrapType: $trapType\ntrapSubSel: $trapSubSel\n"; +# print STDERR "$found"; + + $params = params_parse($params); + $funcInfo{$function} = { 'type' => $type, + 'params' => $params, + 'file' => $base, + 'sel' => $trapSel, + 'trapType' => $trapType, + }; + $fileNames{$base} = 1; + return 1; + } + } + return 0; +} # searchOneFile + +sub searchOneDir($$) { + my ( $dir, $function ) = @_; + my $found = 0; + + opendir(DIR, $dir) || die "can't opendir $dir: $!"; + my @files = readdir(DIR); + closedir DIR; + + foreach my $file (@files) { + if ( $file =~ m|^\.| ) { + # skip if starts with . + } elsif ( -d "$dir/$file" ) { + $found = searchOneDir( "$dir/$file", $function ); + last if $found; + } elsif ( $file =~ m|\.h$| ) { + $found = searchOneFile( "$dir/$file", $function ); + last if $found; + } + } + return $found; +} # searchOneDir + +sub print_params_list($$) { + my ( $params, $startLen ) = @_; + my $result = "("; + + foreach my $param ( @$params ) { + my $p = " " . $$param{"type"} . " " . $$param{'name'} . ","; + if ( length($p) + $startLen >= 80 ) { + $p = "\n $p"; + $startLen = length($p); + } + $result .= $p; + $startLen += length($p); + } + + $result =~ s/,$//; + $result .= " )"; + + return $result; +} + +sub nameToBytes($) { + my ( $nam ) = @_; + # Any way to insert a '\n' in this? + my $str = "\"'" . join( "','", split( //, $nam ) ) . "'\""; + return $str; +} + +sub makeSwapStuff($$$$$) { + my ( $params, $varDecls, $swapIns, $pushes, $swapOuts ) = @_; + my $sizeSum = 0; + my $vcount = 0; + + $$varDecls .= " /* var decls */\n"; + $$pushes .= " /* pushes */\n"; + $$swapOuts .= " /* swapOuts */\n"; + $$swapIns .= " /* swapIns */\n"; + + foreach my $param ( @$params ) { + # each param has "type" and "name" fields + my $type = $param->{"type"}; + my $info = $typeInfo{$type}; + my $size = $info->{'size'}; + my $name = $param->{'name'}; + my $pushName = $name; + + # If type requires swapping, just swap in and out. If it's + # RECT (or later, other things) declare a tmp var and swap in + # and out. If it's const, only swap in. + + my $swapInfo = $info->{"autoSwap"}; + if ( defined $swapInfo ) { + + if ( $swapInfo == -1 ) { + my $typeNoStar = $type; + $typeNoStar =~ s/\*$//; + # Does anything bad happen if there's no *? + # die "no star found" if $typeNoStar eq $type; + my $isConst = $typeNoStar =~ m|^const|; + $typeNoStar =~ s/^const\s+//; + my $vName = "${typeNoStar}_68K${vcount}"; + $pushName = "&$vName"; + $$varDecls .= " $typeNoStar $vName;\n"; + $typeNoStar = uc($typeNoStar); + $$swapIns .= " SWAP_${typeNoStar}_ARM_TO_68K( &$vName, $name );\n"; + if ( ! $isConst ) { + $$swapOuts .= " SWAP_${typeNoStar}_68K_TO_ARM( $name, &$vName );\n"; + } + + } else { + if ( $swapInfo >= 1 && $swapInfo <= 4 ) { + $$swapIns .= " SWAP${swapInfo}_NON_NULL_IN($name);\n"; + $$swapOuts .= " SWAP${swapInfo}_NON_NULL_OUT($name);\n"; + } else { + die "unknown swapInfo $swapInfo\n"; + } + } + } + + $$pushes .= " ADD_TO_STACK$size(stack, $pushName, $sizeSum);\n"; + + $sizeSum += $size; + if ( $size == 1 ) { + ++$sizeSum; + } + ++$vcount; + } + return $sizeSum; +} # makeSwapStuff + + +sub print_body($$$$$) { + my ( $name, $returnType, $params, $trapSel, $trapType ) = @_; + my $result; + my $offset = 0; + my $notVoid = $returnType !~ m|void$|; + + $result .= "{\n"; + if ( $notVoid ) { + $result .= " $returnType result;\n"; + } + $result .= " FUNC_HEADER($name);\n"; + + my ( $varDecls, $swapIns, $pushes, $swapOuts ); + $offset = makeSwapStuff( $params, \$varDecls, + \$swapIns, \$pushes, \$swapOuts ); + + $result .= $varDecls . $swapIns; + + $result .= " {\n"; + $result .= " PNOState* sp = GET_CALLBACK_STATE();\n"; + + $result .= " STACK_START(unsigned char, stack, $offset);\n" + . $pushes + . " STACK_END(stack);\n"; + + my $info = $typeInfo{$returnType}; + if ( $info->{'a0'} ) { + $offset = "kPceNativeWantA0 | $offset"; + } + + if ( $trapType eq "VFSMGR_TRAP" ) { + $result .= " SET_SEL_REG($trapSel, sp);\n"; + $trapSel = "sysTrapFileSystemDispatch"; + } elsif ( $trapType eq "HIGH_DENSITY_TRAP" ) { + $result .= " SET_SEL_REG($trapSel, sp);\n"; + $trapSel = "sysTrapHighDensityDispatch"; + } elsif( $trapType eq "GRF_TRAP" || $trapType eq "SYS_TRAP" + || $trapType eq "BTLIB_TRAP" ) { + # they're the same according to Graffiti.h + } elsif ( $trapType eq "SYS_SEL_TRAP" ) { + print( STDERR "name = $name\n" ); + print( STDERR "returnType = $returnType\n" ); + print( STDERR "params = $params\n" ); + print( STDERR "trapSel = $trapSel\n" ); + print( STDERR "trapType = $trapType\n" ); + die "can't emit for SYS_SEL_TRAP yet"; + } else { + die "unknown dispatch type: \"$trapType\""; + } + + $result .= " "; + if ( $notVoid ) { + $result .= "result = ($returnType)"; + } + + $result .= "(*sp->call68KFuncP)( sp->emulStateP, \n" + . " PceNativeTrapNo($trapSel),\n" + . " stack, $offset );\n"; + + $result .= $swapOuts; + + $result .= " }\n FUNC_TAIL($name);\n"; + $result .= " EMIT_NAME(\"$name\"," . nameToBytes($name) . ");\n"; + if ( $notVoid ) { + $result .= " return result;\n"; + } + $result .= "} /* $name */\n"; + + return $result; +} + +sub print_func_impl($$$$$$) { + my ( $returnType, $name, + $params, # ref-to-list created by params_parse above + $file, $trapSel, $trapType ) = @_; + my $result; + + $result .= "\n/* from file $file */\n"; + $result .= "$returnType\n"; + my $lenToName = length( $result ); + $result .= "$name"; + $result .= print_params_list($params, length($result)-$lenToName) . "\n"; + $result .= print_body( $name, $returnType, $params, $trapSel, $trapType ); + + return $result; +} # print_func_impl + +# create a signature for each function. We'll see how many match up. +sub funcId($$) { + my ( $retType, $parms ) = @_; + my $id = "${retType}_"; + + foreach my $param ( @$parms ) { + $id .= $$param{"type"}; + } + + return $id; +} # funcId + +sub genStub($$$) { + my( $func, $r9off, $off ) = @_; + my $result; + + if ( $func && $r9off && $off ) { + $result .= "\t\t.type $func, %function\n"; + $result .= "\t\t.globl $func\n"; + $result .= "$func:\n"; + $result .= "\tldr ip, [r9, #$r9off]\n"; + my $intoff = 4 * hex($off); + $result .= "\tldr pc, [ip, #$intoff]\n"; + } + return $result; +} + +########################################################################### +# Main +########################################################################### + +my @paths; +my @funcs; +while ( my $arg = shift(@ARGV) ) { + if ( $arg eq "-oh" ) { + $dot_h = shift(@ARGV); + } elsif ( $arg eq "-oc" ) { + $dot_c = shift(@ARGV); + } elsif ( $arg eq "-os" ) { + $dot_s = shift(@ARGV); + } elsif ( $arg eq "-file" ) { + push( @paths, shift(@ARGV)); + } elsif ( $arg eq "-func" ) { + push( @funcs, shift(@ARGV)); + } elsif ( $arg =~ m|^-D(\w+)$| ) { + push( @minusDs, $1 ); + } else { + usage(); + } +} + +map { push( @pathList, makeFileList($_) ); } @paths; +map { push( @funcList, makeFuncList($_) ); } @funcs; + +my $dot_s_out = "\t.text\n"; +foreach my $fref (@funcList) { + if ( $dot_s ) { + $dot_s_out .= genStub( $$fref[0], $$fref[1], $$fref[2] ); + } + if ( $dot_c ) { + my $func = $$fref[0]; + my $found = 0; + my $path; + foreach my $path (@pathList) { + if ( -d $path ) { + $found = searchOneDir( $path, $func ); + last if $found; + } elsif ( -e $path ) { + $found = searchOneFile( $path, $func ); + last if $found; + } + } + die "unable to find declaration of $func\n" if ! $found; + } +# close PATHS; +} + +my $outRef; + +if ( $dot_s ) { + if ( $dot_s eq "-" ) { + $outRef = *STDOUT{IO}; + } else { + open DOT, "> $dot_s"; + $outRef = *DOT{IO}; + } + print $outRef $dot_s_out; + if ( $dot_c ne "-" ) { + close DOT; + } +} + +if ( $dot_c ) { + if ( $dot_c eq "-" ) { + $outRef = *STDOUT{IO}; + } else { + open DOT, "> $dot_c"; + $outRef = *DOT{IO}; + } + print $outRef "/********** this file is autogenerated by $0 " + . "***************/\n\n"; + + print $outRef "\n"; + + print $outRef < $dot_h"; + print DOT "/********** this file is autogenerated by $0 " + . "***************/\n\n"; + + my $def = "_" . uc($dot_h) . "_"; + $def =~ s/\./_/; + + print DOT "#ifndef $def\n"; + print DOT "#define $def\n"; + + map { print DOT "#include <$_>\n"; } keys(%fileNames); + + foreach my $key (keys %funcInfo) { + my $ref = $funcInfo{$key}; + print DOT $${ref}{"type"}, " " + . $key . print_params_list($$ref{'params'}, 0) . ";\n"; + } + + print DOT "\n#include \"pace_man.h\"\n"; + print DOT "#endif /* $def */\n"; + close DOT; +} + + +exit 0; diff --git a/xwords4/palm/l10n/StrRes_en_US.pre b/xwords4/palm/l10n/StrRes_en_US.pre new file mode 100644 index 000000000..31b996bb2 --- /dev/null +++ b/xwords4/palm/l10n/StrRes_en_US.pre @@ -0,0 +1,182 @@ +/* -*- mode: c; compile-command: "cd .. && make ARCH=68K_ONLY MEMDEBUG=TRUE"; -*- */ +/* + * Copyright 1997 - 2008 by Eric House (xwords@eehouse.org) and others. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* This table is where English user-visible strings come from (all + * strings meant to wind up in a str# resource, actually. Each entry + * is a pair of strings, first the constant name and then the string + * itself. The build system creats a str# resource with the latter + * and generates a .h file of #defines using the former. */ + +{ "STR_COMMIT_CONFIRM", "Commit the current move?\n" }, +{ "STR_NOT_YOUR_TURN", "You can't do that; it's not your turn!" }, +{ "STR_NO_PEEK_ROBOT_TILES", "No peeking at the robot's tiles!" }, +#ifndef XWFEATURE_STANDALONE_ONLY +{ "STR_NO_PEEK_REMOTE_TILES", "No peeking at remote player's tiles!" }, +{ "STR_SERVER_DICT_WINS", + "Conflict between Host and Guest dictionaries; Host wins." }, +{ "STR_REG_UNEXPECTED_USER", "Attempt to register unexpected user refused" }, +{ "STR_REG_NEED_REMOTE", "Please make one or more players Remote when " + "playing as Host." }, +{ "STR_REG_BT_NEED_HOST", "Please locate the device where Crosswords is " + "running as Host." }, +{ "STR_RESEND_STANDALONE", "This is a standalone game. There is nothing " + "to resend." }, +#endif +{ "STR_TOO_FEW_TILES", "Too few tiles left to trade." }, +{ "STR_CANT_UNDO_TILEASSIGN", "Nothing to undo. (Initial tile picking " + "cannot be undone.)" }, +{ "STR_CANT_HINT_WHILE_DISABLED", "The hint feature is disabled for this " + "game. Enable it for a new game using the " + "Preferences dialog." }, + +{ "STR_VALUES_TITLE", "Counts and Values" }, +{ "STR_REMAINS_TITLE","Remaining tiles" }, +{ "STRS_VALUES_HEADER", "%s counts/values:\n" }, + +{ "STR_DOUBLE_LETTER", "Double letter" }, +{ "STR_DOUBLE_WORD", "Double word" }, +{ "STR_TRIPLE_LETTER", "Triple letter" }, +{ "STR_TRIPLE_WORD", "Triple word" }, +{ "STR_REMTILES", "rem:%d" }, +{ "STR_CANT_TRADE_MIDTURN", "Remove played tiles before trading."}, +{ "STR_ASK_REPLACE_GAME", "Do you want to overwrite the current game?" }, +{ "STR_OK", "Ok" }, +{ "STR_ABOUT_TITLE", "About Crosswords" }, +{ "STR_DEFAULT_NAME", "Player %d" }, +{ "STR_CONFIRM_END_GAME", "Are you sure you want to end the game now?"}, +{ "STR_CONFIRM_TRADE", "Are you sure you want to use your " \ + "turn trading tiles?" }, +{ "STR_TRADING_REMINDER", "Click D when done." }, + +{ "STR_LOCAL_NAME", "%s" }, +{ "STR_NONLOCAL_NAME", "%s (remote)" }, +{ "STRSD_SUMMARYSCORED", "%s:%d" }, +{ "STRD_TRADED", "Traded %d" }, +{ "STR_PASSED", "Passed" }, +{ "STR_LOSTTURN", "Lost turn" }, + +{ "STR_HISTORY_TITLE", "Game history" }, +{ "STRD_REMAINING_TILES_ADD", "+ %d [all remaining tiles]" }, +{ "STRD_UNUSED_TILES_SUB", "- %d [unused tiles]" }, +{ "STR_BONUS_ALL", "Bonus for using all tiles: 50\n" }, +{ "STRD_TURN_SCORE", "Score for turn: %d\n" }, +{ "STR_ALL_IN_LINE_ERR", "All tiles played must be in a line." }, +{ "STR_NO_EMPTIES_ERR", "Empty squares cannot separate pieces played." }, +{ "STR_FIRST_MOVE_ERR", "Must play two or more pieces on " \ + "the first move." }, + /* phony comment to get diff back in sync */ +{ "STR_MUST_CONTACT_ERR", "New tiles must contact others already " \ + "in place (or the middle square on the " \ + "first move)." }, +{ "STR_PTS", "Pts:" }, +{ "STR_CONFIRM_DEL_GAME", "Really delete the selected game?" }, + +{ "STRD_TIME_PENALTY_SUB", " - %d [time]" }, + +{ "STR_NO_DICT_INSTALLED", "Crosswords 4 requires at least one dictionary." \ + "Download one or more from xwords.sf.net." }, +{ "STR_ILLEGAL_WORD", "Word[s] %s not found in dictionary. Allow anyway?" }, + +{ "STR_FINAL_SCORES_TITLE", "Final scores"}, + + /* another phony comment to get diff back in sync */ + +{ "STRD_CUMULATIVE_SCORE", "Cumulative score: %d\n" }, +{ "STRS_TRAY_AT_START", "Tray at start: %s\n" }, +{ "STRS_MOVE_ACROSS", "move (from %s across)\n" }, +{ "STRS_MOVE_DOWN", "move (from %s down)\n" }, +{ "STRS_NEW_TILES", "New tiles: %s\n" }, +{ "STRSS_TRADED_FOR", "Traded %s for %s." }, +{ "STR_PASS", "pass\n" }, +{ "STR_PHONY_REJECTED", "Turn lost: illegal word in move." }, + +{ "STR_ROBOT_MOVED", "The robot made this move:\n" }, +{ "STR_REMOTE_MOVED", "Remote player made this move:\n" }, +{ "STRD_ROBOT_TRADED", "%d tiles traded this turn." }, +{ "STR_ROBOT_TITLE", "Remote/robot score" }, + + /* The end of 4.0.5's strings */ + +{ "STR_PICK_BLANK", "Select the letter for your blank." }, +#ifdef FEATURE_TRAY_EDIT +{ "STRS_PICK_TILE", "Choose a new tile for %s." }, +{ "STR_PICK_TILE_CUR", "Cur" }, + +#endif + +#ifdef NODE_CAN_4 +{ "STR_CONFIRM_CONVERTDICT", "Do you want to convert any existing Crosswords " \ + "dictionaries to the new format? " \ + "The change is not reversible." }, +{ "STRS_CONFIRM_ONEDICT", "Convert dictionary %s?" }, +#endif +{ "STRS_CANNOT_FIND_DICT", "%s dictionary not found." }, +{ "STR_DICT_COPY_EXPL", "Copying dictionary from card..." }, + +{ "STR_LOCALPLAYERS", "Local players" }, +{ "STR_TOTALPLAYERS", "Total players" }, + +#ifdef XWFEATURE_RELAY +{ "STR_RELAY_XPORTNAME", "Internet" }, +{ "STR_RELAY_TIMEOUT", "Relay error: Other devices failed to " + "connect." }, +{ "STR_RELAY_LOST_OTHER", "Relay error: another device has lost its " + "connection." }, +{ "STR_RELAY_GENERIC", "Relay error: something's wrong." }, +#endif + +#ifdef XWFEATURE_IR +{ "STR_IR_XPORTNAME", "Beaming" }, +#endif + +#ifdef XWFEATURE_BLUETOOTH +{ "STR_BT_XPORTNAME", "Bluetooth" }, +{ "STR_BT_NOINIT", "Bluetooth appears to be off. Please turn it " + "on if you want Crosswords to use it." }, +{ "STRS_BT_CONFIRM", "Is Crosswords running on %s and " + "ready to accept a connection? (If you " + "choose \"No\" Crosswords will not try to " + "connect until you restart it " + "or choose the \"Resend\" menu item.)" }, + +#endif + +{ "STR_ABOUT_CONTENT", +/* #ifdef XWFEATURE_BLUETOOTH */ +/* "THANKS FOR DOWNLOADING this beta version of " */ +/* "Crosswords. Please see the notes and caveats at " */ +/* "xwords.sf.net/bt_palm.php. Please report bugs to " */ +/* "xwords@eehouse.org. Enjoy!\n\n" */ +/* #endif */ + "Crosswords " XW_PALM_VERSION_STRING " (rev. " SVN_REV ").\n" \ + "Copyright 1998-2008 by Eric House. "\ + "Released under the GNU Public License.\n\n"\ + + "See the manual at xwords.sf.net or " + "eehouse.org/xwords/.\n\n" \ + + "This program is postcardware. If you like it " \ + "please mail a postcard to:\n" \ + "The Houses\n" \ + "1610 NW 14th Place\n" \ + "Corvallis, OR 97330 USA\n\n" + + "Thanks to Debian GNU/Linux for the development "\ + "tools and to KT for the time." \ + }, diff --git a/xwords4/palm/l10n/StrRes_es_CT.pre b/xwords4/palm/l10n/StrRes_es_CT.pre new file mode 100644 index 000000000..b9978a113 --- /dev/null +++ b/xwords4/palm/l10n/StrRes_es_CT.pre @@ -0,0 +1,140 @@ +/* -*- mode: c; -*- */ +/* + * Copyright 1997 - 2002 by Eric House (xwords@eehouse.org) and others. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* This table is where English user-visible strings come from (all + * strings meant to wind up in a str# resource, actually. Each entry + * is a pair of strings, first the constant name and then the string + * itself. The build system creats a str# resource with the latter + * and generates a .h file of #defines using the former. */ + +{ "STR_COMMIT_CONFIRM", "Acceptes la jugada?\n\n" }, +{ "STR_NOT_YOUR_TURN", "No ho pots fer; no és el teu torn!" }, +{ "STR_NO_PEEK_ROBOT_TILES", "No miris les fitxes de la màquina!" }, +#ifndef XWFEATURE_STANDALONE_ONLY +{ "STR_NO_PEEK_REMOTE_TILES", "No miris les fitxes dels altres!" }, +{ "STR_SERVER_DICT_WINS", + "Conflicte de diccionaris: mana el del servidor." }, +{ "STR_REG_UNEXPECTED_USER", "Jugador inesperat: no s'accepta" }, +{ "STR_RESEND_IR", "Error enviant el missatge; ¿reenviar?" }, +#endif +{ "STR_TOO_FEW_TILES", "No hi ha prou fitxes." }, +{ "STR_CANT_UNDO_TILEASSIGN", "El repartiment de fitxes no pot desfer-se." }, + +{ "STR_VALUES_TITLE", "Counts and Values" }, /* translate */ +{ "STR_REMAINS_TITLE","Remaining tiles" }, /* translate */ +{ "STRS_VALUES_HEADER", "%s counts/values:\n" }, /* translate */ + +{ "STR_DOUBLE_LETTER", "Doble de lletra" }, +{ "STR_DOUBLE_WORD", "Doble de paraula" }, +{ "STR_TRIPLE_LETTER", "Triple de lletra" }, +{ "STR_TRIPLE_WORD", "Triple de paraula" }, +{ "STR_REMTILES", "Hi ha:%d" }, +{ "STR_CANT_TRADE_MIDTURN", "Encara tens fitxes al tauler."}, +{ "STR_ASK_REPLACE_GAME", "Vols sobreescriure la partida?" }, +{ "STR_OK", "Ok" }, /* translate */ +{ "STR_ABOUT_TITLE", "Sobre Crosswords" }, +{ "STR_DEFAULT_NAME", "Jugador %d" }, +{ "STR_ABOUT_CONTENT", + "Crosswords " XW_PALM_VERSION_STRING " (rev. " SVN_REV ").\n" \ + "Copyright 1998-2004 by Eric House. "\ + "Tots els drets reservats.\n\n"\ + + "El manual és a xwords.sourceforge.net\n\n" \ + + "Aquest programa és postalware.\nSi el fas servir, " \ + "envia una postal a:\n" \ + "The Houses\n" \ + "1610 NW 14th Place\n" \ + "Corvallis, OR 97330 USA\n\n" + + "Escrit sobre Debian GNU/Linux usant les prc-tools.\n\n" \ + + "Versió catalana de F. Xavier Gil\n(setimig@wanadooadsl.net)." \ + }, + +{ "STR_CONFIRM_END_GAME", "Segur que vols acabar la partida?"}, +{ "STR_CONFIRM_TRADE", "Segur que vols usar el teu torn " \ + "per canviar fitxes?" }, +{ "STR_TRADING_REMINDER", "Prem D per acabar." }, + +#ifndef NO_REG_REQUIRED +{ "STR_NOT_UNREG_VERS", "Només funciona a la versió registrada." }, +#endif + +{ "STR_LOCAL_NAME", "%s" }, +{ "STR_NONLOCAL_NAME", "%s (remot)" }, +{ "STRSD_SUMMARYSCORED", "%s: %d" }, +{ "STRD_TRADED", "Canviades: %d" }, +{ "STR_PASSED", "Ha passat" }, +{ "STR_LOSTTURN", "Torn perdut" }, + + +{ "STR_HISTORY_TITLE", "Jugades" }, +{ "STRD_REMAINING_TILES_ADD", "+ %d [fitxes sobreres]" }, +{ "STRD_UNUSED_TILES_SUB", "- %d [fitxes no usades]" }, +{ "STR_BONUS_ALL", "Per usar les set fitxes: 50\n" }, +{ "STRD_TURN_SCORE", "Parcial de la jugada: %d\n" }, +{ "STR_ALL_IN_LINE_ERR", "Les fitxes no estan en línia." }, +{ "STR_NO_EMPTIES_ERR", "Les fitxes s'han de tocar." }, +{ "STR_FIRST_MOVE_ERR", "Has de posar dues o mes fitxes a " \ + "la primera jugada." }, + /* phony comment to get diff back in sync */ +{ "STR_MUST_CONTACT_ERR", "Les fitxes n'han de tocar d'altres (o " \ + "el quadre central a la primera jugada)." }, +{ "STR_PTS", "Punts:" }, +{ "STR_CONFIRM_DEL_GAME", "Segur que la vols esborrar?" }, + +{ "STRD_TIME_PENALTY_SUB", " - %d [per temps]" }, + +{ "STR_NO_DICT_INSTALLED", "Crosswords necessita algun diccionari." }, +{ "STR_ILLEGAL_WORD", "Ep!, %s no està al diccionari." }, + +{ "STR_FINAL_SCORES_TITLE", "Puntuació final"}, + + /* another phony comment to get diff back in sync */ + +{ "STRD_CUMULATIVE_SCORE", "Acumulat: %d\n" }, +{ "STRS_TRAY_AT_START", "Faristol inicial: %s\n" }, +{ "STRS_MOVE_ACROSS", "jugada (%s, horitzontal)\n" }, +{ "STRS_MOVE_DOWN", "jugada (%s, vertical)\n" }, +{ "STRS_NEW_TILES", "Noves fitxes: %s\n" }, +{ "STRSS_TRADED_FOR", "Canvi de %s per %s." }, +{ "STR_PASS", "passa\n" }, +{ "STR_PHONY_REJECTED", "Mot incorrecte; torn perdut!\n" }, + +{ "STR_ROBOT_MOVED", "El robot ha jugat:\n" }, +{ "STR_REMOTE_MOVED", "Un jugador remot ha jugat:\n" }, +{ "STRD_ROBOT_TRADED", "s'han canviat %d fitxes." }, +{ "STR_ROBOT_TITLE", "Punts del robot" }, + + /* The end of 4.0.5's strings */ + +{ "STR_PICK_BLANK", "Tria la fitxa per l'escarràs." }, +#ifdef FEATURE_TRAY_EDIT +{ "STRS_PICK_TILE", "Tria una nova fitxa per %s." }, +#endif + +#ifdef NODE_CAN_4 +{ "STR_CONFIRM_CONVERTDICT", "Vols convertir els diccionaris de Crosswords " \ + "al nou format? " \ + "El canvi no és reversible." }, +{ "STRS_CONFIRM_ONEDICT", "Convertir el diccionari %s?" }, +#endif +{ "STRS_CANNOT_FIND_DICT", "%s dictionary not found." }, /* translate */ +{ "STR_DICT_COPY_EXPL", "Copying dictionary from card..." },/* translate */ diff --git a/xwords4/palm/l10n/StrRes_es_ES.pre b/xwords4/palm/l10n/StrRes_es_ES.pre new file mode 100644 index 000000000..7bd49ed65 --- /dev/null +++ b/xwords4/palm/l10n/StrRes_es_ES.pre @@ -0,0 +1,138 @@ +/* -*- mode: c; -*- */ +/* + * Copyright 1997 - 2002 by Eric House (xwords@eehouse.org) and others. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* This table is where English user-visible strings come from (all + * strings meant to wind up in a str# resource, actually. Each entry + * is a pair of strings, first the constant name and then the string + * itself. The build system creats a str# resource with the latter + * and generates a .h file of #defines using the former. */ + +{ "STR_COMMIT_CONFIRM", "¿Aceptas la jugada?\n\n" }, +{ "STR_NOT_YOUR_TURN", "¡No puedes hacer eso; no es tu turno!" }, +{ "STR_NO_PEEK_ROBOT_TILES", "¡No mires las fichas de la máquina!" }, +#ifndef XWFEATURE_STANDALONE_ONLY +{ "STR_NO_PEEK_REMOTE_TILES", "¡No mires las fichas de los demás!" }, +{ "STR_SERVER_DICT_WINS", "Conflicto entre diccionarios: manda el del servidor." }, +{ "STR_REG_UNEXPECTED_USER", "Jugador inesperado: no se acepta" }, +{ "STR_RESEND_IR", "Error enviando el mensaje; ¿reenviar" }, +#endif +{ "STR_TOO_FEW_TILES", "Ho quedan tantas fichas." }, +{ "STR_CANT_UNDO_TILEASSIGN", "El reparto de fichas no puede deshacerse." }, + +{ "STR_VALUES_TITLE", "Counts and Values" }, /* translate */ +{ "STR_REMAINS_TITLE","Remaining tiles" }, /* translate */ +{ "STRS_VALUES_HEADER", "%s counts/values:\n" }, /* translate */ + +{ "STR_DOUBLE_LETTER", "Doble de letra" }, +{ "STR_DOUBLE_WORD", "Doble de palabra" }, +{ "STR_TRIPLE_LETTER", "Triple de letra" }, +{ "STR_TRIPLE_WORD", "Triple de palabra" }, +{ "STR_REMTILES", "Hay:%d" }, +{ "STR_CANT_TRADE_MIDTURN", "Aún tienes fichas en el tablero."}, +{ "STR_ASK_REPLACE_GAME", "¿Quieres sobreescribir la partida?" }, +{ "STR_OK", "Ok" }, /* translate */ +{ "STR_ABOUT_TITLE", "Acerca de Crosswords" }, +{ "STR_DEFAULT_NAME", "Jugador %d" }, +{ "STR_ABOUT_CONTENT", + "Crosswords " XW_PALM_VERSION_STRING " (rev. " SVN_REV ").\n" \ + "Copyright 1998-2004 by Eric House. "\ + "Todos los derechos reservados.\n\n"\ + + "El manual está en xwords.sourceforge.net\n\n" \ + + "Este programa es postalware.\nSi lo usas " \ + "manda una postal a:\n" \ + "The Houses\n" \ + "1610 NW 14th Place\n" \ + "Corvallis, OR 97330 USA\n\n" + + "Escrito sobre Debian GNU/Linux usando las prc-tools.\n\n"\ + + "Versión española por F. Xavier Gil\n(setimig@wanadooadsl.net)." \ + }, + +{ "STR_CONFIRM_END_GAME", "¿Seguro que quieres terminar la partida?"}, +{ "STR_CONFIRM_TRADE", "¿Seguro que quieres usar tu turno " \ + "para cambiar fichas?" }, +{ "STR_TRADING_REMINDER", "Pulsa D para terminar." }, + +#ifndef NO_REG_REQUIRED +{ "STR_NOT_UNREG_VERS", "Sólo funciona en la versión registrada." }, +#endif + +{ "STR_LOCAL_NAME", "%s" }, +{ "STR_NONLOCAL_NAME", "%s (remoto)" }, +{ "STRSD_SUMMARYSCORED", "%s: %d" }, +{ "STRD_TRADED", "Cambiadas: %d" }, +{ "STR_PASSED", "Ha pasado" }, +{ "STR_LOSTTURN", "Turno perdido" }, + +{ "STR_HISTORY_TITLE", "Jugadas" }, +{ "STRD_REMAINING_TILES_ADD", "+ %d [fichas sobrantes]" }, +{ "STRD_UNUSED_TILES_SUB", "- %d [fichas no usadas]" }, +{ "STR_BONUS_ALL", "Por usar las siete fichas: 50\n" }, +{ "STRD_TURN_SCORE", "Parcial de la jugada: %d\n" }, +{ "STR_ALL_IN_LINE_ERR", "Las fichas no están en línea." }, +{ "STR_NO_EMPTIES_ERR", "Las fichas deben tocarse." }, +{ "STR_FIRST_MOVE_ERR", "Debes usar dos o más fichas en " \ + "la primera jugada." }, + /* phony comment to get diff back in sync */ +{ "STR_MUST_CONTACT_ERR", "Las fichas deben tocar otras (o el " \ + "cuadrado central en la primera jugada)." }, +{ "STR_PTS", "Puntos:" }, +{ "STR_CONFIRM_DEL_GAME", "¿Seguro que quieres borrarla?" }, + +{ "STRD_TIME_PENALTY_SUB", " - %d [por tiempo]" }, + +{ "STR_NO_DICT_INSTALLED", "Crosswords necesita algún diccionario." }, +{ "STR_ILLEGAL_WORD", "¡Ei!, %s no está en el diccionario." }, + +{ "STR_FINAL_SCORES_TITLE", "Puntuación final"}, + + /* another phony comment to get diff back in sync */ + +{ "STRD_CUMULATIVE_SCORE", "Acumulado: %d\n" }, +{ "STRS_TRAY_AT_START", "Atril inicial: %s\n" }, +{ "STRS_MOVE_ACROSS", "jugada (%s, horizontal)\n" }, +{ "STRS_MOVE_DOWN", "jugada (%s, vertical)\n" }, +{ "STRS_NEW_TILES", "Nuevas fichas: %s\n" }, +{ "STRSS_TRADED_FOR", "Cambio de %s por %s." }, +{ "STR_PASS", "pasa\n" }, +{ "STR_PHONY_REJECTED", "Palabra incorrecta; ¡turno perdido!\n" }, + +{ "STR_ROBOT_MOVED", "El robot ha jugado:\n" }, +{ "STR_REMOTE_MOVED", "Un jugador remoto ha jugado:\n" }, +{ "STRD_ROBOT_TRADED", "%d fichas canviadas." }, +{ "STR_ROBOT_TITLE", "Puntos del robot" }, + + /* The end of 4.0.5's strings */ + +{ "STR_PICK_BLANK", "Escoge una ficha para el comodín." }, +#ifdef FEATURE_TRAY_EDIT +{ "STRS_PICK_TILE", "Escoge una nueva ficha para %s." }, +#endif + +#ifdef NODE_CAN_4 +{ "STR_CONFIRM_CONVERTDICT", "¿Quieres convertir los diccionarios de Crosswords " \ + "al nuevo formato? " \ + "El cambio no es reversible." }, +{ "STRS_CONFIRM_ONEDICT", "¿Convertir el diccionario %s?" }, +#endif +{ "STRS_CANNOT_FIND_DICT", "%s dictionary not found." }, /* translate */ +{ "STR_DICT_COPY_EXPL", "Copying dictionary from card..." },/* translate */ diff --git a/xwords4/palm/l10n/StrRes_fr_FR.pre b/xwords4/palm/l10n/StrRes_fr_FR.pre new file mode 100644 index 000000000..48b6ae46f --- /dev/null +++ b/xwords4/palm/l10n/StrRes_fr_FR.pre @@ -0,0 +1,185 @@ +/* -*- mode: c; compile-command: "cd .. && make ARCH=68K_ONLY MEMDEBUG=TRUE LANG=fr_FR"; -*- */ +/* + * Copyright 1997 - 2008 by Eric House (xwords@eehouse.org) and + * others. All rights reserved. Traduction en français par Francis H. + * + * Ce programm est libre de droits ; Vous pouvez le distribuer et/ou + * le modifier en respectant les termes de la licence GNU General + * Public ainsi qu'elle est publiée par la Free Software Foundation; + * que ce soit la version 2 de la Licence, ou (selon votre option), + * toute version ultérieure. + * + * Ce programme est distribué avec l'intention qu'il soit utilisé, + * mais SANS AUCUNE GARANTIE; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Voir la + * Licence GNU General Public pour plus de détails. + * + * Vous devriez avoir reçu une copie de la Licence GNU General Public + * avec ce programme ; si ce n'est pas le cas, écrivez à "Free + * Software Foundation", Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +/* This table is where French user-visible strings come from (all + * strings meant to wind up in a str# resource, actually.) Each entry + * is a pair of strings, first the constant name and then the string + * itself. The build system creats a str# resource with the latter + * and generates a .h file of #defines using the former. */ + +{ "STR_COMMIT_CONFIRM", "Confirmez-vous le coup actuel ?\n" }, +{ "STR_NOT_YOUR_TURN", "Impossible ; ce n'est pas votre tour !" }, +{ "STR_NO_PEEK_ROBOT_TILES", "Ne cherchez pas à voir les lettres du robot !" }, +#ifndef XWFEATURE_STANDALONE_ONLY +{ "STR_NO_PEEK_REMOTE_TILES", "Ne cherchez pas à voir les lettres des autres !" }, +{ "STR_SERVER_DICT_WINS", + "Conflit entre les dictionnaires Hôte et Invité ; l'hôte gagne." }, +{ "STR_REG_UNEXPECTED_USER", "Essai refusé de l'enregistrement de l'utilisateur" }, +{ "STR_REG_NEED_REMOTE", "Svp contactez un ou plusieurs joueurs qd vous " + "jouez comme Hôte." }, +{ "STR_REG_BT_NEED_HOST", "Svp, localisez l'organiseur qd Crossxords joue " + "comme Hôte." }, +{ "STR_RESEND_STANDALONE", "C'est une partie en solo. Il n'y a rien " + "à renvoyer." }, +#endif +{ "STR_TOO_FEW_TILES", "Pas assez de lettres pour échanger." }, +{ "STR_CANT_UNDO_TILEASSIGN", "Impossible d'annuler. (Le tirage initial ne peut être annulé.)" }, +/* translate me */ +{ "STR_CANT_HINT_WHILE_DISABLED", "The hint feature is disabled for this " + "game. Enable it for a new game using the " + "Preferences dialog." }, + +{ "STR_VALUES_TITLE", "Nombres et Valeurs" }, +{ "STR_REMAINS_TITLE","Lettres restantes" }, +{ "STRS_VALUES_HEADER", "%s nombres/valeurs :\n" }, + +{ "STR_DOUBLE_LETTER", "Double lettre" }, +{ "STR_DOUBLE_WORD", "Double mot" }, +{ "STR_TRIPLE_LETTER", "Triple lettre" }, +{ "STR_TRIPLE_WORD", "Triple mot" }, +{ "STR_REMTILES", "rst:%d" }, +{ "STR_CANT_TRADE_MIDTURN", "Enlevez les lettres jouées avant d'échanger."}, +{ "STR_ASK_REPLACE_GAME", "Voulez-vous remplacer la partie en cours ?" }, +{ "STR_OK", "Ok" }, +{ "STR_ABOUT_TITLE", "Au sujet de Crosswords" }, +{ "STR_DEFAULT_NAME", "Joueur %d" }, +{ "STR_CONFIRM_END_GAME", "Etes-vous sûr de vouloir arrêter la partie maintenant ?"}, +{ "STR_CONFIRM_TRADE", "Etes-vous sûr de de vouloir échanger " \ + "vos lettres ?" }, +{ "STR_TRADING_REMINDER", "Cliquez D une fois fini." }, + +{ "STR_LOCAL_NAME", "%s" }, +{ "STR_NONLOCAL_NAME", "%s (autre)" }, +{ "STRSD_SUMMARYSCORED", "%s:%d" }, +{ "STRD_TRADED", "Echangé %d" }, +{ "STR_PASSED", "Passé" }, +{ "STR_LOSTTURN", "Tour perdu" }, + +{ "STR_HISTORY_TITLE", "Historique de la partie" }, +{ "STRD_REMAINING_TILES_ADD", "+ %d [toutes lettres restantes]" }, +{ "STRD_UNUSED_TILES_SUB", "- %d [lettres inutilisées]" }, +{ "STR_BONUS_ALL", "Bonus pour placement toutes les lettres : 50\n" }, +{ "STRD_TURN_SCORE", "Score après ce tour : %d\n" }, +{ "STR_ALL_IN_LINE_ERR", "Toutes les lettres doivent être placées sur une même ligne." }, +{ "STR_NO_EMPTIES_ERR", "Des cases vides ne peuvent séparer les lettres jouées." }, +{ "STR_FIRST_MOVE_ERR", "Vous devez placer au moins deux lettres " \ + "lors du 1er coup." }, + /* phony comment to get diff back in sync */ +{ "STR_MUST_CONTACT_ERR", "Les nouvelles lettres doivent être au contact de celles " \ + "déjà en place (ou sur la case centrale lors du " \ + "premier coup)." }, +{ "STR_PTS", "Pts :" }, +{ "STR_CONFIRM_DEL_GAME", "Supprimer vraiment la partie sélectionnée ?" }, + +{ "STRD_TIME_PENALTY_SUB", " - %d [temps]" }, + +{ "STR_NO_DICT_INSTALLED", "Crosswords 4 nécessite au minimum un dictionnaire." \ + "Téléchargez-en un ou plus sur xwords.sf.net." }, +{ "STR_ILLEGAL_WORD", "Mot[s] %s non trouvé(s) dans le dictionnaire. L'utiliser quand même ?" }, + +{ "STR_FINAL_SCORES_TITLE", "Score final"}, + + /* another phony comment to get diff back in sync */ + +{ "STRD_CUMULATIVE_SCORE", "Score cumulé : %d\n" }, +{ "STRS_TRAY_AT_START", "tableau au départ : %s\n" }, +{ "STRS_MOVE_ACROSS", "coup (depuis %s horizontal)\n" }, +{ "STRS_MOVE_DOWN", "coup (depuis %s vertical)\n" }, +{ "STRS_NEW_TILES", "Nouvelles lettres : %s\n" }, +{ "STRSS_TRADED_FOR", "Echange %s pour %s." }, +{ "STR_PASS", "passé\n" }, +{ "STR_PHONY_REJECTED", "Mot incorrect ; tour perdu !\n" }, + +{ "STR_ROBOT_MOVED", "Le robot a joué ce coup :\n" }, +{ "STR_REMOTE_MOVED", "Le joueur Remote player a joué ce coup :\n" }, +{ "STRD_ROBOT_TRADED", "%d lettres changées ce coup." }, +{ "STR_ROBOT_TITLE", "Score du robot" }, + + /* The end of 4.0.5's strings */ + +{ "STR_PICK_BLANK", "Choisir la lettre en échange de la case blanche." }, +#ifdef FEATURE_TRAY_EDIT +{ "STRS_PICK_TILE", "Choisir une nouvelle lettre pour %s." }, +{ "STR_PICK_TILE_CUR", "En cours" }, +#endif + +#ifdef NODE_CAN_4 +{ "STR_CONFIRM_CONVERTDICT", "Voulez-vous convertir les dictionnaires Crosswords " \ + "existants au nouveau format? " \ + "Le changement est irréversible." }, +{ "STRS_CONFIRM_ONEDICT", "Convertir dictionnaire %s?" }, +#endif +{ "STRS_CANNOT_FIND_DICT", "%s dictionnaire non trouvé." }, +{ "STR_DICT_COPY_EXPL", "Copier dictionnaire depuis la carte..." }, + +{ "STR_LOCALPLAYERS", "Joueurs loc." }, +{ "STR_TOTALPLAYERS", "Total joueurs" }, + +#ifdef XWFEATURE_RELAY +{ "STR_RELAY_XPORTNAME", "Internet" }, +{ "STR_RELAY_TIMEOUT", "Erreur réseau : les autres joueurs n'ont pu " + "se connecter." }, +{ "STR_RELAY_LOST_OTHER", "Erreur réseau : la connexion d'un autre joueur " + "s'est arrêtée." }, +{ "STR_RELAY_GENERIC", "Erreur réseau : quelque chose ne fonctionne pas." }, +#endif + +#ifdef XWFEATURE_BLUETOOTH +{ "STR_BT_XPORTNAME", "Bluetooth" }, +{ "STR_BT_NOINIT", "Impossible initialiser Bluetooth. " + "Est-il connecté ?" }, +{ "STRS_BT_CONFIRM", "Crosswords est-il en marche sur %s " + "et est-il prêt à recevoir une connexion ? " + "(Si vous choisissez \"Non\", Crosswords " + "n'essayera pas de se connecter jusqu'à ce " + "que vous le redémarriez ou choisissiez " + "\"Renvoyer\" dans le menu.)" }, +#endif + +#ifdef XWFEATURE_IR +{ "STR_IR_XPORTNAME", "Transmission" }, +#endif + +{ "STR_ABOUT_CONTENT", +/* #ifdef XWFEATURE_BLUETOOTH */ +/* "MERCI DE VOTRE ATTENTION pour cette version beta de " */ +/* "Crosswords. Svp, regardez les notes et avertissements sur " */ +/* "xwords.sf.net/bt_palm.php. Svp, signalez les bugs à " */ +/* "xwords@eehouse.org. Au plaisir !\n\n" */ +/* #endif */ + "Crosswords " XW_PALM_VERSION_STRING " (rev. " SVN_REV ").\n" \ + "Copyright 1998-2008 par Eric House. "\ + "Réalisé sous GNU Public License.\n\n"\ + + "Voir le manuel sur xwords.sf.net ou eehouse.org/xwords/.\n\n" \ + + "Ce programme est 'postcardware'. Si vous l'appréciez, " \ + "svp, envoyez une carte postale à :\n" \ + "The Houses\n" \ + "1610 NW 14th Place\n" \ + "Corvallis, OR 97330 USA\n\n" + + "Developpé sur Debian GNU/Linux avec the prc-tools "\ + "suite.\n\n"\ + + "Traduction par Francis H." + }, diff --git a/xwords4/palm/l10n/mkstrsres.c b/xwords4/palm/l10n/mkstrsres.c new file mode 100644 index 000000000..7344755e0 --- /dev/null +++ b/xwords4/palm/l10n/mkstrsres.c @@ -0,0 +1,78 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/**************************************************************************** + * * + * Copyright 1998-2000 by Eric House (xwords@eehouse.org). All rights reserved. * + * * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + ****************************************************************************/ + +/* + * Turn a table of string pairs into a resource file of back-to-back strings + * and an includable .h file of constants giving indices into the res file. + * + * Expects the name of the former in argv[1] and of the latter in argv[2]. + */ + +#include +#include + +#include "xwords4defines.h" + +#define FIRST_STR_INDEX 2000 + +typedef struct StringPair { + char* constName; + char* theString; +} StringPair; + +static StringPair table[] = { + // I'm expecting this as a -D option +#include LANGSTRFILE + { (char*)0L, (char*)0L } +}; + + +int +main( int argc, char** argv ) +{ + FILE* stringResFile = fopen( argv[1], "wb" ); + FILE* stringConstFile = fopen( argv[2], "w" ); + StringPair* sp = table; + short count; + + fprintf( stringConstFile, + "/***********************************************************\n" + "* This file is machine generated.\n" + "* Don't edit: your changes will be lost.\n" + "************************************************************/\n"); + + fprintf( stringConstFile, "\n#define FIRST_STR_INDEX %d\n\n", + FIRST_STR_INDEX ); + + count = 0; + for ( ; sp->constName != NULL && sp->theString != NULL; ++sp ) { + short strBytes; + + strBytes = strlen(sp->theString)+1; + fwrite( sp->theString, strBytes, 1, stringResFile ); + fprintf( stringConstFile, "#define %s %d\n", sp->constName, count ); + count += strBytes; + } + fprintf( stringConstFile, "#define %s %d\n", "STR_LAST_STRING", count ); + + fclose( stringResFile ); + fclose( stringConstFile ); + return 0; +} // main diff --git a/xwords4/palm/l10n/xwords4_en_US.rcp.pre b/xwords4/palm/l10n/xwords4_en_US.rcp.pre new file mode 100644 index 000000000..b7e2e0455 --- /dev/null +++ b/xwords4/palm/l10n/xwords4_en_US.rcp.pre @@ -0,0 +1,618 @@ +/* -*-mode: c; fill-column: 78; compile-command: "cd ../ && make ARCH=68K_ONLY MEMDEBUG=TRUE"; -*- */ +/***************************************************************************** + * Copyright 1999 - 2006 by Eric House (xwords@eehouse.org) and others. All + * rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + ****************************************************************************/ + +#define DEFINES_ONLY 1 + +#include "xwords4defines.h" + +#define LEFTMARGIN 5 + +MENU XW_MAIN_MENU_ID +BEGIN + PULLDOWN "File" + BEGIN + MENUITEM "New game..." XW_NEWGAME_PULLDOWN_ID "N" + MENUITEM "Saved games..." XW_SAVEDGAMES_PULLDOWN_ID "S" + MENUITEM "Preferences..." XW_PREFS_PULLDOWN_ID "P" + MENUITEM SEPARATOR + MENUITEM "Beam dictionary" XW_BEAMDICT_PULLDOWN_ID + MENUITEM "Beam boards and colors" XW_BEAMBOARD_PULLDOWN_ID +#ifdef FEATURE_DUALCHOOSE + MENUITEM SEPARATOR + MENUITEM "Launch 68K vers." XW_RUN68K_PULLDOWN_ID + MENUITEM "Launch ARM vers." XW_RUNARM_PULLDOWN_ID +#endif + MENUITEM SEPARATOR + MENUITEM "About Crosswords..." XW_ABOUT_PULLDOWN_ID "A" + END + + PULLDOWN "Game" + BEGIN + MENUITEM "Tile values" XW_TILEVALUES_PULLDOWN_ID "V" + MENUITEM "Remaining tiles" XW_TILESLEFT_PULLDOWN_ID "R" + MENUITEM "Current game info..." XW_PASSWORDS_PULLDOWN_ID "G" + MENUITEM "History" XW_HISTORY_PULLDOWN_ID "Y" + MENUITEM "Final scores" XW_FINISH_PULLDOWN_ID "F" +#ifndef XWFEATURE_STANDALONE_ONLY + MENUITEM SEPARATOR + MENUITEM "Resend" XW_RESENDIR_PULLDOWN_ID +#endif + END + PULLDOWN "Move" + BEGIN + MENUITEM "Hint" XW_HINT_PULLDOWN_ID "I" +#ifdef XWFEATURE_SEARCHLIMIT + MENUITEM "Limited hint" XW_HINTCONFIG_PULLDOWN_ID "C" + MENUITEM SEPARATOR +#endif + MENUITEM "Next hint" XW_NEXTHINT_PULLDOWN_ID "M" + MENUITEM SEPARATOR + MENUITEM "Undo current" XW_UNDOCUR_PULLDOWN_ID "U" + MENUITEM "Undo last" XW_UNDOLAST_PULLDOWN_ID "Z" + MENUITEM SEPARATOR + MENUITEM "Done" XW_DONE_PULLDOWN_ID "D" + MENUITEM "Juggle" XW_JUGGLE_PULLDOWN_ID "J" + MENUITEM "Trade tiles" XW_TRADEIN_PULLDOWN_ID "T" + MENUITEM "[un]Hide tray" XW_HIDESHOWTRAY_PULLDOWN_ID "H" + END +#ifdef FOR_GREMLINS + PULLDOWN "Grem" + BEGIN + MENUITEM "divider right" XW_GREMLIN_DIVIDER_RIGHT + MENUITEM "divider left" XW_GREMLIN_DIVIDER_LEFT + END +#endif + + +#ifndef FOR_GREMLINS +#ifdef DEBUG + PULLDOWN "DBG" + BEGIN + MENUITEM "Log to file" XW_LOGFILE_PULLDOWN_ID + MENUITEM "Log to memo" XW_LOGMEMO_PULLDOWN_ID + MENUITEM "Clear logs" XW_CLEARLOGS_PULLDOWN_ID + MENUITEM "Network stats..." XW_NETSTATS_PULLDOWN_ID +#ifdef DEBUG + MENUITEM "BT stats..." XW_BTSTATS_PULLDOWN_ID +#endif +#ifdef MEM_DEBUG + MENUITEM "Mem stats..." XW_MEMSTATS_PULLDOWN_ID +#endif + END +#endif +#endif /* FOR_GREMLINS */ + +END + +MENU XW_ASK_MENU_ID +BEGIN + PULLDOWN "Edit" + BEGIN + MENUITEM "Copy" ASK_COPY_PULLDOWN_ID + MENUITEM "Select all" ASK_SELECTALL_PULLDOWN_ID + END +END + +#include "common.rcp.pre" /* these don't need localization */ + +#ifdef XWFEATURE_STANDALONE_ONLY +# define NPLAYERS_TOP 15 +# define FORM_TOP 34 +# define FORM_HEIGHT 124 +#else +# define SERVER_TOP 15 +# define NPLAYERS_TOP (SERVER_TOP+18) +# define FORM_TOP 16 +# define FORM_HEIGHT 142 +#endif +#define LABEL_TOP (NPLAYERS_TOP+18) +#define LEFTCOL 4 +#define REMOTE_COL LEFTCOL +#define NAME_COL 50 +#define ROBOT_COL 98 +#define PASSWD_COL RIGHT@156 + +#ifndef XWFEATURE_STANDALONE_ONLY +#define PLAYER_REMOTECHECK( num, offset ) \ + CHECKBOX "" ID XW_REMOTE_##num##_CHECKBOX_ID \ + AT (LEFTCOL PREVBOTTOM+offset AUTO AUTO) USABLE +#else +#define PLAYER_REMOTECHECK( num, offset ) +#endif + +#ifndef XWFEATURE_STANDALONE_ONLY +#define PLAYER_NAMEFIELD( num, offset ) \ + FIELD XW_PLAYERNAME_##num##_FIELD_ID \ + AT (PREVRIGHT PREVTOP 100 AUTO ) \ + UNDERLINED EDITABLE SINGLELINE MAXCHARS MAX_PLAYERNAME_LENGTH +#else +#define PLAYER_NAMEFIELD( num, offset ) \ + FIELD XW_PLAYERNAME_##num##_FIELD_ID \ + AT (LEFTCOL+10 PREVBOTTOM+offset 100 AUTO ) \ + UNDERLINED EDITABLE SINGLELINE MAXCHARS MAX_PLAYERNAME_LENGTH +#endif + +#define PLAYER_ROBCHECK( num, offset ) \ + CHECKBOX "" ID XW_ROBOT_##num##_CHECKBOX_ID \ + AT (PREVRIGHT PREVTOP AUTO AUTO) USABLE RIGHTANCHOR + +#define PLAYER_PASSFIELD( num, offset ) \ + SELECTORTRIGGER "" XW_PLAYERPASSWD_##num##_TRIGGER_ID \ + AT (PREVRIGHT PREVTOP 12 11) + +/* FIELD XW_PLAYERPASSWD_##num##_FIELD_ID PREVRIGHT PREVTOP 20 \ */ +/* AUTO UNDERLINED EDITABLE SINGLELINE MAXCHARS 4 */ + +#define PLAYER_ROW( num, offset ) \ + PLAYER_REMOTECHECK( num, offset ) \ + PLAYER_NAMEFIELD( num, offset ) \ + PLAYER_ROBCHECK( num, offset ) \ + PLAYER_PASSFIELD( num, offset ) + +#define PLAYER_ROW_ID( num ) \ + ID XW_REMOTE_##num##_CHECKBOX_ID \ + ID XW_PLAYERNAME_##num##_FIELD_ID \ + ID XW_ROBOT_##num##_CHECKBOX_ID \ + ID XW_PLAYERPASSWD_##num##_TRIGGER_ID \ + +#define PLAYER_ROW_NAV( num ) \ + ROW XW_REMOTE_##num##_CHECKBOX_ID \ + ROW XW_PLAYERNAME_##num##_FIELD_ID \ + XW_ROBOT_##num##_CHECKBOX_ID \ + XW_PLAYERPASSWD_##num##_TRIGGER_ID \ + +//#define SERVER_GROUP_ID 2000 +#define SERVER_HEIGHT 12 + +FORM ID XW_NEWGAMES_FORM AT (2 FORM_TOP 156 FORM_HEIGHT) +USABLE MODAL SAVEBEHIND DEFAULTBTNID XW_CANCEL_BUTTON_ID +BEGIN + TITLE "Game options" + +#ifndef XWFEATURE_STANDALONE_ONLY + LABEL "Connect:" AUTOID AT (LEFTCOL SERVER_TOP) + GADGET ID XW_SOLO_GADGET_ID + AT (PREVRIGHT+2 SERVER_TOP 55 SERVER_HEIGHT) USABLE + GADGET ID XW_SERVER_GADGET_ID + AT (PREVRIGHT+1 SERVER_TOP 24 SERVER_HEIGHT) USABLE + GADGET ID XW_CLIENT_GADGET_ID + AT (PREVRIGHT+1 SERVER_TOP 30 SERVER_HEIGHT) USABLE + LIST "Stand-alone" "Host" "Guest" XW_SERVERTYPES_LIST_ID + AT (0 0 1 1) VISIBLEITEMS 3 NONUSABLE +#endif + +/* Pick number of players here */ + FIELD XW_TOTALP_FIELD_ID LEFTCOL NPLAYERS_TOP 58 AUTO \ + SINGLELINE NONEDITABLE MAXCHARS 16 + + SELECTORTRIGGER "" XW_NPLAYERS_SELECTOR_ID \ + AT (PREVRIGHT NPLAYERS_TOP AUTO AUTO) USABLE LEFTANCHOR + LIST "1" "2" "3" "4" XW_NPLAYERS_LIST_ID AT (PREVLEFT PREVTOP 10 1) \ + VISIBLEITEMS 4 NONUSABLE + + BUTTON "J" XW_GINFO_JUGGLE_ID PREVRIGHT+3 PREVTOP 10 AUTO + + BUTTON "Other prefs..." XW_PREFS_BUTTON_ID PREVRIGHT+5 PREVTOP 63 AUTO + +#ifndef XWFEATURE_STANDALONE_ONLY + LABEL "Remote" XW_LOCAL_LABEL_ID REMOTE_COL LABEL_TOP FONT 1 +#endif + LABEL "Name" AUTOID NAME_COL LABEL_TOP FONT 1 + LABEL "Robot" AUTOID ROBOT_COL LABEL_TOP FONT 1 + LABEL "Pwd" AUTOID PASSWD_COL LABEL_TOP FONT 1 + + PLAYER_ROW( 1, 2 ) + PLAYER_ROW( 2, 2 ) + PLAYER_ROW( 3, 2 ) + PLAYER_ROW( 4, 2 ) + + GRAFFITISTATEINDICATOR 2 PREVBOTTOM+10 + + SELECTORTRIGGER "Dictionary..." XW_DICT_SELECTOR_ID \ + AT (PREVRIGHT+8 PREVTOP AUTO AUTO) USABLE LEFTANCHOR + + BUTTON "Ok" XW_OK_BUTTON_ID RIGHT@154 PREVTOP AUTO AUTO + BUTTON "Cancel" XW_CANCEL_BUTTON_ID RIGHT@PREVLEFT-5 PREVTOP 30 AUTO +END /* XW_NEWGAMES_FORM */ + +#ifdef XWFEATURE_FIVEWAY +NAVIGATION ID XW_NEWGAMES_FORM +INITIALSTATE kFrmNavHeaderFlagsObjectFocusStartState +#if 0 +BEGIN + ID XW_SOLO_GADGET_ID + ID XW_SERVER_GADGET_ID + ID XW_CLIENT_GADGET_ID + + ID XW_NPLAYERS_SELECTOR_ID + ID XW_GINFO_JUGGLE_ID + ID XW_PREFS_BUTTON_ID + + PLAYER_ROW_ID( 1 ) + PLAYER_ROW_ID( 2 ) + PLAYER_ROW_ID( 3 ) + PLAYER_ROW_ID( 4 ) + + ID XW_DICT_SELECTOR_ID + ID XW_CANCEL_BUTTON_ID + ID XW_OK_BUTTON_ID +#else +NAVIGATIONMAP +#ifndef XWFEATURE_STANDALONE_ONLY + ROW XW_SOLO_GADGET_ID + XW_SERVER_GADGET_ID + XW_CLIENT_GADGET_ID +#endif + ROW XW_NPLAYERS_SELECTOR_ID + XW_GINFO_JUGGLE_ID + XW_PREFS_BUTTON_ID + + PLAYER_ROW_NAV( 1 ) + PLAYER_ROW_NAV( 2 ) + PLAYER_ROW_NAV( 3 ) + PLAYER_ROW_NAV( 4 ) + + ROW XW_DICT_SELECTOR_ID + XW_CANCEL_BUTTON_ID + XW_OK_BUTTON_ID +#endif +END /* NAVIGATION ID XW_NEWGAMES_FORM */ +#endif + +#ifdef XWFEATURE_BLUETOOTH +/* Let's define this in one place so it stays the same */ +# define BT_CONF_STRING "Ask before connecting Bluetooth" +#endif + + +#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH || defined XWFEATURE_IR +#define LEFTCOL 4 +#define CONNS_FIELD_LEFT 70 +#define LOCALIP_TOP 30 + +FORM ID XW_CONNS_FORM AT (2 66 156 93) +USABLE MODAL SAVEBEHIND DEFAULTBTNID XW_CONNS_CANCEL_BUTTON_ID +BEGIN + TITLE "Connections" + + LABEL "Connect via:" AUTOID LEFTCOL 15 FONT 1 + + POPUPTRIGGER "" ID XW_CONNS_TYPE_TRIGGER_ID + AT (PREVRIGHT+5 PREVTOP 72 12) LEFTANCHOR + LIST + "" ID XW_CONNS_TYPE_LIST_ID + PREVLEFT PREVTOP 72 12 VISIBLEITEMS 2 + NONUSABLE POPUPLIST XW_CONNS_TYPE_TRIGGER_ID XW_CONNS_TYPE_LIST_ID + +#ifdef XWFEATURE_BLUETOOTH + LABEL "Host device:" XW_CONNS_BT_HOSTNAME_LABEL_ID + AT ( LEFTCOL LOCALIP_TOP+5 ) NONUSABLE + SELECTORTRIGGER "Find host..." XW_CONNS_BT_HOSTTRIGGER_ID \ + AT (CONNS_FIELD_LEFT PREVTOP 70 AUTO) NONUSABLE LEFTANCHOR + CHECKBOX BT_CONF_STRING ID XW_CONNS_BTCONFIRM_CHECKBOX_ID \ + AT ( LEFTCOL LOCALIP_TOP+21 AUTO AUTO ) NONUSABLE +#endif + +/* Relay... */ +#ifdef XWFEATURE_RELAY + LABEL "Relay name:" XW_CONNS_RELAY_LABEL_ID + AT ( LEFTCOL+10 LOCALIP_TOP ) + FIELD XW_CONNS_RELAY_FIELD_ID CONNS_FIELD_LEFT PREVTOP 70 AUTO \ + SINGLELINE EDITABLE UNDERLINED MAXCHARS 32 + + LABEL "Relay port:" XW_CONNS_PORT_LABEL_ID + AT (LEFTCOL PREVBOTTOM + 2) + FIELD XW_CONNS_PORT_FIELD_ID CONNS_FIELD_LEFT PREVTOP 30 AUTO \ + EDITABLE SINGLELINE UNDERLINED NUMERIC MAXCHARS 5 + + LABEL "Cookie:" XW_CONNS_COOKIE_LABEL_ID + AT ( LEFTCOL+10 PREVBOTTOM + 2 ) + FIELD XW_CONNS_COOKIE_FIELD_ID CONNS_FIELD_LEFT PREVTOP 70 AUTO \ + SINGLELINE EDITABLE UNDERLINED MAXCHARS 32 +#endif + + BUTTON "Cancel" XW_CONNS_CANCEL_BUTTON_ID 42 75 AUTO AUTO + BUTTON "Ok" XW_CONNS_OK_BUTTON_ID PREVRIGHT+10 PREVTOP AUTO AUTO +END /* XW_CONNS_FORM */ +#endif + +#ifdef FEATURE_TRAY_EDIT +# define TRAY_EDIT_ADJUST 15 +#else +# define TRAY_EDIT_ADJUST 0 +#endif +#ifdef XWFEATURE_SEARCHLIMIT +# define SEARCHLIMIT_ADJUST 15 +#else +# define SEARCHLIMIT_ADJUST 0 +#endif +#ifdef XWFEATURE_BLUETOOTH +# define BTCONF_ADJUST PREFS_SPACING +#else +# define BTCONF_ADJUST 0 +#endif + +#define PREFS_MODE_TOP 15 +#define PREFS_ROW1 30 +#define PREFS_SPACING 15 + +/* #define DLG_TOP (52-TRAY_EDIT_ADJUST-SEARCHLIMIT_ADJUST) */ +#define DLG_HEIGHT (112+TRAY_EDIT_ADJUST+SEARCHLIMIT_ADJUST+BTCONF_ADJUST) +#define DLG_TOP (160 - DLG_HEIGHT - 2) +#define TIMER_TOP (74+SEARCHLIMIT_ADJUST) +#define BTCONF_TOP (TIMER_TOP+18+TRAY_EDIT_ADJUST) +#define BUTTON_TOP (BTCONF_TOP + PREFS_SPACING) +#define PREFS_LNHT 4 + +FORM ID XW_PREFS_FORM AT (2 DLG_TOP 156 DLG_HEIGHT) +USABLE MODAL SAVEBEHIND DEFAULTBTNID XW_PREFS_CANCEL_BUTTON_ID +BEGIN + TITLE "Preferences" + + GADGET ID XW_PREFS_ALLGAMES_GADGET_ID + AT (LEFTCOL+8 PREFS_MODE_TOP 60 SERVER_HEIGHT) USABLE + GADGET ID XW_PREFS_ONEGAME_GADGET_ID + AT (PREVRIGHT+1 PREVTOP 60 SERVER_HEIGHT) USABLE + LIST "All games" "This game" XW_PREFS_TYPES_LIST_ID + AT (0 0 1 1) VISIBLEITEMS 2 NONUSABLE + + /* global prefs */ + CHECKBOX "Color played tiles" ID XW_PREFS_PLAYERCOLORS_CHECKBOX_ID \ + AT (LEFTCOL PREFS_ROW1 AUTO AUTO) NONUSABLE + CHECKBOX "Show progress bar" ID XW_PREFS_PROGRESSBAR_CHECKBOX_ID \ + AT (LEFTCOL PREVTOP+PREFS_SPACING AUTO AUTO) NONUSABLE + CHECKBOX "Larger board" ID XW_PREFS_SHOWGRID_CHECKBOX_ID \ + AT (LEFTCOL PREVTOP+PREFS_SPACING AUTO AUTO) NONUSABLE + CHECKBOX "Enable arrow cursor" ID XW_PREFS_SHOWARROW_CHECKBOX_ID \ + AT (LEFTCOL PREVTOP+PREFS_SPACING AUTO AUTO) NONUSABLE + CHECKBOX "Explain robot/remote scores" ID XW_PREFS_ROBOTSCORE_CHECKBOX_ID \ + AT (LEFTCOL PREVTOP+PREFS_SPACING AUTO AUTO) NONUSABLE + CHECKBOX "Hide tile values" ID XW_PREFS_HIDETRAYVAL_CHECKBOX_ID \ + AT (LEFTCOL PREVTOP+PREFS_SPACING AUTO AUTO) NONUSABLE + + /* single-game prefs */ + CHECKBOX "Smart robot" ID XW_PREFS_ROBOTSMART_CHECKBOX_ID \ + AT (LEFTCOL PREFS_ROW1 AUTO AUTO) NONUSABLE +#ifdef XWFEATURE_SEARCHLIMIT + CHECKBOX "Disallow hints" ID XW_PREFS_NOHINTS_CHECKBOX_ID \ + AT (LEFTCOL PREVTOP+PREFS_SPACING AUTO AUTO) NONUSABLE + CHECKBOX "Local hints" ID XW_PREFS_HINTRECT_CHECKBOX_ID \ + AT (PREVRIGHT+3 PREVTOP AUTO AUTO) NONUSABLE +#else + CHECKBOX "Disallow hints" ID XW_PREFS_NOHINTS_CHECKBOX_ID \ + AT (PREVRIGHT+3 PREFS_ROW1 AUTO AUTO) NONUSABLE +#endif + + LABEL "Phonies:" XW_PREFS_PHONIES_LABEL_ID + AT (LEFTCOL PREVTOP+PREFS_SPACING) + POPUPTRIGGER "" ID XW_PREFS_PHONIES_TRIGGER_ID + AT (PREVRIGHT+5 PREVTOP 72 12) LEFTANCHOR + + LABEL "Board size: " XW_PREFS_BDSIZE_LABEL_ID + AT (LEFTCOL PREVTOP+PREFS_SPACING) + SELECTORTRIGGER "" XW_PREFS_BDSIZE_SELECTOR_ID \ + AT (PREVRIGHT PREVTOP AUTO AUTO) USABLE LEFTANCHOR + LIST "Ignore" "Warn" "Disallow" ID XW_PREFS_PHONIES_LIST_ID + AT (PREVLEFT PREVTOP 72 12) NONUSABLE VISIBLEITEMS 3 + POPUPLIST XW_PREFS_PHONIES_TRIGGER_ID XW_PREFS_PHONIES_LIST_ID + + LIST "15x15" "13x13" "11x11" \ + XW_PREFS_BDSIZE_LIST_ID AT (PREVLEFT PREVTOP 30 1) \ + NONUSABLE VISIBLEITEMS NUM_BOARD_SIZES + + CHECKBOX "Enable timer (minutes:)" ID XW_PREFS_TIMERON_CHECKBOX_ID \ + AT (LEFTCOL PREVTOP+PREFS_SPACING AUTO AUTO) NONUSABLE + FIELD XW_PREFS_TIMER_FIELD_ID PREVRIGHT+5 PREVTOP 15 AUTO UNDERLINED \ + EDITABLE SINGLELINE NUMERIC MAXCHARS 3 + +#ifdef FEATURE_TRAY_EDIT + CHECKBOX "Pick tiles face-up" ID XW_PREFS_PICKTILES_CHECKBOX_ID \ + AT (LEFTCOL PREVTOP+PREFS_SPACING AUTO AUTO) NONUSABLE +#endif + +#ifdef XWFEATURE_BLUETOOTH + CHECKBOX BT_CONF_STRING ID XW_PREFS_BTCONFIRM_CHECKBOX_ID \ + AT (LEFTCOL PREVTOP+PREFS_SPACING AUTO AUTO) NONUSABLE +#endif + + /* buttons at the bottom */ + BUTTON "Cancel" XW_PREFS_CANCEL_BUTTON_ID 42 BUTTON_TOP AUTO AUTO + BUTTON "Ok" XW_PREFS_OK_BUTTON_ID PREVRIGHT+10 PREVTOP AUTO AUTO +END /* XW_PREFS_FORM */ + +NAVIGATION ID XW_PREFS_FORM +INITIALSTATE kFrmNavHeaderFlagsObjectFocusStartState +INITIALOBJECTID XW_PREFS_ONEGAME_GADGET_ID +NAVIGATIONMAP + ROW XW_PREFS_ALLGAMES_GADGET_ID + XW_PREFS_ONEGAME_GADGET_ID + + /* global prefs */ + ROW XW_PREFS_PLAYERCOLORS_CHECKBOX_ID + ROW XW_PREFS_PROGRESSBAR_CHECKBOX_ID + ROW XW_PREFS_SHOWGRID_CHECKBOX_ID + ROW XW_PREFS_SHOWARROW_CHECKBOX_ID + ROW XW_PREFS_ROBOTSCORE_CHECKBOX_ID + ROW XW_PREFS_HIDETRAYVAL_CHECKBOX_ID + + /* Per-game prefs */ + ROW XW_PREFS_ROBOTSMART_CHECKBOX_ID + ROW XW_PREFS_NOHINTS_CHECKBOX_ID +#ifdef XWFEATURE_SEARCHLIMIT + XW_PREFS_HINTRECT_CHECKBOX_ID +#endif + ROW XW_PREFS_PHONIES_TRIGGER_ID + ROW XW_PREFS_BDSIZE_SELECTOR_ID + + ROW XW_PREFS_TIMERON_CHECKBOX_ID + XW_PREFS_TIMER_FIELD_ID +#ifdef FEATURE_TRAY_EDIT + ROW XW_PREFS_PICKTILES_CHECKBOX_ID +#endif + ROW XW_PREFS_BTCONFIRM_CHECKBOX_ID + + /* cmd buttons */ + ROW XW_PREFS_CANCEL_BUTTON_ID + XW_PREFS_OK_BUTTON_ID +END + +#define LEFT_EDGE 10 +FORM ID XW_DICTINFO_FORM AT (2 111 156 47) +USABLE MODAL DEFAULTBTNID XW_DICTINFO_CANCEL_BUTTON_ID +BEGIN + TITLE "Dictionaries" + + LABEL "Dict:" AUTOID AT (LEFT_EDGE 15) + POPUPTRIGGER "" ID XW_DICTINFO_TRIGGER_ID + AT (PREVRIGHT+5 PREVTOP 72 12) LEFTANCHOR + LIST "" ID XW_DICTINFO_LIST_ID AT (PREVLEFT PREVTOP 72 1) + NONUSABLE VISIBLEITEMS 4 + POPUPLIST XW_DICTINFO_TRIGGER_ID XW_DICTINFO_LIST_ID + + BUTTON "Ok" XW_DICTINFO_DONE_BUTTON_ID 25 31 AUTO AUTO + BUTTON "Beam" XW_DICTINFO_BEAM_BUTTON_ID 22 PREVTOP AUTO AUTO + BUTTON "Cancel" XW_DICTINFO_CANCEL_BUTTON_ID PREVRIGHT+20 PREVTOP + AUTO AUTO +END + +FORM ID XW_ASK_FORM_ID AT (2 70 156 88) +USABLE MODAL SAVEBEHIND DEFAULTBTNID XW_ASK_NO_BUTTON_ID +MENUID XW_ASK_MENU_ID +BEGIN + TITLE "Query" + + /* This has to be non-editable because the field is set via a ptr */ + FIELD XW_ASK_TXT_FIELD_ID LEFT_EDGE-5 16 135 52 \ + NONEDITABLE MULTIPLELINES + + SCROLLBAR ID XW_ASK_SCROLLBAR_ID + AT ( PREVRIGHT+2 PREVTOP RECOMMENDED_SBAR_WIDTH + PREVBOTTOM - PREVTOP) USABLE + + BUTTON "Yes" XW_ASK_YES_BUTTON_ID RIGHT@(156/2)-20 PREVBOTTOM+5 AUTO AUTO + BUTTON "No" XW_ASK_NO_BUTTON_ID 156/2+20 PREVTOP AUTO AUTO +END + +FORM ID XW_PASSWORD_DIALOG_ID AT ( 2 88 156 70 ) + MODAL SAVEBEHIND DEFAULTBTNID XW_PASSWORD_CANCEL_BUTTON +BEGIN + TITLE "Password" + + LABEL "Enter password for:" XW_PASSWORD_NAME_LABEL 10 18 FONT 1 NONUSABLE + LABEL "Enter new password for:" XW_PASSWORD_NEWNAME_LABEL 10 18 FONT 1 + NONUSABLE + FIELD XW_PASSWORD_NAME_FIELD AT (PREVLEFT PREVBOTTOM+3 90 12) NONEDITABLE + SINGLELINE MAXCHARS MAX_PLAYERNAME_LENGTH + FIELD XW_PASSWORD_PASS_FIELD \ + AT (PREVRIGHT+10 PREVTOP MAX_PASSWORD_LENGTH*6 12) \ + EDITABLE SINGLELINE UNDERLINED MAXCHARS MAX_PASSWORD_LENGTH + GRAFFITISTATEINDICATOR 2 PREVBOTTOM+8 + BUTTON "Ok" XW_PASSWORD_OK_BUTTON 45 PREVTOP AUTO AUTO + BUTTON "Cancel" XW_PASSWORD_CANCEL_BUTTON PREVRIGHT+10 PREVTOP AUTO AUTO +END + +#define BLANK_PICK_TOP 15 + +FORM ID XW_BLANK_DIALOG_ID AT ( 2 74 156 83 ) USABLE + MODAL SAVEBEHIND +#ifdef FEATURE_TRAY_EDIT + DEFAULTBTNID XW_BLANK_PICK_BUTTON_ID +#endif +BEGIN + TITLE "Tile picker" + + FIELD XW_BLANK_LABEL_FIELD_ID AT (10 BLANK_PICK_TOP 110 39) + NONEDITABLE MULTIPLELINES + + LIST "" ID XW_BLANK_LIST_ID AT (PREVRIGHT+2 BLANK_PICK_TOP 28 72) + USABLE VISIBLEITEMS 6 + + BUTTON "Ok" XW_BLANK_OK_BUTTON_ID RIGHT@PREVLEFT-8 65 AUTO AUTO + +#ifdef FEATURE_TRAY_EDIT + BUTTON "Pick all!" XW_BLANK_PICK_BUTTON_ID 5 65 AUTO AUTO + BUTTON "Del" XW_BLANK_BACKUP_BUTTON_ID PREVRIGHT+5 PREVTOP 22 AUTO +#endif +END + +#ifdef XWFEATURE_SEARCHLIMIT +# define NTILES_TOP 17 +# define NTILES_BUTTON_TOP 60 +FORM ID XW_HINTCONFIG_FORM_ID AT ( 2 75 156 82 ) USABLE MODAL SAVEBEHIND +BEGIN + TITLE "Hint parameters" + + LABEL "At least this many tiles:" AUTOID 10 NTILES_TOP FONT 1 USABLE + SELECTORTRIGGER "" XW_HINTCONFIG_MINSELECTOR_ID \ + AT (PREVRIGHT+3 NTILES_TOP AUTO AUTO) USABLE LEFTANCHOR + LIST "1" "2" "3" "4" "5" "6" "7" ID XW_HINTCONFIG_MINLIST_ID + AT (PREVRIGHT PREVTOP AUTO AUTO) VISIBLEITEMS 7 NONUSABLE + + LABEL "No more than this many:" AUTOID 10 PREVTOP+15 FONT 1 USABLE + SELECTORTRIGGER "" XW_HINTCONFIG_MAXSELECTOR_ID \ + AT (PREVRIGHT+3 PREVTOP AUTO AUTO) USABLE LEFTANCHOR + LIST "1" "2" "3" "4" "5" "6" "7" ID XW_HINTCONFIG_MAXLIST_ID + AT (PREVRIGHT PREVTOP AUTO AUTO) VISIBLEITEMS 7 NONUSABLE + + BUTTON "Ok" XW_HINTCONFIG_OK_ID RIGHT@156-10 NTILES_BUTTON_TOP AUTO 12 + BUTTON "Cancel" XW_HINTCONFIG_CANCEL_ID RIGHT@PREVLEFT-10 PREVTOP AUTO 12 +END +#endif + +#if defined OWNER_HASH || defined NO_REG_REQUIRED +FORM ID XW_SAVEDGAMES_DIALOG_ID AT ( 2 2 156 156 ) +USABLE MODAL DEFAULTBTNID XW_SAVEDGAMES_DONE_BUTTON +BEGIN + TITLE "Saved games" + + LIST "" ID XW_SAVEDGAMES_LIST_ID AT (2 15 140 60) \ + USABLE ENABLED VISIBLEITEMS 1 + GRAFFITISTATEINDICATOR 2 120 + FIELD XW_SAVEDGAMES_NAME_FIELD AT (PREVRIGHT+10 PREVTOP 100 AUTO) + EDITABLE SINGLELINE UNDERLINED MAXCHARS MAX_GAMENAME_LENGTH + + BUTTON "Mod." XW_SAVEDGAMES_USE_BUTTON RIGHT@154 PREVTOP 30 AUTO + + BUTTON "Dup." XW_SAVEDGAMES_DUPE_BUTTON 2 PREVBOTTOM+5 31 AUTO + BUTTON "Delete" XW_SAVEDGAMES_DELETE_BUTTON PREVRIGHT+5 PREVTOP 39 AUTO + BUTTON "Open" XW_SAVEDGAMES_OPEN_BUTTON PREVRIGHT+5 PREVTOP 33 AUTO + BUTTON "Done" XW_SAVEDGAMES_DONE_BUTTON PREVRIGHT+5 PREVTOP 33 AUTO +END /* XW_SAVEDGAMES_DIALOG_ID */ +#endif + +ALERT XW_ERROR_ALERT_ID +ERROR +BEGIN + TITLE "Oops" + MESSAGE "^1" + BUTTONS "Ok" +END + +#ifdef FOR_GREMLINS + +FORM ID XW_GREMLIN_WARN_FORM_ID AT ( 2 60 156 98 ) +USABLE MODAL +BEGIN + TITLE "Gremlin Oops" + FIELD XW_GREMLIN_WARN_FIELD_ID AT (2 15 150 75) + NONEDITABLE MULTIPLELINES +END + +#endif diff --git a/xwords4/palm/l10n/xwords4_es_CT.rcp.pre b/xwords4/palm/l10n/xwords4_es_CT.rcp.pre new file mode 100644 index 000000000..bf11a0649 --- /dev/null +++ b/xwords4/palm/l10n/xwords4_es_CT.rcp.pre @@ -0,0 +1,414 @@ +/* -*-mode: c; fill-column: 78; -*- */ +/***************************************************************************** + * Copyright 1999 - 2002 by Eric House (xwords@eehouse.org) and others. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + ****************************************************************************/ + +#define DEFINES_ONLY 1 + +#include "xwords4defines.h" + +#define LEFTMARGIN 5 + +MENU XW_MAIN_MENU_ID +BEGIN + PULLDOWN "Fitxer" + BEGIN + MENUITEM "Nova partida..." XW_NEWGAME_PULLDOWN_ID "N" + MENUITEM "Partides..." XW_SAVEDGAMES_PULLDOWN_ID "S" + MENUITEM "Preferències..." XW_PREFS_PULLDOWN_ID "P" + MENUITEM SEPARATOR + MENUITEM "Enviar diccionari" XW_BEAMDICT_PULLDOWN_ID + MENUITEM "Enviar tauler i colors" XW_BEAMBOARD_PULLDOWN_ID + MENUITEM SEPARATOR + MENUITEM "Sobre Crosswords..." XW_ABOUT_PULLDOWN_ID "A" + END + + PULLDOWN "Partida" + BEGIN + MENUITEM "Bossa de fitxes" XW_TILEVALUES_PULLDOWN_ID "V" + MENUITEM "Opcions..." XW_PASSWORDS_PULLDOWN_ID "G" + MENUITEM "Jugades" XW_HISTORY_PULLDOWN_ID "Y" + MENUITEM "Puntuació" XW_FINISH_PULLDOWN_ID "F" +#ifndef XWFEATURE_STANDALONE_ONLY + MENUITEM SEPARATOR + MENUITEM "Reenviar missatge" XW_RESENDIR_PULLDOWN_ID +#endif + END + PULLDOWN "Jugada" + BEGIN + MENUITEM "Pista" XW_HINT_PULLDOWN_ID "I" + MENUITEM "Més pistes" XW_NEXTHINT_PULLDOWN_ID "M" +#ifdef XWFEATURE_SEARCHLIMIT + MENUITEM "Configurat pistes..." XW_HINTCONFIG_PULLDOWN_ID "C" +#endif + MENUITEM SEPARATOR + MENUITEM "Descartar l'actual" XW_UNDOCUR_PULLDOWN_ID "U" + MENUITEM "Descartar la prèvia" XW_UNDOLAST_PULLDOWN_ID "Z" + MENUITEM SEPARATOR + MENUITEM "D'acord" XW_DONE_PULLDOWN_ID "D" + MENUITEM "Remenar el faristol" XW_JUGGLE_PULLDOWN_ID "J" + MENUITEM "Canviar fitxes" XW_TRADEIN_PULLDOWN_ID "T" + MENUITEM "Amagar el faristol" XW_HIDESHOWTRAY_PULLDOWN_ID "H" + END +#ifdef FOR_GREMLINS + PULLDOWN "Grem" + BEGIN + MENUITEM "divider right" XW_GREMLIN_DIVIDER_RIGHT + MENUITEM "divider left" XW_GREMLIN_DIVIDER_LEFT + END +#endif + + +#ifndef FOR_GREMLINS +#ifdef DEBUG + PULLDOWN "DBG" + BEGIN + MENUITEM "Show debugstrs" XW_DEBUGSHOW_PULLDOWN_ID + MENUITEM "Hide debugstrs" XW_DEBUGHIDE_PULLDOWN_ID +/* MENUITEM "Reset game" XW_RESET_PULLDOWN_ID */ + MENUITEM "Network stats..." XW_NETSTATS_PULLDOWN_ID +#ifdef MEM_DEBUG + MENUITEM "Mem stats..." XW_MEMSTATS_PULLDOWN_ID +#endif + END +#endif +#endif /* FOR_GREMLINS */ + +// NO NEED TO TRANSLATE GREMLINS or DEBUG stuff +#ifdef FOR_GREMLINS +/* PULLDOWN "Gremlins" */ +/* BEGIN */ +/* MENUITEM "Divider left" GREMLIN_DIVIDER_LEFT */ +/* MENUITEM "Divider right" GREMLIN_DIVIDER_RIGHT */ +/* END */ +#endif +END + +MENU XW_ASK_MENU_ID +BEGIN + PULLDOWN "Editar" + BEGIN + MENUITEM "Copiar" ASK_COPY_PULLDOWN_ID + MENUITEM "Seleccionar tot" ASK_SELECTALL_PULLDOWN_ID + END +END + +#include "common.rcp.pre" /* these don't need localization */ + +#ifdef XWFEATURE_STANDALONE_ONLY +# define NPLAYERS_TOP 15 +# define FORM_TOP 34 +# define FORM_HEIGHT 124 +#else +# define SERVER_TOP 15 +# define NPLAYERS_TOP (SERVER_TOP+18) +# define FORM_TOP 16 +# define FORM_HEIGHT 142 +#endif +#define LABEL_TOP (NPLAYERS_TOP+18) +#define LEFTCOL 4 +#define REMOTE_COL LEFTCOL +#define NAME_COL 50 +#define ROBOT_COL 98 +#define PASSWD_COL RIGHT@156 + +#ifndef XWFEATURE_STANDALONE_ONLY +#define PLAYER_REMOTECHECK( num, offset ) \ + CHECKBOX "" ID XW_REMOTE_##num##_CHECKBOX_ID \ + AT (LEFTCOL PREVBOTTOM+offset AUTO AUTO) USABLE +#else +#define PLAYER_REMOTECHECK( num, offset ) +#endif + +#ifndef XWFEATURE_STANDALONE_ONLY +#define PLAYER_NAMEFIELD( num, offset ) \ + FIELD XW_PLAYERNAME_##num##_FIELD_ID \ + AT (PREVRIGHT PREVTOP 100 AUTO ) \ + UNDERLINED EDITABLE SINGLELINE MAXCHARS MAX_PLAYERNAME_LENGTH +#else +#define PLAYER_NAMEFIELD( num, offset ) \ + FIELD XW_PLAYERNAME_##num##_FIELD_ID \ + AT (LEFTCOL+10 PREVBOTTOM+offset 100 AUTO ) \ + UNDERLINED EDITABLE SINGLELINE MAXCHARS MAX_PLAYERNAME_LENGTH +#endif + +#define PLAYER_ROBCHECK( num, offset ) \ + CHECKBOX "" ID XW_ROBOT_##num##_CHECKBOX_ID \ + AT (PREVRIGHT PREVTOP AUTO AUTO) USABLE RIGHTANCHOR + +#define PLAYER_PASSFIELD( num, offset ) \ + SELECTORTRIGGER "" XW_PLAYERPASSWD_##num##_TRIGGER_ID \ + AT (PREVRIGHT PREVTOP 12 11) + +/* FIELD XW_PLAYERPASSWD_##num##_FIELD_ID PREVRIGHT PREVTOP 20 \ */ +/* AUTO UNDERLINED EDITABLE SINGLELINE MAXCHARS 4 */ + +#define PLAYER_ROW( num, offset ) \ + PLAYER_REMOTECHECK( num, offset ) \ + PLAYER_NAMEFIELD( num, offset ) \ + PLAYER_ROBCHECK( num, offset ) \ + PLAYER_PASSFIELD( num, offset ) + +//#define SERVER_GROUP_ID 2000 +#define SERVER_HEIGHT 12 +#define PLAYER_SEL_LEFT 64 + +FORM ID XW_NEWGAMES_FORM AT (2 FORM_TOP 156 FORM_HEIGHT) +USABLE MODAL SAVEBEHIND DEFAULTBTNID XW_OK_BUTTON_ID +BEGIN + TITLE "Opcions" + +#ifndef XWFEATURE_STANDALONE_ONLY + LABEL "Connexió:" AUTOID AT (LEFTCOL SERVER_TOP) + GADGET ID XW_SOLO_GADGET_ID AT (PREVRIGHT+2 SERVER_TOP 53 SERVER_HEIGHT) + USABLE + GADGET ID XW_SERVER_GADGET_ID + AT (PREVRIGHT+1 SERVER_TOP 22 SERVER_HEIGHT) USABLE + GADGET ID XW_CLIENT_GADGET_ID + AT (PREVRIGHT+1 SERVER_TOP 28 SERVER_HEIGHT) USABLE + LIST "Sense" "Servidor" "Client" XW_SERVERTYPES_LIST_ID + AT (0 0 1 1) VISIBLEITEMS 3 NONUSABLE +#endif + +/* Pick number of players here */ +#ifndef XWFEATURE_STANDALONE_ONLY + LABEL "Locals: " XW_LOCALP_LABEL_ID AT (LEFTCOL NPLAYERS_TOP) + LABEL "Jugadors: " XW_TOTALP_LABEL_ID AT (LEFTCOL NPLAYERS_TOP) +#else + LABEL "Jugadors: " AUTOID AT (LEFTCOL NPLAYERS_TOP) +#endif + SELECTORTRIGGER "" XW_NPLAYERS_SELECTOR_ID \ + AT (PLAYER_SEL_LEFT NPLAYERS_TOP AUTO AUTO) USABLE LEFTANCHOR + LIST "1" "2" "3" "4" XW_NPLAYERS_LIST_ID AT (PREVLEFT PREVTOP 10 1) \ + VISIBLEITEMS 4 NONUSABLE + + BUTTON "Més opcions..." XW_PREFS_BUTTON_ID RIGHT@154 NPLAYERS_TOP AUTO AUTO + +#ifndef XWFEATURE_STANDALONE_ONLY + LABEL "Client" XW_LOCAL_LABEL_ID REMOTE_COL LABEL_TOP FONT 1 +#endif + LABEL "Nom" AUTOID NAME_COL LABEL_TOP FONT 1 + LABEL "Robot" AUTOID ROBOT_COL LABEL_TOP FONT 1 + LABEL "Pwd" AUTOID PASSWD_COL LABEL_TOP FONT 1 + + PLAYER_ROW( 1, 2 ) + PLAYER_ROW( 2, 2 ) + PLAYER_ROW( 3, 2 ) + PLAYER_ROW( 4, 2 ) + + GRAFFITISTATEINDICATOR 2 PREVBOTTOM+10 + + SELECTORTRIGGER "Diccionari..." XW_DICT_SELECTOR_ID \ + AT (PREVRIGHT+12 PREVTOP AUTO AUTO) USABLE LEFTANCHOR + + BUTTON "D'acord" XW_OK_BUTTON_ID RIGHT@154 PREVTOP 36 AUTO + BUTTON "Sortir" XW_CANCEL_BUTTON_ID RIGHT@PREVLEFT-3 PREVTOP 39 AUTO +END /* FORM XW_PLAYERINFO_FORM */ + +#ifdef FEATURE_TRAY_EDIT +# define TRAY_EDIT_ADJUST 15 +#else +# define TRAY_EDIT_ADJUST 0 +#endif + +#define PREFS_MODE_TOP 15 +#define PREFS_TOP 30 +#define DLG_TOP 41-TRAY_EDIT_ADJUST +#define DLG_HEIGHT 118+TRAY_EDIT_ADJUST +#define TIMER_TOP 87 +#define BUTTON_TOP TIMER_TOP+16+TRAY_EDIT_ADJUST + +/* FORM ID XW_PREFS_FORM AT (2 29 156 130) */ +FORM ID XW_PREFS_FORM AT (2 DLG_TOP 156 DLG_HEIGHT) +USABLE MODAL SAVEBEHIND DEFAULTBTNID XW_PREFS_CANCEL_BUTTON_ID +BEGIN + TITLE "Preferències" + + GADGET ID XW_PREFS_APPWIDE_CHECKBX_ID + AT (LEFTCOL+8 PREFS_MODE_TOP 75 SERVER_HEIGHT) USABLE + GADGET ID XW_PREFS_ONEGAME_CHECKBX_ID + AT (PREVRIGHT+1 PREVTOP 50 SERVER_HEIGHT) USABLE + LIST "Del programa" "De la partida" XW_PREFS_TYPES_LIST_ID + AT (0 0 1 1) VISIBLEITEMS 2 NONUSABLE + + /* global prefs */ + CHECKBOX "Acolorir fitxes segons el jugador" ID XW_PREFS_PLAYERCOLORS_CHECKBOX_ID \ + AT (LEFTCOL PREFS_TOP AUTO AUTO) NONUSABLE + CHECKBOX "Mostrar la barra de progrés" ID XW_PREFS_PROGRESSBAR_CHECKBOX_ID \ + AT (LEFTCOL PREVBOTTOM+2 AUTO AUTO) NONUSABLE + CHECKBOX "Dibuixar el tauler gran" ID XW_PREFS_SHOWGRID_CHECKBOX_ID \ + AT (LEFTCOL PREVBOTTOM+2 AUTO AUTO) NONUSABLE + CHECKBOX "Activar el cursor" ID XW_PREFS_SHOWARROW_CHECKBOX_ID \ + AT (LEFTCOL PREVBOTTOM+2 AUTO AUTO) NONUSABLE + CHECKBOX "Veure jugada del robot" ID XW_PREFS_ROBOTSCORE_CHECKBOX_ID \ + AT (LEFTCOL PREVBOTTOM+2 AUTO AUTO) NONUSABLE + + /* single-game prefs */ + CHECKBOX "El robot mai no falla" ID XW_PREFS_ROBOTSMART_CHECKBOX_ID \ + AT (LEFTCOL PREFS_TOP AUTO AUTO) NONUSABLE + CHECKBOX "Desactivar pistes" ID XW_PREFS_NOHINTS_CHECKBOX_ID \ + AT (LEFTCOL PREVBOTTOM AUTO AUTO) NONUSABLE + + LABEL "Si no troba:" XW_PREFS_PHONIES_LABEL_ID AT (LEFTCOL PREVBOTTOM+2) + POPUPTRIGGER "" ID XW_PREFS_PHONIES_TRIGGER_ID + AT (PREVRIGHT+5 PREVTOP 72 12) LEFTANCHOR + LABEL "Tauler de: " XW_PREFS_BDSIZE_LABEL_ID + AT (LEFTCOL PREVBOTTOM+2) + SELECTORTRIGGER "" XW_PREFS_BDSIZE_SELECTOR_ID \ + AT (PREVRIGHT PREVTOP AUTO AUTO) USABLE LEFTANCHOR + LIST "Ignora" "Avisa" "No permet" ID XW_PREFS_PHONIES_LIST_ID + AT (PREVLEFT PREVTOP 72 12) NONUSABLE VISIBLEITEMS 3 + POPUPLIST XW_PREFS_PHONIES_TRIGGER_ID XW_PREFS_PHONIES_LIST_ID + + LIST "15x15" "13x13" "11x11" \ + XW_PREFS_BDSIZE_LIST_ID AT (PREVLEFT PREVTOP 30 1) \ + NONUSABLE VISIBLEITEMS NUM_BOARD_SIZES + + CHECKBOX "Activar rellotge (minuts): " ID XW_PREFS_TIMERON_CHECKBOX_ID \ + AT (LEFTCOL TIMER_TOP AUTO AUTO) NONUSABLE + FIELD XW_PREFS_TIMER_FIELD_ID PREVRIGHT+5 PREVTOP 15 AUTO UNDERLINED \ + EDITABLE SINGLELINE NUMERIC MAXCHARS 3 + +#ifdef FEATURE_TRAY_EDIT + CHECKBOX "Faristol editable" ID XW_PREFS_PICKTILES_CHECKBOX_ID \ + AT (LEFTCOL PREVBOTTOM AUTO AUTO) NONUSABLE +#endif + + /* buttons at the bottom */ + BUTTON "Sortir" XW_PREFS_CANCEL_BUTTON_ID 35 BUTTON_TOP AUTO AUTO + BUTTON "D'acord" XW_PREFS_OK_BUTTON_ID PREVRIGHT+10 PREVTOP AUTO AUTO +END + +#define LEFT_EDGE 10 +FORM ID XW_DICTINFO_FORM AT (2 111 156 47) +USABLE MODAL DEFAULTBTNID XW_DICTINFO_CANCEL_BUTTON_ID +BEGIN + TITLE "Diccionaris" + + LABEL "Nom:" AUTOID AT (LEFT_EDGE 15) + POPUPTRIGGER "" ID XW_DICTINFO_TRIGGER_ID + AT (PREVRIGHT+5 PREVTOP 72 12) LEFTANCHOR + LIST "" ID XW_DICTINFO_LIST_ID AT (PREVLEFT PREVTOP 72 1) + NONUSABLE VISIBLEITEMS 4 + POPUPLIST XW_DICTINFO_TRIGGER_ID XW_DICTINFO_LIST_ID + + BUTTON "D'acord" XW_DICTINFO_DONE_BUTTON_ID 25 31 AUTO AUTO + BUTTON "Enviar" XW_DICTINFO_BEAM_BUTTON_ID 22 PREVTOP AUTO AUTO + BUTTON "Sortir" XW_DICTINFO_CANCEL_BUTTON_ID PREVRIGHT+20 PREVTOP + AUTO AUTO +END + +FORM ID XW_ASK_FORM_ID AT (2 70 156 88) +USABLE MODAL SAVEBEHIND DEFAULTBTNID XW_ASK_NO_BUTTON_ID +MENUID XW_ASK_MENU_ID +BEGIN + TITLE "Pregunta" + + FIELD XW_ASK_TXT_FIELD_ID LEFT_EDGE-5 15 135 52 \ + NONEDITABLE MULTIPLELINES + + SCROLLBAR ID XW_ASK_SCROLLBAR_ID + AT ( PREVRIGHT+2 PREVTOP RECOMMENDED_SBAR_WIDTH + PREVBOTTOM - PREVTOP) USABLE + + BUTTON "D'acord" XW_ASK_YES_BUTTON_ID RIGHT@(156/2)-10 PREVBOTTOM+2 AUTO AUTO + BUTTON "Sortir" XW_ASK_NO_BUTTON_ID 156/2+10 PREVTOP + AUTO AUTO +END + +FORM ID XW_PASSWORD_DIALOG_ID AT ( 2 88 156 70 ) + MODAL SAVEBEHIND DEFAULTBTNID XW_PASSWORD_CANCEL_BUTTON +BEGIN + TITLE "Password" + + LABEL "Entra el password per:" XW_PASSWORD_NAME_LABEL 10 18 FONT 1 NONUSABLE + LABEL "Entra el nou password per:" XW_PASSWORD_NEWNAME_LABEL 10 18 FONT 1 + NONUSABLE + FIELD XW_PASSWORD_NAME_FIELD AT (PREVLEFT PREVBOTTOM+3 90 12) NONEDITABLE + SINGLELINE MAXCHARS MAX_PLAYERNAME_LENGTH + FIELD XW_PASSWORD_PASS_FIELD \ + AT (PREVRIGHT+10 PREVTOP MAX_PASSWORD_LENGTH*6 12) \ + EDITABLE SINGLELINE UNDERLINED MAXCHARS MAX_PASSWORD_LENGTH + GRAFFITISTATEINDICATOR 2 PREVBOTTOM+8 + BUTTON "D'acord" XW_PASSWORD_OK_BUTTON 45 PREVTOP AUTO AUTO + BUTTON "Sortir" XW_PASSWORD_CANCEL_BUTTON PREVRIGHT+10 PREVTOP AUTO AUTO +END + +#define BLANK_PICK_TOP 15 + +FORM ID XW_BLANK_DIALOG_ID AT ( 2 74 156 83 ) USABLE + MODAL SAVEBEHIND +#ifdef FEATURE_TRAY_EDIT + DEFAULTBTNID XW_BLANK_PICK_BUTTON_ID +#endif +BEGIN + TITLE "Editar el faristol" + + FIELD XW_BLANK_LABEL_FIELD_ID AT (10 BLANK_PICK_TOP 110 39) + NONEDITABLE MULTIPLELINES + + LIST "" ID XW_BLANK_LIST_ID AT (PREVRIGHT+2 BLANK_PICK_TOP 28 72) + USABLE VISIBLEITEMS 6 + + BUTTON "D'acord" XW_BLANK_OK_BUTTON_ID RIGHT@PREVLEFT-10 65 42 AUTO + +#ifdef FEATURE_TRAY_EDIT + BUTTON "Tot!" XW_BLANK_PICK_BUTTON_ID 5 65 32 AUTO + BUTTON "Esb." XW_BLANK_BACKUP_BUTTON_ID PREVRIGHT+3 PREVTOP 23 AUTO +#endif +END + +#if defined OWNER_HASH || defined NO_REG_REQUIRED +FORM ID XW_SAVEDGAMES_DIALOG_ID AT ( 2 2 156 156 ) +USABLE MODAL DEFAULTBTNID XW_SAVEDGAMES_DONE_BUTTON +BEGIN + TITLE "Partides" + + LIST "" ID XW_SAVEDGAMES_LIST_ID AT (2 15 140 60) \ + USABLE ENABLED VISIBLEITEMS 1 + GRAFFITISTATEINDICATOR 2 120 + FIELD XW_SAVEDGAMES_NAME_FIELD AT (PREVRIGHT+10 PREVTOP 108 AUTO) + EDITABLE SINGLELINE UNDERLINED MAXCHARS MAX_GAMENAME_LENGTH + + BUTTON "Modif." XW_SAVEDGAMES_USE_BUTTON RIGHT@154 PREVTOP 30 AUTO + + BUTTON "Duplic." XW_SAVEDGAMES_DUPE_BUTTON 2 PREVBOTTOM+5 33 AUTO + BUTTON "Esborrar" XW_SAVEDGAMES_DELETE_BUTTON PREVRIGHT+5 PREVTOP 41 AUTO + BUTTON "Triar" XW_SAVEDGAMES_OPEN_BUTTON PREVRIGHT+5 PREVTOP 28 AUTO + BUTTON "Tancar" XW_SAVEDGAMES_DONE_BUTTON PREVRIGHT+5 PREVTOP 33 AUTO +END +#endif + +ALERT XW_ERROR_ALERT_ID +ERROR +BEGIN + TITLE "Alerta!" + MESSAGE "^1" + BUTTONS "D'acord" +END + +#ifdef FOR_GREMLINS + +FORM ID XW_GREMLIN_WARN_FORM_ID AT ( 2 60 156 98 ) +USABLE MODAL +BEGIN + TITLE "Gremlin Oops" + FIELD XW_GREMLIN_WARN_FIELD_ID AT (2 15 150 75) + NONEDITABLE MULTIPLELINES +END + +#endif diff --git a/xwords4/palm/l10n/xwords4_es_ES.rcp.pre b/xwords4/palm/l10n/xwords4_es_ES.rcp.pre new file mode 100644 index 000000000..fa637bfd4 --- /dev/null +++ b/xwords4/palm/l10n/xwords4_es_ES.rcp.pre @@ -0,0 +1,413 @@ +/* -*-mode: c; fill-column: 78; -*- */ +/***************************************************************************** + * Copyright 1999 - 2002 by Eric House (xwords@eehouse.org) and others. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + ****************************************************************************/ + +#define DEFINES_ONLY 1 + +#include "xwords4defines.h" + +#define LEFTMARGIN 5 + +MENU XW_MAIN_MENU_ID +BEGIN + PULLDOWN "Fichero" + BEGIN + MENUITEM "Nueva partida..." XW_NEWGAME_PULLDOWN_ID "N" + MENUITEM "Partidas..." XW_SAVEDGAMES_PULLDOWN_ID "S" + MENUITEM "Preferencias..." XW_PREFS_PULLDOWN_ID "P" + MENUITEM SEPARATOR + MENUITEM "Enviar diccionario" XW_BEAMDICT_PULLDOWN_ID + MENUITEM "Enviar tablero y colores" XW_BEAMBOARD_PULLDOWN_ID + MENUITEM SEPARATOR + MENUITEM "Acerca de Crosswords..." XW_ABOUT_PULLDOWN_ID "A" + END + + PULLDOWN "Partida" + BEGIN + MENUITEM "Bolsa de fichas" XW_TILEVALUES_PULLDOWN_ID "V" + MENUITEM "Opciones..." XW_PASSWORDS_PULLDOWN_ID "G" + MENUITEM "Jugadas" XW_HISTORY_PULLDOWN_ID "Y" + MENUITEM "Puntuación" XW_FINISH_PULLDOWN_ID "F" +#ifndef XWFEATURE_STANDALONE_ONLY + MENUITEM SEPARATOR + MENUITEM "Reenviar mensajes" XW_RESENDIR_PULLDOWN_ID +#endif + END + PULLDOWN "Jugada" + BEGIN + MENUITEM "Pista" XW_HINT_PULLDOWN_ID "I" + MENUITEM "Más pistas" XW_NEXTHINT_PULLDOWN_ID "M" +#ifdef XWFEATURE_SEARCHLIMIT + MENUITEM "Configurar pistas..." XW_HINTCONFIG_PULLDOWN_ID "C" +#endif + MENUITEM SEPARATOR + MENUITEM "Descartar la actual" XW_UNDOCUR_PULLDOWN_ID "U" + MENUITEM "Descartar la previa" XW_UNDOLAST_PULLDOWN_ID "Z" + MENUITEM SEPARATOR + MENUITEM "Aceptar" XW_DONE_PULLDOWN_ID "D" + MENUITEM "Remover el atril" XW_JUGGLE_PULLDOWN_ID "J" + MENUITEM "Cambiar fichas" XW_TRADEIN_PULLDOWN_ID "T" + MENUITEM "Ocultar el atril" XW_HIDESHOWTRAY_PULLDOWN_ID "H" + END +#ifdef FOR_GREMLINS + PULLDOWN "Grem" + BEGIN + MENUITEM "divider right" XW_GREMLIN_DIVIDER_RIGHT + MENUITEM "divider left" XW_GREMLIN_DIVIDER_LEFT + END +#endif + + +#ifndef FOR_GREMLINS +#ifdef DEBUG + PULLDOWN "DBG" + BEGIN + MENUITEM "Show debugstrs" XW_DEBUGSHOW_PULLDOWN_ID + MENUITEM "Hide debugstrs" XW_DEBUGHIDE_PULLDOWN_ID + MENUITEM "Network stats..." XW_NETSTATS_PULLDOWN_ID +#ifdef MEM_DEBUG + MENUITEM "Mem stats..." XW_MEMSTATS_PULLDOWN_ID +#endif + END +#endif +#endif /* FOR_GREMLINS */ + +// NO NEED TO TRANSLATE GREMLINS or DEBUG stuff +#ifdef FOR_GREMLINS +/* PULLDOWN "Gremlins" */ +/* BEGIN */ +/* MENUITEM "Divider left" GREMLIN_DIVIDER_LEFT */ +/* MENUITEM "Divider right" GREMLIN_DIVIDER_RIGHT */ +/* END */ +#endif +END + +MENU XW_ASK_MENU_ID +BEGIN + PULLDOWN "Editar" + BEGIN + MENUITEM "Copiar" ASK_COPY_PULLDOWN_ID + MENUITEM "Seleccionar todo" ASK_SELECTALL_PULLDOWN_ID + END +END + +#include "common.rcp.pre" /* these don't need localization */ + +#ifdef XWFEATURE_STANDALONE_ONLY +# define NPLAYERS_TOP 15 +# define FORM_TOP 34 +# define FORM_HEIGHT 124 +#else +# define SERVER_TOP 15 +# define NPLAYERS_TOP (SERVER_TOP+18) +# define FORM_TOP 16 +# define FORM_HEIGHT 142 +#endif +#define LABEL_TOP (NPLAYERS_TOP+18) +#define LEFTCOL 4 +#define REMOTE_COL LEFTCOL +#define NAME_COL 50 +#define ROBOT_COL 98 +#define PASSWD_COL RIGHT@156 + +#ifndef XWFEATURE_STANDALONE_ONLY +#define PLAYER_REMOTECHECK( num, offset ) \ + CHECKBOX "" ID XW_REMOTE_##num##_CHECKBOX_ID \ + AT (LEFTCOL PREVBOTTOM+offset AUTO AUTO) USABLE +#else +#define PLAYER_REMOTECHECK( num, offset ) +#endif + +#ifndef XWFEATURE_STANDALONE_ONLY +#define PLAYER_NAMEFIELD( num, offset ) \ + FIELD XW_PLAYERNAME_##num##_FIELD_ID \ + AT (PREVRIGHT PREVTOP 100 AUTO ) \ + UNDERLINED EDITABLE SINGLELINE MAXCHARS MAX_PLAYERNAME_LENGTH +#else +#define PLAYER_NAMEFIELD( num, offset ) \ + FIELD XW_PLAYERNAME_##num##_FIELD_ID \ + AT (LEFTCOL+10 PREVBOTTOM+offset 100 AUTO ) \ + UNDERLINED EDITABLE SINGLELINE MAXCHARS MAX_PLAYERNAME_LENGTH +#endif + +#define PLAYER_ROBCHECK( num, offset ) \ + CHECKBOX "" ID XW_ROBOT_##num##_CHECKBOX_ID \ + AT (PREVRIGHT PREVTOP AUTO AUTO) USABLE RIGHTANCHOR + +#define PLAYER_PASSFIELD( num, offset ) \ + SELECTORTRIGGER "" XW_PLAYERPASSWD_##num##_TRIGGER_ID \ + AT (PREVRIGHT PREVTOP 12 11) + +/* FIELD XW_PLAYERPASSWD_##num##_FIELD_ID PREVRIGHT PREVTOP 20 \ */ +/* AUTO UNDERLINED EDITABLE SINGLELINE MAXCHARS 4 */ + +#define PLAYER_ROW( num, offset ) \ + PLAYER_REMOTECHECK( num, offset ) \ + PLAYER_NAMEFIELD( num, offset ) \ + PLAYER_ROBCHECK( num, offset ) \ + PLAYER_PASSFIELD( num, offset ) + +//#define SERVER_GROUP_ID 2000 +#define SERVER_HEIGHT 12 +#define PLAYER_SEL_LEFT 64 + +FORM ID XW_NEWGAMES_FORM AT (2 FORM_TOP 156 FORM_HEIGHT) +USABLE MODAL SAVEBEHIND DEFAULTBTNID XW_OK_BUTTON_ID +BEGIN + TITLE "Opciones" + +#ifndef XWFEATURE_STANDALONE_ONLY + LABEL "Conexión:" AUTOID AT (LEFTCOL SERVER_TOP) + GADGET ID XW_SOLO_GADGET_ID AT (PREVRIGHT+2 SERVER_TOP 53 SERVER_HEIGHT) + USABLE + GADGET ID XW_SERVER_GADGET_ID + AT (PREVRIGHT+1 SERVER_TOP 22 SERVER_HEIGHT) USABLE + GADGET ID XW_CLIENT_GADGET_ID + AT (PREVRIGHT+1 SERVER_TOP 28 SERVER_HEIGHT) USABLE + LIST "Sin" "Servidor" "Cliente" XW_SERVERTYPES_LIST_ID + AT (0 0 1 1) VISIBLEITEMS 3 NONUSABLE +#endif + +/* Pick number of players here */ +#ifndef XWFEATURE_STANDALONE_ONLY + LABEL "Locales: " XW_LOCALP_LABEL_ID AT (LEFTCOL NPLAYERS_TOP) + LABEL "Jugadores: " XW_TOTALP_LABEL_ID AT (LEFTCOL NPLAYERS_TOP) +#else + LABEL "Jugadores: " AUTOID AT (LEFTCOL NPLAYERS_TOP) +#endif + SELECTORTRIGGER "" XW_NPLAYERS_SELECTOR_ID \ + AT (PLAYER_SEL_LEFT NPLAYERS_TOP AUTO AUTO) USABLE LEFTANCHOR + LIST "1" "2" "3" "4" XW_NPLAYERS_LIST_ID AT (PREVLEFT PREVTOP 10 1) \ + VISIBLEITEMS 4 NONUSABLE + + BUTTON "Más opciones..." XW_PREFS_BUTTON_ID RIGHT@154 NPLAYERS_TOP AUTO AUTO + +#ifndef XWFEATURE_STANDALONE_ONLY + LABEL "Cliente" XW_LOCAL_LABEL_ID REMOTE_COL LABEL_TOP FONT 1 +#endif + LABEL "Nombre" AUTOID NAME_COL LABEL_TOP FONT 1 + LABEL "Robot" AUTOID ROBOT_COL LABEL_TOP FONT 1 + LABEL "Pwd" AUTOID PASSWD_COL LABEL_TOP FONT 1 + + PLAYER_ROW( 1, 2 ) + PLAYER_ROW( 2, 2 ) + PLAYER_ROW( 3, 2 ) + PLAYER_ROW( 4, 2 ) + + GRAFFITISTATEINDICATOR 2 PREVBOTTOM+10 + + SELECTORTRIGGER "Diccionario..." XW_DICT_SELECTOR_ID \ + AT (PREVRIGHT+12 PREVTOP AUTO AUTO) USABLE LEFTANCHOR + + BUTTON "Aceptar" XW_OK_BUTTON_ID RIGHT@154 PREVTOP 36 AUTO + BUTTON "Cancelar" XW_CANCEL_BUTTON_ID RIGHT@PREVLEFT-3 PREVTOP 37 AUTO +END /* FORM XW_PLAYERINFO_FORM */ + +#ifdef FEATURE_TRAY_EDIT +# define TRAY_EDIT_ADJUST 15 +#else +# define TRAY_EDIT_ADJUST 0 +#endif + +#define PREFS_MODE_TOP 15 +#define PREFS_TOP 30 +#define DLG_TOP 39-TRAY_EDIT_ADJUST +#define DLG_HEIGHT 120+TRAY_EDIT_ADJUST +#define TIMER_TOP 85 +#define BUTTON_TOP 104+TRAY_EDIT_ADJUST + +FORM ID XW_PREFS_FORM AT (2 DLG_TOP 156 DLG_HEIGHT) +USABLE MODAL SAVEBEHIND DEFAULTBTNID XW_PREFS_CANCEL_BUTTON_ID +BEGIN + TITLE "Preferencias" + + GADGET ID XW_PREFS_APPWIDE_CHECKBX_ID + AT (LEFTCOL+8 PREFS_MODE_TOP 75 SERVER_HEIGHT) USABLE + GADGET ID XW_PREFS_ONEGAME_CHECKBX_ID + AT (PREVRIGHT+1 PREVTOP 50 SERVER_HEIGHT) USABLE + LIST "Del programa" "De la partida" XW_PREFS_TYPES_LIST_ID + AT (0 0 1 1) VISIBLEITEMS 2 NONUSABLE + + /* global prefs */ + CHECKBOX "Colorear fichas según el jugador" \ + ID XW_PREFS_PLAYERCOLORS_CHECKBOX_ID \ + AT (LEFTCOL PREFS_TOP AUTO AUTO) NONUSABLE + CHECKBOX "Mostrar la barra de progreso" \ + ID XW_PREFS_PROGRESSBAR_CHECKBOX_ID \ + AT (LEFTCOL PREVBOTTOM+2 AUTO AUTO) NONUSABLE + CHECKBOX "Dibujar el tablero grande" ID XW_PREFS_SHOWGRID_CHECKBOX_ID \ + AT (LEFTCOL PREVBOTTOM+2 AUTO AUTO) NONUSABLE + CHECKBOX "Activar cursor" ID XW_PREFS_SHOWARROW_CHECKBOX_ID \ + AT (LEFTCOL PREVBOTTOM+2 AUTO AUTO) NONUSABLE + CHECKBOX "Ver jugada del robot" ID XW_PREFS_ROBOTSCORE_CHECKBOX_ID \ + AT (LEFTCOL PREVBOTTOM+2 AUTO AUTO) NONUSABLE + + /* single-game prefs */ + CHECKBOX "El robot nunca falla" ID XW_PREFS_ROBOTSMART_CHECKBOX_ID \ + AT (LEFTCOL PREFS_TOP AUTO AUTO) NONUSABLE + CHECKBOX "Desactivar pistas" ID XW_PREFS_NOHINTS_CHECKBOX_ID \ + AT (LEFTCOL PREVBOTTOM AUTO AUTO) NONUSABLE + + LABEL "Si no encuentra:" XW_PREFS_PHONIES_LABEL_ID AT (LEFTCOL PREVBOTTOM+2) + POPUPTRIGGER "" ID XW_PREFS_PHONIES_TRIGGER_ID \ + AT (PREVRIGHT+5 PREVTOP 72 12) LEFTANCHOR + LABEL "Tablero de: " XW_PREFS_BDSIZE_LABEL_ID \ + AT (LEFTCOL PREVBOTTOM+2) + SELECTORTRIGGER "" XW_PREFS_BDSIZE_SELECTOR_ID \ + AT (PREVRIGHT PREVTOP AUTO AUTO) USABLE LEFTANCHOR + LIST "Ignora" "Avisa" "No permite" ID XW_PREFS_PHONIES_LIST_ID \ + AT (PREVLEFT PREVTOP 72 12) NONUSABLE VISIBLEITEMS 3 + POPUPLIST XW_PREFS_PHONIES_TRIGGER_ID XW_PREFS_PHONIES_LIST_ID + + LIST "15x15" "13x13" "11x11" \ + XW_PREFS_BDSIZE_LIST_ID AT (PREVLEFT PREVTOP 30 1) \ + NONUSABLE VISIBLEITEMS NUM_BOARD_SIZES + + CHECKBOX "Activar reloj (minutos): " ID XW_PREFS_TIMERON_CHECKBOX_ID \ + AT (LEFTCOL TIMER_TOP AUTO AUTO) NONUSABLE + FIELD XW_PREFS_TIMER_FIELD_ID PREVRIGHT+5 PREVTOP 15 AUTO UNDERLINED \ + EDITABLE SINGLELINE NUMERIC MAXCHARS 3 + +#ifdef FEATURE_TRAY_EDIT + CHECKBOX "Atril editable" ID XW_PREFS_PICKTILES_CHECKBOX_ID \ + AT (LEFTCOL PREVBOTTOM AUTO AUTO) NONUSABLE +#endif + + /* buttons at the bottom */ + BUTTON "Cancelar" XW_PREFS_CANCEL_BUTTON_ID 30 BUTTON_TOP AUTO AUTO + BUTTON "Aceptar" XW_PREFS_OK_BUTTON_ID PREVRIGHT+10 PREVTOP AUTO AUTO +END + +#define LEFT_EDGE 10 +FORM ID XW_DICTINFO_FORM AT (2 111 156 47) +USABLE MODAL DEFAULTBTNID XW_DICTINFO_CANCEL_BUTTON_ID +BEGIN + TITLE "Diccionarios" + + LABEL "Nombre:" AUTOID AT (LEFT_EDGE 15) + POPUPTRIGGER "" ID XW_DICTINFO_TRIGGER_ID + AT (PREVRIGHT+5 PREVTOP 72 12) LEFTANCHOR + LIST "" ID XW_DICTINFO_LIST_ID AT (PREVLEFT PREVTOP 72 1) + NONUSABLE VISIBLEITEMS 4 + POPUPLIST XW_DICTINFO_TRIGGER_ID XW_DICTINFO_LIST_ID + + BUTTON "Aceptar" XW_DICTINFO_DONE_BUTTON_ID 25 31 AUTO AUTO + BUTTON "Enviar" XW_DICTINFO_BEAM_BUTTON_ID 22 PREVTOP AUTO AUTO + BUTTON "Cancelar" XW_DICTINFO_CANCEL_BUTTON_ID PREVRIGHT+20 PREVTOP + AUTO AUTO +END + +FORM ID XW_ASK_FORM_ID AT (2 70 156 88) +USABLE MODAL SAVEBEHIND DEFAULTBTNID XW_ASK_NO_BUTTON_ID +MENUID XW_ASK_MENU_ID +BEGIN + TITLE "Pregunta" + + FIELD XW_ASK_TXT_FIELD_ID LEFT_EDGE-5 16 135 52 \ + NONEDITABLE MULTIPLELINES + + SCROLLBAR ID XW_ASK_SCROLLBAR_ID + AT ( PREVRIGHT+2 PREVTOP RECOMMENDED_SBAR_WIDTH + PREVBOTTOM - PREVTOP) USABLE + + BUTTON "Aceptar" XW_ASK_YES_BUTTON_ID + RIGHT@(156/2)-10 PREVBOTTOM+5 AUTO AUTO + BUTTON "Cancelar" XW_ASK_NO_BUTTON_ID 156/2+10 PREVTOP + AUTO AUTO +END + +FORM ID XW_PASSWORD_DIALOG_ID AT ( 2 88 156 70 ) + MODAL SAVEBEHIND DEFAULTBTNID XW_PASSWORD_CANCEL_BUTTON +BEGIN + TITLE "Password" + + LABEL "Entra el password para:" XW_PASSWORD_NAME_LABEL 10 18 FONT 1 NONUSABLE + LABEL "Entra el nuevo password para:" XW_PASSWORD_NEWNAME_LABEL 10 18 FONT 1 + NONUSABLE + FIELD XW_PASSWORD_NAME_FIELD AT (PREVLEFT PREVBOTTOM+3 90 12) NONEDITABLE + SINGLELINE MAXCHARS MAX_PLAYERNAME_LENGTH + FIELD XW_PASSWORD_PASS_FIELD \ + AT (PREVRIGHT+10 PREVTOP MAX_PASSWORD_LENGTH*6 12) \ + EDITABLE SINGLELINE UNDERLINED MAXCHARS MAX_PASSWORD_LENGTH + GRAFFITISTATEINDICATOR 2 PREVBOTTOM+8 + BUTTON "Aceptar" XW_PASSWORD_OK_BUTTON 45 PREVTOP AUTO AUTO + BUTTON "Cancelar" XW_PASSWORD_CANCEL_BUTTON PREVRIGHT+10 PREVTOP AUTO AUTO +END + +#define BLANK_PICK_TOP 15 + +FORM ID XW_BLANK_DIALOG_ID AT ( 2 74 156 83 ) USABLE + MODAL SAVEBEHIND +#ifdef FEATURE_TRAY_EDIT + DEFAULTBTNID XW_BLANK_PICK_BUTTON_ID +#endif +BEGIN + TITLE "Editar el atril" + + FIELD XW_BLANK_LABEL_FIELD_ID AT (10 BLANK_PICK_TOP 110 39) + NONEDITABLE MULTIPLELINES + + LIST "" ID XW_BLANK_LIST_ID AT (PREVRIGHT+2 BLANK_PICK_TOP 28 72) + USABLE VISIBLEITEMS 6 + + BUTTON "Aceptar" XW_BLANK_OK_BUTTON_ID RIGHT@PREVLEFT-5 65 40 AUTO + +#ifdef FEATURE_TRAY_EDIT + BUTTON "¡Todo!" XW_BLANK_PICK_BUTTON_ID 3 65 34 AUTO + BUTTON "Borr." XW_BLANK_BACKUP_BUTTON_ID PREVRIGHT+3 PREVTOP 29 AUTO +#endif +END + +#if defined OWNER_HASH || defined NO_REG_REQUIRED +FORM ID XW_SAVEDGAMES_DIALOG_ID AT ( 2 2 156 156 ) +USABLE MODAL DEFAULTBTNID XW_SAVEDGAMES_DONE_BUTTON +BEGIN + TITLE "Partidas" + + LIST "" ID XW_SAVEDGAMES_LIST_ID AT (2 15 140 60) \ + USABLE ENABLED VISIBLEITEMS 1 + GRAFFITISTATEINDICATOR 2 120 + FIELD XW_SAVEDGAMES_NAME_FIELD AT (PREVRIGHT+10 PREVTOP 108 AUTO) + EDITABLE SINGLELINE UNDERLINED MAXCHARS MAX_GAMENAME_LENGTH + + BUTTON "Modif." XW_SAVEDGAMES_USE_BUTTON RIGHT@154 PREVTOP 30 AUTO + + BUTTON "Duplic." XW_SAVEDGAMES_DUPE_BUTTON 2 PREVBOTTOM+5 33 AUTO + BUTTON "Borrar" XW_SAVEDGAMES_DELETE_BUTTON PREVRIGHT+5 PREVTOP 33 AUTO + BUTTON "Escoger" XW_SAVEDGAMES_OPEN_BUTTON PREVRIGHT+5 PREVTOP 37 AUTO + BUTTON "Cerrar" XW_SAVEDGAMES_DONE_BUTTON PREVRIGHT+5 PREVTOP 33 AUTO +END +#endif + +ALERT XW_ERROR_ALERT_ID +ERROR +BEGIN + TITLE "¡Atención!" + MESSAGE "^1" + BUTTONS "De acuerdo" +END + +#ifdef FOR_GREMLINS +FORM ID XW_GREMLIN_WARN_FORM_ID AT ( 2 60 156 98 ) +USABLE MODAL +BEGIN + TITLE "Gremlin Oops" + FIELD XW_GREMLIN_WARN_FIELD_ID AT (2 15 150 75) + NONEDITABLE MULTIPLELINES +END +#endif diff --git a/xwords4/palm/l10n/xwords4_fr_FR.rcp.pre b/xwords4/palm/l10n/xwords4_fr_FR.rcp.pre new file mode 100644 index 000000000..80fb1ead2 --- /dev/null +++ b/xwords4/palm/l10n/xwords4_fr_FR.rcp.pre @@ -0,0 +1,621 @@ +/* -*-mode: c; fill-column: 78; compile-command: "cd ../ && make ARCH=68K_ONLY MEMDEBUG=TRUE LANG=fr_FR"; -*- */ +/***************************************************************************** + * Copyright 1999 - 2007 par Eric House (xwords@eehouse.org) et autres. Tous + * droits réservés. Tradtion en français par Francis H. + * + * Ce programm est libre de droits ; Vous pouvez le distribuer et/ou + * le modifier en respectant les termes de la licence GNU General Public + * ainsi qu'elle est publiée par la Free Software Foundation ; que ce soit la version 2 + * de la Licence, ou (selon votre option), toute version ultérieure. + * + * Ce programme est distribué avec l'intention qu'il soit utilisé, + * mais SANS AUCUNE GARANTIE ; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Voir la + * Licence GNU General Public pour plus de détails. + * + * Vous devriez avoir reçu une copie de la Licence GNU General Public + * avec ce programme ; si ce n'est pas le cas, écrivez à "Free Software + * Foundation", Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + ****************************************************************************/ + +#define DEFINES_ONLY 1 + +#include "xwords4defines.h" + +#define LEFTMARGIN 5 + +MENU XW_MAIN_MENU_ID +BEGIN + PULLDOWN "Fichier" + BEGIN + MENUITEM "Nouvelle partie..." XW_NEWGAME_PULLDOWN_ID "N" + MENUITEM "Parties sauvegardées..." XW_SAVEDGAMES_PULLDOWN_ID "S" + MENUITEM "Préférences..." XW_PREFS_PULLDOWN_ID "P" + MENUITEM SEPARATOR + MENUITEM "Transm. dictionnaire" XW_BEAMDICT_PULLDOWN_ID + MENUITEM "Transm. grilles et couleurs" XW_BEAMBOARD_PULLDOWN_ID +#ifdef FEATURE_DUALCHOOSE + MENUITEM SEPARATOR + MENUITEM "Ouvrir vers. 68K" XW_RUN68K_PULLDOWN_ID + MENUITEM "Ouvrir vers. ARM" XW_RUNARM_PULLDOWN_ID +#endif + MENUITEM SEPARATOR + MENUITEM "Au sujet de Crosswords..." XW_ABOUT_PULLDOWN_ID "A" + END + + PULLDOWN "Partie" + BEGIN + MENUITEM "Valeur des lettres" XW_TILEVALUES_PULLDOWN_ID "V" + MENUITEM "Lettres restantes" XW_TILESLEFT_PULLDOWN_ID "R" + MENUITEM "Info partie courante..." XW_PASSWORDS_PULLDOWN_ID "G" + MENUITEM "Historique" XW_HISTORY_PULLDOWN_ID "Y" + MENUITEM "Score final" XW_FINISH_PULLDOWN_ID "F" +#ifndef XWFEATURE_STANDALONE_ONLY + MENUITEM SEPARATOR + MENUITEM "Renvoyer" XW_RESENDIR_PULLDOWN_ID +#endif + END + PULLDOWN "Coup" + BEGIN + MENUITEM "Indice" XW_HINT_PULLDOWN_ID "I" +#ifdef XWFEATURE_SEARCHLIMIT + MENUITEM "Indice restreint" XW_HINTCONFIG_PULLDOWN_ID "C" + MENUITEM SEPARATOR +#endif + MENUITEM "Indice suivant" XW_NEXTHINT_PULLDOWN_ID "M" + MENUITEM SEPARATOR + MENUITEM "Annuler actuel" XW_UNDOCUR_PULLDOWN_ID "U" + MENUITEM "Annuler dernier" XW_UNDOLAST_PULLDOWN_ID "Z" + MENUITEM SEPARATOR + MENUITEM "Fait" XW_DONE_PULLDOWN_ID "D" + MENUITEM "Mélanger" XW_JUGGLE_PULLDOWN_ID "J" + MENUITEM "Echanger lettres" XW_TRADEIN_PULLDOWN_ID "T" + MENUITEM "[dé]Cacher tableau" XW_HIDESHOWTRAY_PULLDOWN_ID "H" + END +#ifdef FOR_GREMLINS + PULLDOWN "Grem" + BEGIN + MENUITEM "divider right" XW_GREMLIN_DIVIDER_RIGHT + MENUITEM "divider left" XW_GREMLIN_DIVIDER_LEFT + END +#endif + + +#ifndef FOR_GREMLINS +#ifdef DEBUG + PULLDOWN "DBG" + BEGIN + MENUITEM "Log to file" XW_LOGFILE_PULLDOWN_ID + MENUITEM "Log to memo" XW_LOGMEMO_PULLDOWN_ID + MENUITEM "Clear logs" XW_CLEARLOGS_PULLDOWN_ID + MENUITEM "Network stats..." XW_NETSTATS_PULLDOWN_ID +#ifdef DEBUG + MENUITEM "BT stats..." XW_BTSTATS_PULLDOWN_ID +#endif +#ifdef MEM_DEBUG + MENUITEM "Mem stats..." XW_MEMSTATS_PULLDOWN_ID +#endif + END +#endif +#endif /* FOR_GREMLINS */ + +END + +MENU XW_ASK_MENU_ID +BEGIN + PULLDOWN "Editer" + BEGIN + MENUITEM "Copier" ASK_COPY_PULLDOWN_ID + MENUITEM "Tout sélectionner" ASK_SELECTALL_PULLDOWN_ID + END +END + +#include "common.rcp.pre" /* these don't need localization */ + +#ifdef XWFEATURE_STANDALONE_ONLY +# define NPLAYERS_TOP 15 +# define FORM_TOP 34 +# define FORM_HEIGHT 124 +#else +# define SERVER_TOP 15 +# define NPLAYERS_TOP (SERVER_TOP+18) +# define FORM_TOP 16 +# define FORM_HEIGHT 142 +#endif +#define LABEL_TOP (NPLAYERS_TOP+18) +#define LEFTCOL 4 +#define REMOTE_COL LEFTCOL +#define NAME_COL 50 +#define ROBOT_COL 98 +#define PASSWD_COL RIGHT@156 + +#ifndef XWFEATURE_STANDALONE_ONLY +#define PLAYER_REMOTECHECK( num, offset ) \ + CHECKBOX "" ID XW_REMOTE_##num##_CHECKBOX_ID \ + AT (LEFTCOL PREVBOTTOM+offset AUTO AUTO) USABLE +#else +#define PLAYER_REMOTECHECK( num, offset ) +#endif + +#ifndef XWFEATURE_STANDALONE_ONLY +#define PLAYER_NAMEFIELD( num, offset ) \ + FIELD XW_PLAYERNAME_##num##_FIELD_ID \ + AT (PREVRIGHT PREVTOP 100 AUTO ) \ + UNDERLINED EDITABLE SINGLELINE MAXCHARS MAX_PLAYERNAME_LENGTH +#else +#define PLAYER_NAMEFIELD( num, offset ) \ + FIELD XW_PLAYERNAME_##num##_FIELD_ID \ + AT (LEFTCOL+10 PREVBOTTOM+offset 100 AUTO ) \ + UNDERLINED EDITABLE SINGLELINE MAXCHARS MAX_PLAYERNAME_LENGTH +#endif + +#define PLAYER_ROBCHECK( num, offset ) \ + CHECKBOX "" ID XW_ROBOT_##num##_CHECKBOX_ID \ + AT (PREVRIGHT PREVTOP AUTO AUTO) USABLE RIGHTANCHOR + +#define PLAYER_PASSFIELD( num, offset ) \ + SELECTORTRIGGER "" XW_PLAYERPASSWD_##num##_TRIGGER_ID \ + AT (PREVRIGHT PREVTOP 12 11) + +/* FIELD XW_PLAYERPASSWD_##num##_FIELD_ID PREVRIGHT PREVTOP 20 \ */ +/* AUTO UNDERLINED EDITABLE SINGLELINE MAXCHARS 4 */ + +#define PLAYER_ROW( num, offset ) \ + PLAYER_REMOTECHECK( num, offset ) \ + PLAYER_NAMEFIELD( num, offset ) \ + PLAYER_ROBCHECK( num, offset ) \ + PLAYER_PASSFIELD( num, offset ) + +#define PLAYER_ROW_ID( num ) \ + ID XW_REMOTE_##num##_CHECKBOX_ID \ + ID XW_PLAYERNAME_##num##_FIELD_ID \ + ID XW_ROBOT_##num##_CHECKBOX_ID \ + ID XW_PLAYERPASSWD_##num##_TRIGGER_ID \ + +#define PLAYER_ROW_NAV( num ) \ + ROW XW_REMOTE_##num##_CHECKBOX_ID \ + ROW XW_PLAYERNAME_##num##_FIELD_ID \ + XW_ROBOT_##num##_CHECKBOX_ID \ + XW_PLAYERPASSWD_##num##_TRIGGER_ID \ + +//#define SERVER_GROUP_ID 2000 +#define SERVER_HEIGHT 12 + +FORM ID XW_NEWGAMES_FORM AT (2 FORM_TOP 156 FORM_HEIGHT) +USABLE MODAL SAVEBEHIND DEFAULTBTNID XW_OK_BUTTON_ID +BEGIN + TITLE "Options de la partie" + +#ifndef XWFEATURE_STANDALONE_ONLY + LABEL "En jeu :" AUTOID AT (LEFTCOL SERVER_TOP) + GADGET ID XW_SOLO_GADGET_ID + AT (PREVRIGHT+5 SERVER_TOP 36 SERVER_HEIGHT) USABLE + GADGET ID XW_SERVER_GADGET_ID + AT (PREVRIGHT+1 SERVER_TOP 31 SERVER_HEIGHT) USABLE + GADGET ID XW_CLIENT_GADGET_ID + AT (PREVRIGHT+1 SERVER_TOP 36 SERVER_HEIGHT) USABLE + LIST "Locale" "Hôte" "Invité" XW_SERVERTYPES_LIST_ID + AT (0 0 1 1) VISIBLEITEMS 3 NONUSABLE +#endif + +/* Pick number of players here */ + FIELD XW_TOTALP_FIELD_ID LEFTCOL NPLAYERS_TOP 58 AUTO \ + SINGLELINE NONEDITABLE MAXCHARS 16 + + SELECTORTRIGGER "" XW_NPLAYERS_SELECTOR_ID \ + AT (PREVRIGHT NPLAYERS_TOP AUTO AUTO) USABLE LEFTANCHOR + LIST "1" "2" "3" "4" XW_NPLAYERS_LIST_ID AT (PREVLEFT PREVTOP 10 1) \ + VISIBLEITEMS 4 NONUSABLE + + BUTTON "J" XW_GINFO_JUGGLE_ID PREVRIGHT+3 PREVTOP 10 AUTO + + BUTTON "Autres préfs..." XW_PREFS_BUTTON_ID PREVRIGHT+5 PREVTOP 63 AUTO + +#ifndef XWFEATURE_STANDALONE_ONLY + LABEL "Effacer" XW_LOCAL_LABEL_ID REMOTE_COL LABEL_TOP FONT 1 +#endif + LABEL "Nom" AUTOID NAME_COL LABEL_TOP FONT 1 + LABEL "Robot" AUTOID ROBOT_COL LABEL_TOP FONT 1 + LABEL "MdP" AUTOID PASSWD_COL LABEL_TOP FONT 1 + + PLAYER_ROW( 1, 2 ) + PLAYER_ROW( 2, 2 ) + PLAYER_ROW( 3, 2 ) + PLAYER_ROW( 4, 2 ) + + GRAFFITISTATEINDICATOR 2 PREVBOTTOM+10 + + SELECTORTRIGGER "Dictionnaire..." XW_DICT_SELECTOR_ID \ + AT (PREVRIGHT+8 PREVTOP AUTO AUTO) USABLE LEFTANCHOR + + BUTTON "Ok" XW_OK_BUTTON_ID RIGHT@154 PREVTOP 18 AUTO + BUTTON "Annuler" XW_CANCEL_BUTTON_ID RIGHT@PREVLEFT-5 PREVTOP 36 AUTO +END /* XW_NEWGAMES_FORM */ + +#ifdef XWFEATURE_FIVEWAY +NAVIGATION ID XW_NEWGAMES_FORM +INITIALSTATE kFrmNavHeaderFlagsObjectFocusStartState +#if 0 +BEGIN + ID XW_SOLO_GADGET_ID + ID XW_SERVER_GADGET_ID + ID XW_CLIENT_GADGET_ID + + ID XW_NPLAYERS_SELECTOR_ID + ID XW_GINFO_JUGGLE_ID + ID XW_PREFS_BUTTON_ID + + PLAYER_ROW_ID( 1 ) + PLAYER_ROW_ID( 2 ) + PLAYER_ROW_ID( 3 ) + PLAYER_ROW_ID( 4 ) + + ID XW_DICT_SELECTOR_ID + ID XW_CANCEL_BUTTON_ID + ID XW_OK_BUTTON_ID +#else +NAVIGATIONMAP +#ifndef XWFEATURE_STANDALONE_ONLY + ROW XW_SOLO_GADGET_ID + XW_SERVER_GADGET_ID + XW_CLIENT_GADGET_ID +#endif + ROW XW_NPLAYERS_SELECTOR_ID + XW_GINFO_JUGGLE_ID + XW_PREFS_BUTTON_ID + + PLAYER_ROW_NAV( 1 ) + PLAYER_ROW_NAV( 2 ) + PLAYER_ROW_NAV( 3 ) + PLAYER_ROW_NAV( 4 ) + + ROW XW_DICT_SELECTOR_ID + XW_CANCEL_BUTTON_ID + XW_OK_BUTTON_ID +#endif +END /* NAVIGATION ID XW_NEWGAMES_FORM */ +#endif + +#ifdef XWFEATURE_BLUETOOTH +/* Let's define this in one place so it stays the same */ +# define BT_CONF_STRING "Demander avant Bluetooth" +#endif + + +#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH || defined XWFEATURE_IR +#define LEFTCOL 4 +#define CONNS_FIELD_LEFT 73 +#define LOCALIP_TOP 30 + +FORM ID XW_CONNS_FORM AT (2 66 156 93) +USABLE MODAL SAVEBEHIND DEFAULTBTNID XW_CONNS_CANCEL_BUTTON_ID +BEGIN + TITLE "Connexions" + + LABEL "Connecter via:" AUTOID LEFTCOL 15 FONT 1 + + POPUPTRIGGER "" ID XW_CONNS_TYPE_TRIGGER_ID + AT (PREVRIGHT+5 PREVTOP 72 12) LEFTANCHOR + LIST + "" ID XW_CONNS_TYPE_LIST_ID + PREVLEFT PREVTOP 72 12 VISIBLEITEMS 2 + NONUSABLE POPUPLIST XW_CONNS_TYPE_TRIGGER_ID XW_CONNS_TYPE_LIST_ID + +/* Bluetooth stuff must be here even if XWFEATURE_BLUETOOTH is not defined + since, e.g. ARM and 68K share these resources yet may not both support + BT */ +#ifdef XWFEATURE_BLUETOOTH + LABEL "Nom de l'hôte :" XW_CONNS_BT_HOSTNAME_LABEL_ID + AT ( LEFTCOL LOCALIP_TOP+5 ) NONUSABLE + SELECTORTRIGGER "Trouver l'hôte..." XW_CONNS_BT_HOSTTRIGGER_ID \ + AT (CONNS_FIELD_LEFT PREVTOP 70 AUTO) NONUSABLE LEFTANCHOR + CHECKBOX BT_CONF_STRING ID XW_CONNS_BTCONFIRM_CHECKBOX_ID \ + AT ( LEFTCOL LOCALIP_TOP+21 AUTO AUTO ) NONUSABLE +#endif + +/* Relay... */ +#ifdef XWFEATURE_RELAY + LABEL "Nom du réseau :" XW_CONNS_RELAY_LABEL_ID + AT ( LEFTCOL+10 LOCALIP_TOP ) + FIELD XW_CONNS_RELAY_FIELD_ID CONNS_FIELD_LEFT PREVTOP 70 AUTO \ + SINGLELINE EDITABLE UNDERLINED MAXCHARS 32 + + LABEL "Port du réseau :" XW_CONNS_PORT_LABEL_ID + AT (LEFTCOL PREVBOTTOM + 2) + FIELD XW_CONNS_PORT_FIELD_ID CONNS_FIELD_LEFT PREVTOP 30 AUTO \ + EDITABLE SINGLELINE UNDERLINED NUMERIC MAXCHARS 5 + + LABEL "Cookie:" XW_CONNS_COOKIE_LABEL_ID + AT ( LEFTCOL+10 PREVBOTTOM + 2 ) + FIELD XW_CONNS_COOKIE_FIELD_ID CONNS_FIELD_LEFT PREVTOP 70 AUTO \ + SINGLELINE EDITABLE UNDERLINED MAXCHARS 32 +#endif + + BUTTON "Annuler" XW_CONNS_CANCEL_BUTTON_ID 42 75 AUTO AUTO + BUTTON "Ok" XW_CONNS_OK_BUTTON_ID PREVRIGHT+10 PREVTOP AUTO AUTO +END /* XW_CONNS_FORM */ +#endif + +#ifdef FEATURE_TRAY_EDIT +# define TRAY_EDIT_ADJUST 15 +#else +# define TRAY_EDIT_ADJUST 0 +#endif +#ifdef XWFEATURE_SEARCHLIMIT +# define SEARCHLIMIT_ADJUST 15 +#else +# define SEARCHLIMIT_ADJUST 0 +#endif +#ifdef XWFEATURE_BLUETOOTH +# define BTCONF_ADJUST PREFS_SPACING +#else +# define BTCONF_ADJUST 0 +#endif + +#define PREFS_MODE_TOP 15 +#define PREFS_ROW1 30 +#define PREFS_SPACING 15 + +/* #define DLG_TOP (52-TRAY_EDIT_ADJUST-SEARCHLIMIT_ADJUST) */ +#define DLG_HEIGHT (112+TRAY_EDIT_ADJUST+SEARCHLIMIT_ADJUST+BTCONF_ADJUST) +#define DLG_TOP (160 - DLG_HEIGHT - 2) +#define TIMER_TOP (74+SEARCHLIMIT_ADJUST) +#define BTCONF_TOP (TIMER_TOP+18+TRAY_EDIT_ADJUST) +#define BUTTON_TOP (BTCONF_TOP + PREFS_SPACING) +#define PREFS_LNHT 4 + +FORM ID XW_PREFS_FORM AT (2 DLG_TOP 156 DLG_HEIGHT) +USABLE MODAL SAVEBEHIND DEFAULTBTNID XW_PREFS_CANCEL_BUTTON_ID +BEGIN + TITLE "Préférences" + + GADGET ID XW_PREFS_ALLGAMES_GADGET_ID + AT (LEFTCOL+8 PREFS_MODE_TOP 76 SERVER_HEIGHT) USABLE + GADGET ID XW_PREFS_ONEGAME_GADGET_ID + AT (PREVRIGHT+1 PREVTOP 54 SERVER_HEIGHT) USABLE + LIST "Toutes les parties" "Cette partie" XW_PREFS_TYPES_LIST_ID + AT (0 0 1 1) VISIBLEITEMS 2 NONUSABLE + + /* global prefs */ + CHECKBOX "Lettres jouées en couleurs" ID XW_PREFS_PLAYERCOLORS_CHECKBOX_ID \ + AT (LEFTCOL PREFS_ROW1 AUTO AUTO) NONUSABLE + CHECKBOX "Montrer barre défilement" ID XW_PREFS_PROGRESSBAR_CHECKBOX_ID \ + AT (LEFTCOL PREVTOP+PREFS_SPACING AUTO AUTO) NONUSABLE + CHECKBOX "Grande grille" ID XW_PREFS_SHOWGRID_CHECKBOX_ID \ + AT (LEFTCOL PREVTOP+PREFS_SPACING AUTO AUTO) NONUSABLE + CHECKBOX "Activer curseur" ID XW_PREFS_SHOWARROW_CHECKBOX_ID \ + AT (LEFTCOL PREVTOP+PREFS_SPACING AUTO AUTO) NONUSABLE + CHECKBOX "Expliquer robot/effacer scores" ID XW_PREFS_ROBOTSCORE_CHECKBOX_ID \ + AT (LEFTCOL PREVTOP+PREFS_SPACING AUTO AUTO) NONUSABLE + CHECKBOX "Cacher valeurs des lettres" ID XW_PREFS_HIDETRAYVAL_CHECKBOX_ID \ + AT (LEFTCOL PREVTOP+PREFS_SPACING AUTO AUTO) NONUSABLE + + /* single-game prefs */ + CHECKBOX "Robot commence" ID XW_PREFS_ROBOTSMART_CHECKBOX_ID \ + AT (LEFTCOL PREFS_ROW1 AUTO AUTO) NONUSABLE +#ifdef XWFEATURE_SEARCHLIMIT + CHECKBOX "Désactiver indices" ID XW_PREFS_NOHINTS_CHECKBOX_ID \ + AT (LEFTCOL PREVTOP+PREFS_SPACING AUTO AUTO) NONUSABLE + CHECKBOX "En local" ID XW_PREFS_HINTRECT_CHECKBOX_ID \ + AT (PREVRIGHT+3 PREVTOP AUTO AUTO) NONUSABLE +#else + CHECKBOX "Désactiver indices" ID XW_PREFS_NOHINTS_CHECKBOX_ID \ + AT (PREVRIGHT+3 PREFS_ROW1 AUTO AUTO) NONUSABLE +#endif + + LABEL "Inconnus :" XW_PREFS_PHONIES_LABEL_ID + AT (LEFTCOL PREVTOP+PREFS_SPACING) + POPUPTRIGGER "" ID XW_PREFS_PHONIES_TRIGGER_ID + AT (PREVRIGHT+5 PREVTOP 72 12) LEFTANCHOR + + LABEL "Dimensions grille : " XW_PREFS_BDSIZE_LABEL_ID + AT (LEFTCOL PREVTOP+PREFS_SPACING) + SELECTORTRIGGER "" XW_PREFS_BDSIZE_SELECTOR_ID \ + AT (PREVRIGHT PREVTOP AUTO AUTO) USABLE LEFTANCHOR + LIST "Ignorer" "Avertir" "Désactiver" ID XW_PREFS_PHONIES_LIST_ID + AT (PREVLEFT PREVTOP 72 12) NONUSABLE VISIBLEITEMS 3 + POPUPLIST XW_PREFS_PHONIES_TRIGGER_ID XW_PREFS_PHONIES_LIST_ID + + LIST "15x15" "13x13" "11x11" \ + XW_PREFS_BDSIZE_LIST_ID AT (PREVLEFT PREVTOP 30 1) \ + NONUSABLE VISIBLEITEMS NUM_BOARD_SIZES + + CHECKBOX "Activer minuteur (minutes :)" ID XW_PREFS_TIMERON_CHECKBOX_ID \ + AT (LEFTCOL PREVTOP+PREFS_SPACING AUTO AUTO) NONUSABLE + FIELD XW_PREFS_TIMER_FIELD_ID PREVRIGHT+2 PREVTOP 12 AUTO UNDERLINED \ + EDITABLE SINGLELINE NUMERIC MAXCHARS 3 + +#ifdef FEATURE_TRAY_EDIT + CHECKBOX "Choisir lettres à découvert" ID XW_PREFS_PICKTILES_CHECKBOX_ID \ + AT (LEFTCOL PREVTOP+PREFS_SPACING AUTO AUTO) NONUSABLE +#endif + +#ifdef XWFEATURE_BLUETOOTH + CHECKBOX BT_CONF_STRING ID XW_PREFS_BTCONFIRM_CHECKBOX_ID \ + AT (LEFTCOL PREVTOP+PREFS_SPACING AUTO AUTO) NONUSABLE +#endif + + /* buttons at the bottom */ + BUTTON "Annuler" XW_PREFS_CANCEL_BUTTON_ID 42 BUTTON_TOP AUTO AUTO + BUTTON "Ok" XW_PREFS_OK_BUTTON_ID PREVRIGHT+10 PREVTOP AUTO AUTO +END /* XW_PREFS_FORM */ + +NAVIGATION ID XW_PREFS_FORM +INITIALSTATE kFrmNavHeaderFlagsObjectFocusStartState +INITIALOBJECTID XW_PREFS_ONEGAME_GADGET_ID +NAVIGATIONMAP + ROW XW_PREFS_ALLGAMES_GADGET_ID + XW_PREFS_ONEGAME_GADGET_ID + + /* global prefs */ + ROW XW_PREFS_PLAYERCOLORS_CHECKBOX_ID + ROW XW_PREFS_PROGRESSBAR_CHECKBOX_ID + ROW XW_PREFS_SHOWGRID_CHECKBOX_ID + ROW XW_PREFS_SHOWARROW_CHECKBOX_ID + ROW XW_PREFS_ROBOTSCORE_CHECKBOX_ID + ROW XW_PREFS_HIDETRAYVAL_CHECKBOX_ID + + /* Per-game prefs */ + ROW XW_PREFS_ROBOTSMART_CHECKBOX_ID + ROW XW_PREFS_NOHINTS_CHECKBOX_ID +#ifdef XWFEATURE_SEARCHLIMIT + XW_PREFS_HINTRECT_CHECKBOX_ID +#endif + ROW XW_PREFS_PHONIES_TRIGGER_ID + ROW XW_PREFS_BDSIZE_SELECTOR_ID + + ROW XW_PREFS_TIMERON_CHECKBOX_ID + XW_PREFS_TIMER_FIELD_ID +#ifdef FEATURE_TRAY_EDIT + ROW XW_PREFS_PICKTILES_CHECKBOX_ID +#endif + ROW XW_PREFS_BTCONFIRM_CHECKBOX_ID + + /* cmd buttons */ + ROW XW_PREFS_CANCEL_BUTTON_ID + XW_PREFS_OK_BUTTON_ID +END + +#define LEFT_EDGE 10 +FORM ID XW_DICTINFO_FORM AT (2 111 156 47) +USABLE MODAL DEFAULTBTNID XW_DICTINFO_CANCEL_BUTTON_ID +BEGIN + TITLE "Dictionnaires" + + LABEL "Dict. :" AUTOID AT (LEFT_EDGE 15) + POPUPTRIGGER "" ID XW_DICTINFO_TRIGGER_ID + AT (PREVRIGHT+5 PREVTOP 72 12) LEFTANCHOR + LIST "" ID XW_DICTINFO_LIST_ID AT (PREVLEFT PREVTOP 72 1) + NONUSABLE VISIBLEITEMS 4 + POPUPLIST XW_DICTINFO_TRIGGER_ID XW_DICTINFO_LIST_ID + + BUTTON "Ok" XW_DICTINFO_DONE_BUTTON_ID 25 31 AUTO AUTO + BUTTON "Transm." XW_DICTINFO_BEAM_BUTTON_ID 22 PREVTOP AUTO AUTO + BUTTON "Annuler" XW_DICTINFO_CANCEL_BUTTON_ID PREVRIGHT+20 PREVTOP + AUTO AUTO +END + +FORM ID XW_ASK_FORM_ID AT (2 70 156 88) +USABLE MODAL SAVEBEHIND DEFAULTBTNID XW_ASK_NO_BUTTON_ID +MENUID XW_ASK_MENU_ID +BEGIN + TITLE "Requête" + + /* This has to be non-editable because the field is set via a ptr */ + FIELD XW_ASK_TXT_FIELD_ID LEFT_EDGE-5 16 135 52 \ + NONEDITABLE MULTIPLELINES + + SCROLLBAR ID XW_ASK_SCROLLBAR_ID + AT ( PREVRIGHT+2 PREVTOP RECOMMENDED_SBAR_WIDTH + PREVBOTTOM - PREVTOP) USABLE + + BUTTON "Oui" XW_ASK_YES_BUTTON_ID RIGHT@(156/2)-20 PREVBOTTOM+5 AUTO AUTO + BUTTON "Non" XW_ASK_NO_BUTTON_ID 156/2+20 PREVTOP AUTO AUTO +END + +FORM ID XW_PASSWORD_DIALOG_ID AT ( 2 88 156 70 ) + MODAL SAVEBEHIND DEFAULTBTNID XW_PASSWORD_CANCEL_BUTTON +BEGIN + TITLE "Mot de Passe" + + LABEL "Entrer mot de passe pour :" XW_PASSWORD_NAME_LABEL 10 18 FONT 1 NONUSABLE + LABEL "Entrer nouveau mot de passe pour :" XW_PASSWORD_NEWNAME_LABEL 10 18 FONT 1 + NONUSABLE + FIELD XW_PASSWORD_NAME_FIELD AT (PREVLEFT PREVBOTTOM+3 90 12) NONEDITABLE + SINGLELINE MAXCHARS MAX_PLAYERNAME_LENGTH + FIELD XW_PASSWORD_PASS_FIELD \ + AT (PREVRIGHT+10 PREVTOP MAX_PASSWORD_LENGTH*6 12) \ + EDITABLE SINGLELINE UNDERLINED MAXCHARS MAX_PASSWORD_LENGTH + GRAFFITISTATEINDICATOR 2 PREVBOTTOM+8 + BUTTON "Ok" XW_PASSWORD_OK_BUTTON 45 PREVTOP AUTO AUTO + BUTTON "Annuler" XW_PASSWORD_CANCEL_BUTTON PREVRIGHT+10 PREVTOP AUTO AUTO +END + +#define BLANK_PICK_TOP 15 + +FORM ID XW_BLANK_DIALOG_ID AT ( 2 74 156 83 ) USABLE + MODAL SAVEBEHIND +#ifdef FEATURE_TRAY_EDIT + DEFAULTBTNID XW_BLANK_PICK_BUTTON_ID +#endif +BEGIN + TITLE "Tirage des lettres" + + FIELD XW_BLANK_LABEL_FIELD_ID AT (10 BLANK_PICK_TOP 110 39) + NONEDITABLE MULTIPLELINES + + LIST "" ID XW_BLANK_LIST_ID AT (PREVRIGHT+2 BLANK_PICK_TOP 28 72) + USABLE VISIBLEITEMS 6 + + BUTTON "Ok" XW_BLANK_OK_BUTTON_ID RIGHT@PREVLEFT-4 65 16 AUTO + +#ifdef FEATURE_TRAY_EDIT + BUTTON "Choisir toutes !" XW_BLANK_PICK_BUTTON_ID 5 65 62 AUTO + BUTTON "Suppr." XW_BLANK_BACKUP_BUTTON_ID PREVRIGHT+4 PREVTOP 28 AUTO +#endif +END + +#ifdef XWFEATURE_SEARCHLIMIT +# define NTILES_TOP 17 +# define NTILES_BUTTON_TOP 60 +FORM ID XW_HINTCONFIG_FORM_ID AT ( 2 75 156 82 ) USABLE MODAL SAVEBEHIND +BEGIN + TITLE "Paramètres de l'aide" + + LABEL "Au moins ce nb de lettres :" AUTOID 10 NTILES_TOP FONT 1 USABLE + SELECTORTRIGGER "" XW_HINTCONFIG_MINSELECTOR_ID \ + AT (PREVRIGHT+3 NTILES_TOP AUTO AUTO) USABLE LEFTANCHOR + LIST "1" "2" "3" "4" "5" "6" "7" ID XW_HINTCONFIG_MINLIST_ID + AT (PREVRIGHT PREVTOP AUTO AUTO) VISIBLEITEMS 7 NONUSABLE + + LABEL "Pas plus que ce nombre :" AUTOID 10 PREVTOP+15 FONT 1 USABLE + SELECTORTRIGGER "" XW_HINTCONFIG_MAXSELECTOR_ID \ + AT (PREVRIGHT+3 PREVTOP AUTO AUTO) USABLE LEFTANCHOR + LIST "1" "2" "3" "4" "5" "6" "7" ID XW_HINTCONFIG_MAXLIST_ID + AT (PREVRIGHT PREVTOP AUTO AUTO) VISIBLEITEMS 7 NONUSABLE + + BUTTON "Ok" XW_HINTCONFIG_OK_ID RIGHT@156-10 NTILES_BUTTON_TOP AUTO 12 + BUTTON "Annuler" XW_HINTCONFIG_CANCEL_ID RIGHT@PREVLEFT-10 PREVTOP AUTO 12 +END +#endif + +#if defined OWNER_HASH || defined NO_REG_REQUIRED +FORM ID XW_SAVEDGAMES_DIALOG_ID AT ( 2 2 156 156 ) +USABLE MODAL DEFAULTBTNID XW_SAVEDGAMES_DONE_BUTTON +BEGIN + TITLE "Parties sauvegardées" + + LIST "" ID XW_SAVEDGAMES_LIST_ID AT (2 15 140 60) \ + USABLE ENABLED VISIBLEITEMS 1 + GRAFFITISTATEINDICATOR 2 120 + FIELD XW_SAVEDGAMES_NAME_FIELD AT (PREVRIGHT+10 PREVTOP 100 AUTO) + EDITABLE SINGLELINE UNDERLINED MAXCHARS MAX_GAMENAME_LENGTH + + BUTTON "Modif." XW_SAVEDGAMES_USE_BUTTON RIGHT@154 PREVTOP 30 AUTO + + BUTTON "Dupl." XW_SAVEDGAMES_DUPE_BUTTON 2 PREVBOTTOM+5 31 AUTO + BUTTON "Suppr." XW_SAVEDGAMES_DELETE_BUTTON PREVRIGHT+5 PREVTOP 39 AUTO + BUTTON "Ouvrir" XW_SAVEDGAMES_OPEN_BUTTON PREVRIGHT+5 PREVTOP 33 AUTO + BUTTON "Fait" XW_SAVEDGAMES_DONE_BUTTON PREVRIGHT+5 PREVTOP 33 AUTO +END /* XW_SAVEDGAMES_DIALOG_ID */ +#endif + +ALERT XW_ERROR_ALERT_ID +ERROR +BEGIN + TITLE "Erreur" + MESSAGE "^1" + BUTTONS "Ok" +END + +#ifdef FOR_GREMLINS + +FORM ID XW_GREMLIN_WARN_FORM_ID AT ( 2 60 156 98 ) +USABLE MODAL +BEGIN + TITLE "Gremlin Oops" + FIELD XW_GREMLIN_WARN_FIELD_ID AT (2 15 150 75) + NONEDITABLE MULTIPLELINES +END + +#endif diff --git a/xwords4/palm/ldscript.arm b/xwords4/palm/ldscript.arm new file mode 100644 index 000000000..a374631c5 --- /dev/null +++ b/xwords4/palm/ldscript.arm @@ -0,0 +1,8 @@ +OUTPUT_FORMAT("elf32-littlearm") +OUTPUT_ARCH(arm) +ENTRY(ArmletEntryPoint) +SEARCH_DIR(/usr/local/arm-elf/lib); +SECTIONS +{ +.text 0x00000000 : { *(.text) *(.rodata) *(.rodata.str1.4) } +} diff --git a/xwords4/palm/newgame.c b/xwords4/palm/newgame.c new file mode 100644 index 000000000..81ed3b072 --- /dev/null +++ b/xwords4/palm/newgame.c @@ -0,0 +1,718 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 1999 - 2006 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include /* for nextFieldChr */ +#include /* for GrfSetState */ +#include +#ifdef XWFEATURE_FIVEWAY +# include +#endif + +#include "callback.h" +#include "comtypes.h" +#include "palmmain.h" +#include "comms.h" +#include "strutils.h" +#include "newgame.h" +#include "xwords4defines.h" +#include "dictui.h" +#include "palmdict.h" +#include "palmutil.h" +#include "palmir.h" +#include "prefsdlg.h" +#include "connsdlg.h" +#include "LocalizedStrIncludes.h" + +static void handlePasswordTrigger( PalmAppGlobals* globals, + UInt16 controlID ); +static XP_Bool updatePlayerInfo( PalmAppGlobals* globals ); +static void loadNewGameState( PalmAppGlobals* globals ); +static void unloadNewGameState( PalmAppGlobals* globals ); +static void setNameThatFits( PalmNewGameState* state ); + +static void palmEnableColProc( void* closure, XP_U16 player, + NewGameColumn col, XP_TriEnable enable ); +static void palmEnableAttrProc( void* closure, NewGameAttr attr, + XP_TriEnable enable ); +static void palmGetColProc( void* closure, XP_U16 player, NewGameColumn col, + NgCpCallbk cpcb, const void* cbClosure ); +static void palmSetColProc( void* closure, XP_U16 player, NewGameColumn col, + const NGValue value ); +static void palmSetAttrProc( void* closure, NewGameAttr attr, + const NGValue value ); +static void handleRobotChanged( PalmNewGameState* state, XP_U16 controlID, + XP_Bool on ); + +#ifndef XWFEATURE_STANDALONE_ONLY +static void handleRemoteChanged( PalmNewGameState* state, XP_U16 controlID, + XP_Bool on ); +static Boolean checkHiliteGadget(PalmAppGlobals* globals, + const EventType* event, + PalmNewGameState* state ); +static void drawConnectGadgets( PalmAppGlobals* globals ); +static void changeGadgetHilite( PalmAppGlobals* globals, UInt16 hiliteID ); + +#else +# define checkHiliteGadget(globals, event, state) XP_FALSE +# define drawConnectGadgets( globals ) +#endif + +/***************************************************************************** + * + ****************************************************************************/ +Boolean +newGameHandleEvent( EventPtr event ) +{ + Boolean result = false; + PalmAppGlobals* globals; + FormPtr form; + CurGameInfo* gi; + PalmNewGameState* state; + Int16 chosen; + XP_S16 itemId; + Boolean on; + + CALLBACK_PROLOGUE(); + + globals = getFormRefcon(); + gi = &globals->gameInfo; + state = &globals->newGameState; + + switch ( event->eType ) { + + case frmOpenEvent: + + GlobalPrefsToLocal( globals ); + + form = FrmGetActiveForm(); + + XP_ASSERT( !state->ngc ); + XP_MEMSET( state, 0, sizeof(*state) ); + + state->form = form; + + loadNewGameState( globals ); + if ( !globals->isNewGame ) { + disOrEnableTri( form, XW_DICT_SELECTOR_ID, TRI_ENAB_DISABLED ); + } + + XP_ASSERT( !!state->dictName ); + setNameThatFits( state ); + + XP_ASSERT( !!globals->game.server ); + case frmUpdateEvent: + GrfSetState( false, false, false ); + FrmDrawForm( FrmGetActiveForm() ); + + drawConnectGadgets( globals ); + + result = true; + break; + +#ifdef XWFEATURE_FIVEWAY + /* docs say to return HANDLED for both take and lost if the right + object */ + case frmObjectFocusTakeEvent: + case frmObjectFocusLostEvent: + result = considerGadgetFocus( globals, event, XW_SOLO_GADGET_ID, + XW_CLIENT_GADGET_ID ); + break; +#endif + + case penDownEvent: + result = checkHiliteGadget( globals, event, state ); + break; + +#ifdef XWFEATURE_FIVEWAY + case keyDownEvent: + itemId = getFocusOwner(); + if ( itemId >= 0 ) { + result = tryRockerKey( event->data.keyDown.chr, itemId, + XW_SOLO_GADGET_ID, XW_CLIENT_GADGET_ID ); + if ( result ) { + changeGadgetHilite( globals, itemId ); + } + } + break; +#endif + + case prefsChangedEvent: + state->forwardChange = true; + break; + + case ctlSelectEvent: + result = true; + itemId = event->data.ctlSelect.controlID; + on = event->data.ctlSelect.on; + switch ( itemId ) { + + case XW_ROBOT_1_CHECKBOX_ID: + case XW_ROBOT_2_CHECKBOX_ID: + case XW_ROBOT_3_CHECKBOX_ID: + case XW_ROBOT_4_CHECKBOX_ID: + handleRobotChanged( state, itemId, on ); + break; +#ifndef XWFEATURE_STANDALONE_ONLY + case XW_REMOTE_1_CHECKBOX_ID: + case XW_REMOTE_2_CHECKBOX_ID: + case XW_REMOTE_3_CHECKBOX_ID: + case XW_REMOTE_4_CHECKBOX_ID: + handleRemoteChanged( state, itemId, on ); + break; +#endif + + case XW_NPLAYERS_SELECTOR_ID: + XP_ASSERT( globals->isNewGame ); + chosen = LstPopupList( state->playerNumList ); + if ( chosen >= 0 ) { + NGValue value; + setSelectorFromList( XW_NPLAYERS_SELECTOR_ID, + state->playerNumList, + chosen ); + value.ng_u16 = chosen + 1; + newg_attrChanged( state->ngc, NG_ATTR_NPLAYERS, value ); + } + break; + + case XW_DICT_SELECTOR_ID: + XP_ASSERT( globals->isNewGame ); + globals->dictuiForBeaming = false; + FrmPopupForm( XW_DICTINFO_FORM ); + + /* popup dict selection dialog -- or maybe just a list if there + are no preferences to set. The results should all be + cancellable, so don't delete the existing dictionary (if + any) until OK is chosen */ + break; + + case XW_GINFO_JUGGLE_ID: + while ( !newg_juggle( state->ngc ) ) { + } + (void)newg_juggle( state->ngc ); + break; + + case XW_OK_BUTTON_ID: + if ( updatePlayerInfo( globals ) ) { + /* if we put up the prefs form from within this one and the user + clicked ok, we need to make sure the main form gets the + notification so it can make use of any changes. This event + needs to arrive before the newGame event so any changes will + be incorporated. */ + if ( state->forwardChange ) { + postEmptyEvent( prefsChangedEvent ); + state->forwardChange = false; + } + + if ( globals->isNewGame ) { + postEmptyEvent( newGameOkEvent ); + globals->postponeDraw = true; + } + + unloadNewGameState( globals ); + + FrmReturnToForm( 0 ); + } + break; + + case XW_CANCEL_BUTTON_ID: + unloadNewGameState( globals ); + postEmptyEvent( newGameCancelEvent ); + FrmReturnToForm( 0 ); + break; + + case XW_PREFS_BUTTON_ID: + /* bring up with the this-game tab selected */ + XP_ASSERT( !!globals->prefsDlgState ); + globals->prefsDlgState->stateTypeIsGlobal = false; + FrmPopupForm( XW_PREFS_FORM ); + break; + + case XW_PLAYERPASSWD_1_TRIGGER_ID: + case XW_PLAYERPASSWD_2_TRIGGER_ID: + case XW_PLAYERPASSWD_3_TRIGGER_ID: + case XW_PLAYERPASSWD_4_TRIGGER_ID: + handlePasswordTrigger( globals, itemId ); + break; + + default: /* one of the password selectors? */ + result = false; + } + break; + + case dictSelectedEvent: + /* posted by the form we raise when user clicks Dictionary selector + above. */ + if ( state->dictName != NULL ) { + XP_FREE( globals->mpool, state->dictName ); + } + state->dictName = + ((DictSelectedData*)&event->data.generic)->dictName; + setNameThatFits( state ); + break; + + case appStopEvent: /* I get these on older palms */ + unloadNewGameState( globals ); + result = false; + + default: + break; + } + + CALLBACK_EPILOGUE(); + return result; +} /* newGameHandleEvent */ + +static void +setNameThatFits( PalmNewGameState* state ) +{ + RectangleType rect; + XP_U16 width; + XP_U16 len = XP_STRLEN( (const char*)state->dictName ); + + XP_MEMCPY( state->shortDictName, state->dictName, len + 1 ); + + /* The width available is the cancel button's left minus ours */ + getObjectBounds( XW_CANCEL_BUTTON_ID, &rect ); + width = rect.topLeft.x; + getObjectBounds( XW_DICT_SELECTOR_ID, &rect ); + width -= (rect.topLeft.x + 6); + + for ( ; FntCharsWidth( (const char*)state->dictName, len ) > width; --len ) { + /* do nothing */ + } + + state->shortDictName[len] = '\0'; + CtlSetLabel( getActiveObjectPtr( XW_DICT_SELECTOR_ID ), + (const char*)state->shortDictName ); +} /* setNameThatFits */ + +/* + * Copy the local state into global state. + */ +static XP_Bool +updatePlayerInfo( PalmAppGlobals* globals ) +{ + CurGameInfo* gi; + PalmNewGameState* state = &globals->newGameState; + XP_Bool success; + + gi = &globals->gameInfo; + success = newg_store( state->ngc, gi, XP_TRUE ); + if ( success ) { + gi->boardSize = globals->prefsDlgState->curBdSize; + + replaceStringIfDifferent( globals->mpool, &gi->dictName, + globals->newGameState.dictName ); + } + return success; +} /* updatePlayerInfo */ + +/* Frame 'em, draw their text, and highlight the one that's selected + */ +#ifndef XWFEATURE_STANDALONE_ONLY + +static void +drawConnectGadgets( PalmAppGlobals* globals ) +{ + XP_U16 hiliteItem = globals->newGameState.curServerHilite + + XW_SOLO_GADGET_ID ; + ListPtr list = getActiveObjectPtr( XW_SERVERTYPES_LIST_ID ); + XP_ASSERT( !!list ); + + drawGadgetsFromList( list, XW_SOLO_GADGET_ID, XW_CLIENT_GADGET_ID, + hiliteItem ); +#ifdef XWFEATURE_FIVEWAY + drawFocusRingOnGadget( globals, XW_SOLO_GADGET_ID, XW_CLIENT_GADGET_ID ); +#endif +} /* drawConnectGadgets */ + +static void +changeGadgetHilite( PalmAppGlobals* globals, UInt16 hiliteID ) +{ + PalmNewGameState* state = &globals->newGameState; + XP_Bool isNewGame = globals->isNewGame; + + hiliteID -= XW_SOLO_GADGET_ID; + + if ( hiliteID != state->curServerHilite ) { + /* if it's not a new game, don't recognize the change */ + if ( isNewGame ) { + NGValue value; + + state->curServerHilite = hiliteID; + drawConnectGadgets( globals ); + + value.ng_role = hiliteID; + newg_attrChanged( state->ngc, NG_ATTR_ROLE, value ); + } else { + beep(); + } + } + +#if defined XWFEATURE_BLUETOOTH || defined XWFEATURE_RELAY + /* Even if it didn't change, pop the connections form. It's only + informational in the non-new-game case; nothing can be changed. */ + if ( (hiliteID != SERVER_STANDALONE) && (state->nXPorts > 1) ) { + if ( isNewGame || (hiliteID==globals->newGameState.curServerHilite) ) { + PopupConnsForm( globals, hiliteID, &state->addr ); + } + } +#endif +} /* changeGadgetHilite */ + +static Boolean +checkHiliteGadget( PalmAppGlobals* globals, const EventType* event, + PalmNewGameState* XP_UNUSED_DBG(state) ) +{ + Boolean result = false; + UInt16 selGadget; + + XP_ASSERT( &globals->newGameState == state ); + + result = penInGadget( event, &selGadget ); + if ( result ) { + changeGadgetHilite( globals, selGadget ); + } + + return result; +} /* checkHiliteGadget */ +#endif + +static void +showMaskedPwd( XP_U16 objectID, XP_Bool set ) +{ + const char* label = set? "*" : " "; + /* control owns the string passed in */ + CtlSetLabel( getActiveObjectPtr( objectID ), label ); +} + +/* If there's currently no password set, just let 'em set one. If there is + * one set, they need to know the old before setting the new. + */ +static void +handlePasswordTrigger( PalmAppGlobals* globals, UInt16 controlID ) +{ + UInt16 playerNum; + PalmNewGameState* state = &globals->newGameState; + XP_UCHAR* name; + FieldPtr nameField; + XP_U16 len; + + playerNum = (controlID - XW_PLAYERPASSWD_1_TRIGGER_ID) / NUM_PLAYER_COLS; + XP_ASSERT( playerNum < MAX_NUM_PLAYERS ); + + nameField = getActiveObjectPtr( XW_PLAYERNAME_1_FIELD_ID + + (NUM_PLAYER_COLS * playerNum) ); + name = (XP_UCHAR*)FldGetTextPtr( nameField ); + + len = sizeof(state->passwds[playerNum]); + if ( askPassword( globals, name, true, state->passwds[playerNum], &len )) { + showMaskedPwd( controlID, len > 0 ); + } +} /* handlePasswordTrigger */ + +static void +unloadNewGameState( PalmAppGlobals* globals ) +{ + PalmNewGameState* state = &globals->newGameState; + + XP_FREE( globals->mpool, state->dictName ); + state->dictName = NULL; + + newg_destroy( state->ngc ); + state->ngc = NULL; +} /* unloadNewGameState */ + +static XP_U16 +getBaseForCol( NewGameColumn col ) +{ + XP_U16 resID = 0; + switch ( col ) { +#ifndef XWFEATURE_STANDALONE_ONLY + case NG_COL_REMOTE: + resID = XW_REMOTE_1_CHECKBOX_ID; + break; +#endif + case NG_COL_NAME: + resID = XW_PLAYERNAME_1_FIELD_ID; + break; + case NG_COL_ROBOT: + resID = XW_ROBOT_1_CHECKBOX_ID; + break; + case NG_COL_PASSWD: + resID = XW_PLAYERPASSWD_1_TRIGGER_ID; + break; + default: + XP_ASSERT( XP_FALSE ); + } + XP_ASSERT( !!resID ); + return resID; +} /* getBaseForCol */ + +static XP_U16 +objIDForCol( XP_U16 player, NewGameColumn col ) +{ + XP_U16 base = getBaseForCol( col ); + return base + (NUM_PLAYER_COLS * player); +} + +static ControlPtr +getControlForCol( XP_U16 player, NewGameColumn col ) +{ + XP_U16 objID = objIDForCol( player, col ); + ControlPtr ctrl = getActiveObjectPtr( objID ); + return ctrl; +} + +static void +palmEnableColProc( void* closure, XP_U16 player, NewGameColumn col, + XP_TriEnable enable ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)closure; + PalmNewGameState* state = &globals->newGameState; + XP_U16 objID; + + /* If it's a field, there need to be three states */ + objID = objIDForCol( player, col ); + disOrEnableTri( state->form, objID, enable ); +} + +/* Palm doesn't really do "disabled." Things are visible or not. But we + * want the player count dropdown in particular visible since it give + * information. So different objects get treated differently. The code + * handling ctlEnterEvent can disable for non-newgame dialogs even if a + * control is technically enabled. + */ +static void +palmEnableAttrProc(void* closure, NewGameAttr attr, XP_TriEnable ngEnable ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)closure; + PalmNewGameState* state = &globals->newGameState; + XP_U16 objID = 0; + + switch ( attr ) { +#ifndef XWFEATURE_STANDALONE_ONLY + case NG_ATTR_ROLE: + /* always enabled */ + break; + case NG_ATTR_REMHEADER: + objID = XW_LOCAL_LABEL_ID; + break; +#endif + case NG_ATTR_NPLAYERS: + objID = XW_NPLAYERS_SELECTOR_ID; + break; + case NG_ATTR_NPLAYHEADER: + break; + case NG_ATTR_CANJUGGLE: + objID = XW_GINFO_JUGGLE_ID; + break; + } + + if ( objID != 0 ) { + disOrEnableTri( state->form, objID, ngEnable ); + } +} /* palmEnableAttrProc */ + +static void +palmGetColProc( void* closure, XP_U16 player, NewGameColumn col, + NgCpCallbk cpcb, const void* cbClosure ) +{ + PalmAppGlobals* globals; + PalmNewGameState* state; + NGValue value; + XP_U16 objID = objIDForCol( player, col ); + + switch ( col ) { +#ifndef XWFEATURE_STANDALONE_ONLY + case NG_COL_REMOTE: +#endif + case NG_COL_ROBOT: + value.ng_bool = getBooleanCtrl( objID ); + break; + case NG_COL_NAME: + value.ng_cp = FldGetTextPtr( getActiveObjectPtr( objID ) ); + break; + case NG_COL_PASSWD: + globals = (PalmAppGlobals*)closure; + state = &globals->newGameState; + value.ng_cp = state->passwds[player]; + break; + default: + XP_ASSERT(0); + } + + (*cpcb)( value, cbClosure ); +} + +static void +palmSetColProc( void* closure, XP_U16 player, NewGameColumn col, + const NGValue value ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)closure; + PalmNewGameState* state = &globals->newGameState; + ControlPtr ctrl; + XP_U16 objID = objIDForCol( player, col ); + + switch ( col ) { +#ifndef XWFEATURE_STANDALONE_ONLY + case NG_COL_REMOTE: +#endif + case NG_COL_ROBOT: + ctrl = getControlForCol( player, col ); + CtlSetValue( ctrl, value.ng_bool ); + break; + case NG_COL_NAME: + setFieldStr( objID, value.ng_cp ); + break; + case NG_COL_PASSWD: + if ( !!value.ng_cp ) { + XP_SNPRINTF( state->passwds[player], sizeof(state->passwds[player]), + "%s", value.ng_cp ); + showMaskedPwd( objID, *value.ng_cp != '\0' ); + } + + default: /* shut up compiler */ + break; + } +} /* palmSetColProc */ + +static void +palmSetAttrProc( void* closure, NewGameAttr attr, const NGValue value ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)closure; + PalmNewGameState* state = &globals->newGameState; + + switch ( attr ) { +#ifndef XWFEATURE_STANDALONE_ONLY + case NG_ATTR_ROLE: + state->curServerHilite = value.ng_role; + break; + case NG_ATTR_REMHEADER: + break; +#endif + case NG_ATTR_NPLAYERS: + setSelectorFromList( XW_NPLAYERS_SELECTOR_ID, + state->playerNumList, value.ng_u16 - 1 ); + break; + case NG_ATTR_NPLAYHEADER: { + FieldPtr field = getActiveObjectPtr( XW_TOTALP_FIELD_ID ); + char* cur = FldGetTextPtr( field ); + /* Need to update to get it drawn and that flashes the whole dialog. + So avoid if possible. */ + if ( !cur || (0 != StrCompare( cur, value.ng_cp ) ) ) { + FldSetTextPtr( field, (char*)value.ng_cp ); + FrmUpdateForm( 0, frmRedrawUpdateCode ); + } + } + break; + case NG_ATTR_CANJUGGLE: + XP_ASSERT(0); /* doesn't make sense */ + break; + } +} /* palmSetAttrProc */ + +static XP_U16 +palmPlayerFromID( XP_U16 id, XP_U16 base ) +{ + XP_U16 player; + player = (id - base) / NUM_PLAYER_COLS; + return player; +} /* palmPlayerFromID */ + +static void +handleRobotChanged( PalmNewGameState* state, XP_U16 controlID, XP_Bool on ) +{ + XP_U16 player; + NGValue value; + + player = palmPlayerFromID( controlID, XW_ROBOT_1_CHECKBOX_ID ); + value.ng_bool = on; + newg_colChanged( state->ngc, player ); +} + +#ifndef XWFEATURE_STANDALONE_ONLY +static void +handleRemoteChanged( PalmNewGameState* state, XP_U16 controlID, XP_Bool on ) +{ + XP_U16 player; + NGValue value; + + XP_LOGF( "%s: controlID=%d", __func__, controlID ); + + player = palmPlayerFromID( controlID, XW_REMOTE_1_CHECKBOX_ID ); + value.ng_bool = on; + newg_colChanged( state->ngc, player ); +} +#endif + +XP_U16 +countXPorts( const PalmAppGlobals* globals ) +{ + XP_U16 xports = 0; +#ifdef XWFEATURE_IR + ++xports; +#endif +#ifdef XWFEATURE_BLUETOOTH + if ( globals->hasBTLib ) { + ++xports; + } +#endif +#ifdef XWFEATURE_RELAY + ++xports; +#endif + return xports; +} + +static void +loadNewGameState( PalmAppGlobals* globals ) +{ + CurGameInfo* gi = &globals->gameInfo; + PalmNewGameState* state = &globals->newGameState; + + state->dictName = copyString( globals->mpool, gi->dictName ); + state->playerNumList = getActiveObjectPtr( XW_NPLAYERS_LIST_ID ); + state->nXPorts = countXPorts( globals ); + + XP_ASSERT( !state->ngc ); + state->ngc = newg_make( MPPARM(globals->mpool) + globals->isNewGame, + &globals->util, + palmEnableColProc, + palmEnableAttrProc, + palmGetColProc, + palmSetColProc, + palmSetAttrProc, + globals ); + newg_load( state->ngc, gi ); + +#if defined XWFEATURE_BLUETOOTH || defined XWFEATURE_RELAY + if ( globals->game.comms ) { + comms_getAddr( globals->game.comms, &state->addr ); + } else { + comms_getInitialAddr( &state->addr ); + } +#elif defined XWFEATURE_IR + state->addr.conType = COMMS_CONN_IR; +#endif +} /* loadNewGameState */ diff --git a/xwords4/palm/newgame.h b/xwords4/palm/newgame.h new file mode 100644 index 000000000..b2dc9f0dd --- /dev/null +++ b/xwords4/palm/newgame.h @@ -0,0 +1,30 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2001-2002 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef NEWGAME_H +#define NEWGAME_H + +#include + +#include "palmmain.h" + + +Boolean newGameHandleEvent( EventPtr event ); + +#endif diff --git a/xwords4/palm/pace_man.c b/xwords4/palm/pace_man.c new file mode 100644 index 000000000..e5d136ae9 --- /dev/null +++ b/xwords4/palm/pace_man.c @@ -0,0 +1,1432 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; compile-command: "make ARCH=ARM_ONLY MEMDEBUG=TRUE"; -*- */ +/* + * Copyright 2004-2007 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include + +#ifdef XWFEATURE_BLUETOOTH +#include +#include +#endif + +#include "pace_man.h" + +#include "palmmain.h" /* for custom event types */ +#include "palmsavg.h" +#include "strutils.h" + +/* Looks like I still need this??? */ +void* +memcpy( void* dest, const void* src, unsigned long n ) +{ + void* oldDest = dest; + unsigned char* d = (unsigned char*)dest; + unsigned char* s = (unsigned char*)src; + + FUNC_HEADER(memcpy); + + if ( dest < src ) { + while ( n-- > 0 ) { + *d++ = *s++; + } + } else { + d += n; + s += n; + while ( n-- > 0 ) { + *--d = *--s; + } + } + + FUNC_TAIL(memcpy); + return oldDest; +} /* memcpy */ + +unsigned long +Byte_Swap32( unsigned long l ) +{ + unsigned long result; + result = ((l >> 24) & 0xFF); + result |= ((l >> 8) & 0xFF00); + result |= ((l << 8) & 0xFF0000); + result |= ((l << 24) & 0xFF000000); + return result; +} + +unsigned short +Byte_Swap16( unsigned short l ) +{ + unsigned short result; + result = ((l >> 8) & 0xFF); + result |= ((l << 8) & 0xFF00); + return result; +} + +void +write_unaligned32( unsigned char* dest, unsigned long val ) +{ + int i; + dest += sizeof(val); + for ( i = 0; i < sizeof(val); ++i ) { + *--dest = val & 0x000000FF; + val >>= 8; + } +} /* write_unaligned32 */ + +void +write_unaligned16( unsigned char* dest, unsigned short val ) +{ + int i; + + dest += sizeof(val); + for ( i = 0; i < sizeof(val); ++i ) { + *--dest = val & 0x00FF; + val >>= 8; + } +} /* write_unaligned16 */ + +#define write_unaligned8( p, v ) *(p) = v + +unsigned short +read_unaligned16( const unsigned char* src ) +{ + int i; + unsigned short val = 0; + + for ( i = 0; i < sizeof(val); ++i ) { + val <<= 8; + val |= *src++; + } + + return val; +} /* read_unaligned16 */ + +#define read_unaligned8(cp) (*(cp)) + +unsigned long +read_unaligned32( const unsigned char* src ) +{ + int i; + unsigned long val = 0; + + for ( i = 0; i < sizeof(val); ++i ) { + val <<= 8; + val |= *src++; + } + + return val; +} /* read_unaligned32 */ + +#ifdef XWFEATURE_FIVEWAY +#include "pnostate.h" +Err +HsNavDrawFocusRing (FormType* formP, UInt16 objectID, Int16 extraInfo, + RectangleType* rP, + HsNavFocusRingStyleEnum ringStyle, Boolean forceRestore) +{ + Err result; + FUNC_HEADER(HsNavDrawFocusRing); + + RectangleType RectangleType_68K1; + SWAP_RECTANGLETYPE_ARM_TO_68K( &RectangleType_68K1, rP ); + { + PNOState* sp = GET_CALLBACK_STATE(); + STACK_START(unsigned char, stack, 18); + + ADD_TO_STACK2(stack, hsSelNavDrawFocusRing, 0); + ADD_TO_STACK4(stack, formP, 2); + ADD_TO_STACK2(stack, objectID, 6); + ADD_TO_STACK2(stack, extraInfo, 8); + ADD_TO_STACK4(stack, &RectangleType_68K1, 10); + ADD_TO_STACK1(stack, ringStyle, 14); + ADD_TO_STACK1(stack, forceRestore, 16); + STACK_END(stack); + + result = (Err)(*sp->call68KFuncP)( sp->emulStateP, + PceNativeTrapNo(sysTrapHsSelector), + stack, 18 ); + sp->emulStateP->regA[7] -= 2; + } + FUNC_TAIL(HsNavDrawFocusRing); + return result; +} + +Err +FrmNavDrawFocusRing(FormType* formP, UInt16 objectID, Int16 extraInfo, + RectangleType* rP, + HsNavFocusRingStyleEnum ringStyle, Boolean forceRestore) +{ + Err result; + FUNC_HEADER(FrmNavDrawFocusRing); + + RectangleType RectangleType_68K1; + SWAP_RECTANGLETYPE_ARM_TO_68K( &RectangleType_68K1, rP ); + { + PNOState* sp = GET_CALLBACK_STATE(); + STACK_START(unsigned char, stack, 18); + + ADD_TO_STACK2(stack, 0x0007, 0); + ADD_TO_STACK4(stack, formP, 2); + ADD_TO_STACK2(stack, objectID, 6); + ADD_TO_STACK2(stack, extraInfo, 8); + ADD_TO_STACK4(stack, &RectangleType_68K1, 10); + ADD_TO_STACK1(stack, ringStyle, 14); + ADD_TO_STACK1(stack, forceRestore, 16); + STACK_END(stack); + + result = (Err)(*sp->call68KFuncP)( sp->emulStateP, + PceNativeTrapNo(sysTrapNavSelector), + stack, 18 ); + sp->emulStateP->regA[7] -= 2; + } + FUNC_TAIL(FrmNavDrawFocusRing); + return result; +} +#endif + +/* Need to parse the format string */ +Int16 +StrPrintF( Char* s, const Char* formatStr, ... ) +{ + unsigned long* inArgs = ((unsigned long*)&formatStr) + 1; + + return StrVPrintF( s, formatStr, (_Palm_va_list)inArgs ); +} /* StrPrintF */ + +/* from file StringMgr.h */ +Int16 +StrVPrintF( Char* s, const Char* formatStr, _Palm_va_list arg ) +{ + Int16 result; + unsigned long* argv_arm = (unsigned long*)arg; + unsigned char argv_68k[48]; + unsigned short done, isLong, innerDone, useArg; + unsigned char* str = (unsigned char*)formatStr; + unsigned short offset = 0; + + for ( done = 0; !done; ) { + switch( *str++ ) { + case '\0': + done = 1; + break; + case '%': + isLong = useArg = 0; + for( innerDone = 0; !innerDone; ) { + unsigned char nxt = *str++; + switch( nxt ) { + case '%': + innerDone = 1; + break; + case 'l': + isLong = 1; + break; + case 's': + isLong = 1; + case 'd': + case 'x': + innerDone = 1; + useArg = 1; + break; + default: + if ( nxt >= '0' && nxt <= '9' ) { + /* accept %4x */ + } else { + crash(); + } + } + } + + if ( useArg ) { + unsigned long param; + param = *argv_arm++; + if ( isLong ) { + write_unaligned32( &argv_68k[offset], param ); + offset += 4; + } else { + write_unaligned16( &argv_68k[offset], + (unsigned short)param ); + offset += 2; + } + } + break; + } + } + + /* now call the OS.... */ + { + PNOState* sp = GET_CALLBACK_STATE(); + STACK_START(unsigned char, stack, 12); + ADD_TO_STACK4(stack, s, 0); + ADD_TO_STACK4(stack, formatStr, 4); + ADD_TO_STACK4(stack, argv_68k, 8); + STACK_END(stack); + result = (Int16) + (*sp->call68KFuncP)( sp->emulStateP, + // NOT sysTrapStrPrintF !!!! + PceNativeTrapNo(sysTrapStrVPrintF), + stack, 12 ); + } + + return result; +} /* StrVPrintF */ + +/* Events. Need to translate back and forth since the ARM struct looks + * different from the 68K version yet we have to be able to come up with a 68K + * version for the OS at various times. May also need to translate inside + * event handlers since the OS will pass the 68K struct to us there. + */ +#define EVT_DATASIZE_68K 16 /* sez sizeof(event.data) in 68K code */ +void +evt68k2evtARM( EventType* event, const unsigned char* evt68k ) +{ + event->eType = read_unaligned16( evt68k ); + event->penDown = read_unaligned8( evt68k+2 ); + event->tapCount = read_unaligned8( evt68k+3 ); + event->screenX = read_unaligned16( evt68k+4 ); + event->screenY = read_unaligned16( evt68k+6 ); + + evt68k += 8; /* skip to start of data union */ + + switch ( event->eType ) { + case frmLoadEvent: + case frmOpenEvent: + case frmCloseEvent: + case frmUpdateEvent: + XP_ASSERT( &event->data.frmLoad.formID == + &event->data.frmClose.formID ); + event->data.frmLoad.formID = read_unaligned16( evt68k ); + event->data.frmUpdate.updateCode = read_unaligned16( evt68k + 2 ); + break; + case keyDownEvent: + case keyUpEvent: + event->data.keyDown.chr = read_unaligned16( evt68k ); + event->data.keyDown.keyCode = read_unaligned16( evt68k+2 ); + event->data.keyDown.modifiers = read_unaligned16( evt68k+4 ); + break; + + case ctlSelectEvent: + event->data.ctlSelect.controlID = read_unaligned16(evt68k); + event->data.ctlSelect.pControl + = (ControlType*)read_unaligned32(evt68k+2); + event->data.ctlSelect.on = (Boolean)read_unaligned8(evt68k+6); + event->data.ctlSelect.reserved1 = read_unaligned8(evt68k+7); + event->data.ctlSelect.value = read_unaligned16(evt68k+8); + break; + +#ifdef XWFEATURE_FIVEWAY + case frmObjectFocusTakeEvent: + case frmObjectFocusLostEvent: + event->data.frmObjectFocusTake.formID = read_unaligned16(evt68k); + event->data.frmObjectFocusTake.objectID = read_unaligned16(evt68k+2); + event->data.frmObjectFocusTake.dispatchHint = read_unaligned32(evt68k+4); + break; +#endif + + case winExitEvent: + case winEnterEvent: + XP_ASSERT( &event->data.winEnter.enterWindow == + &event->data.winExit.enterWindow ); + event->data.winEnter.enterWindow + = (WinHandle)read_unaligned32( evt68k ); + event->data.winEnter.exitWindow + = (WinHandle)read_unaligned32( evt68k+4 ); + break; + + // case penDownEvent: // stuff above is enough + // case penMoveEvent: // stuff above is enough + // case penUpEvent: // stuff above is enough + case menuEvent: + event->data.menu.itemID = read_unaligned16( evt68k ); + break; + case sclRepeatEvent: + event->data.sclRepeat.scrollBarID = read_unaligned16( evt68k ); + event->data.sclRepeat.pScrollBar + = (ScrollBarType*)read_unaligned32( evt68k+2 ); + event->data.sclRepeat.value = read_unaligned16( evt68k+6 ); + event->data.sclRepeat.newValue = read_unaligned16( evt68k+8 ); + event->data.sclRepeat.time = read_unaligned32( evt68k+10 ); + break; + /* Crosswords events */ + case newGameCancelEvent: + break; + case openSavedGameEvent: + ((OpenSavedGameData*)(&event->data.generic))->newGameIndex = + read_unaligned16( evt68k ); + break; + case newGameOkEvent: + case loadGameEvent: + break; +/* case boardRedrawEvt: */ + // doResizeWinEvent + case prefsChangedEvent: + break; + + default: /* copy the data as binary so we can copy it back later */ + memcpy( &event->data, evt68k, EVT_DATASIZE_68K ); + break; + } +} /* evt68k2evtARM */ + +void +evtArm2evt68K( unsigned char* evt68k, const EventType* event ) +{ + write_unaligned16( evt68k, event->eType ); + write_unaligned8( evt68k + 2, event->penDown ); + write_unaligned8( evt68k + 3, event->tapCount ); + write_unaligned16( evt68k + 4, event->screenX ); + write_unaligned16( evt68k + 6, event->screenY ); + + evt68k += 8; + + switch ( event->eType ) { + case frmLoadEvent: + case frmOpenEvent: + case frmCloseEvent: + case frmUpdateEvent: + XP_ASSERT( &event->data.frmLoad.formID + == &event->data.frmOpen.formID ); + XP_ASSERT( &event->data.frmLoad.formID + == &event->data.frmClose.formID ); + XP_ASSERT( &event->data.frmLoad.formID + == &event->data.frmUpdate.formID ); + write_unaligned16( evt68k, event->data.frmLoad.formID ); + write_unaligned16( evt68k + 2, event->data.frmUpdate.updateCode ); + break; + case keyDownEvent: + case keyUpEvent: + write_unaligned16( evt68k, event->data.keyDown.chr ); + write_unaligned16( evt68k+2, event->data.keyDown.keyCode ); + write_unaligned16( evt68k+4, event->data.keyDown.modifiers ); + break; + + case ctlSelectEvent: + write_unaligned16( evt68k, event->data.ctlSelect.controlID ); + write_unaligned32( evt68k+2, + (unsigned long)event->data.ctlSelect.pControl ); + write_unaligned8( evt68k+6, event->data.ctlSelect.on ); + write_unaligned8( evt68k+7, event->data.ctlSelect.reserved1 ); + write_unaligned16( evt68k+8, event->data.ctlSelect.value ); + break; + +#ifdef XWFEATURE_FIVEWAY + case frmObjectFocusTakeEvent: + case frmObjectFocusLostEvent: + write_unaligned16( evt68k, event->data.frmObjectFocusTake.formID ); + write_unaligned16( evt68k+2, event->data.frmObjectFocusTake.objectID ); + write_unaligned32( evt68k+4, event->data.frmObjectFocusTake.dispatchHint ); + break; +#endif + + case winExitEvent: + case winEnterEvent: + XP_ASSERT( &event->data.winEnter.enterWindow == + &event->data.winExit.enterWindow ); + write_unaligned32( evt68k, + (unsigned long)event->data.winEnter.enterWindow ); + write_unaligned32( evt68k+4, + (unsigned long)event->data.winEnter.exitWindow ); + break; + + // case penDownEvent: // stuff above is enough + // case penMoveEvent: // stuff above is enough + // case penUpEvent: // stuff above is enough + case menuEvent: + write_unaligned16( evt68k, event->data.menu.itemID ); + break; + case sclRepeatEvent: + write_unaligned16( evt68k, event->data.sclRepeat.scrollBarID ); + write_unaligned32( evt68k+2, + (unsigned long)event->data.sclRepeat.pScrollBar ); + write_unaligned16( evt68k+6, event->data.sclRepeat.value ); + write_unaligned16( evt68k+8, event->data.sclRepeat.newValue ); + write_unaligned32( evt68k+10, event->data.sclRepeat.time ); + break; + /* Crosswords events */ + case newGameCancelEvent: + break; + case openSavedGameEvent: + write_unaligned16( evt68k, + ((OpenSavedGameData*) + (&event->data.generic))->newGameIndex ); + break; + case newGameOkEvent: + case loadGameEvent: + break; +/* case boardRedrawEvt: */ + // doResizeWinEvent + case prefsChangedEvent: + break; + + default: /* copy the data as binary so we can copy it back later */ + memcpy( evt68k, &event->data, EVT_DATASIZE_68K ); + break; + } + +} /* evtArm2evt68K */ + +/* from file SystemMgr.h */ +Boolean +SysHandleEvent( EventPtr eventP ) +{ + Boolean result; + EventType event68K; + FUNC_HEADER(SysHandleEvent); + + evtArm2evt68K( (unsigned char*)&event68K, eventP ); + + { + PNOState* sp = GET_CALLBACK_STATE(); + STACK_START(unsigned char, stack, 4); + ADD_TO_STACK4(stack, &event68K, 0); + STACK_END(stack); + result = (Boolean)(*sp->call68KFuncP)( sp->emulStateP, + PceNativeTrapNo(sysTrapSysHandleEvent), + stack, 4 ); + } + FUNC_TAIL(SysHandleEvent); + return result; +} /* SysHandleEvent */ + +unsigned long +handlerEntryPoint( const void* XP_UNUSED(emulStateP), + void* userData68KP, + Call68KFuncType* XP_UNUSED(call68KFuncP) ) +{ + unsigned long* data = (unsigned long*)userData68KP; + FormEventHandlerType* handler + = (FormEventHandlerType*)read_unaligned32( (unsigned char*)&data[0] ); + PNOState* state = getStorageLoc(); + unsigned long oldR10; + EventType evtArm; + Boolean result; + + /* set up stack here too? */ + asm( "mov %0, r10" : "=r" (oldR10) ); + asm( "mov r10, %0" : : "r" (state->gotTable) ); + + evt68k2evtARM( &evtArm, + (unsigned char*)read_unaligned32( (unsigned char*)&data[1]) ); + + result = (*handler)(&evtArm); + + asm( "mov r10, %0" : : "r" (oldR10) ); + + return (unsigned long)result; +} + +/* The stub wants to look like this: + static Boolean + FormEventHandlerType( EventType *eventP ) + { + unsigned long data[] = { armEvtHandler, eventP }; + return (Boolean)PceNativeCall( handlerEntryPoint, (void*)data ); + } + */ +static unsigned char* +makeHandlerStub( FormEventHandlerType* handlerArm ) +{ + unsigned char* stub; + unsigned char code_68k[] = { + /* 0:*/ 0x4e, 0x56, 0xff, 0xf8, // linkw %fp,#-8 + /* 4:*/ 0x20, 0x2e, 0x00, 0x08, // movel %fp@(8),%d0 + /* 8:*/ 0x2d, 0x7c, 0x11, 0x22, 0x33, 0x44, // movel #287454020,%fp@(-8) + /*14:*/ 0xff, 0xf8, // ????? REQUIRED!!!! + /*16:*/ 0x2d, 0x40, 0xff, 0xfc, // movel %d0,%fp@(-4) + /*20:*/ 0x48, 0x6e, 0xff, 0xf8, // pea %fp@(-8) + /*24:*/ 0x2f, 0x3c, 0x55, 0x66, 0x77, 0x88, // movel #1432778632,%sp@- + /*30:*/ 0x4e, 0x4f, // trap #15 + /*32:*/ 0xa4, 0x5a, // 0122132 + /*34:*/ 0x02, 0x40, 0x00, 0xff, // andiw #255,%d0 + /*38:*/ 0x4e, 0x5e, // unlk %fp + /*40:*/ 0x4e, 0x75 // rts + }; + + stub = MemPtrNew( sizeof(code_68k) ); + memcpy( stub, code_68k, sizeof(code_68k) ); + + write_unaligned32( &stub[10], + /* replace 0x11223344 */ + (unsigned long)handlerArm ); + write_unaligned32( &stub[26], + /* replace 0x55667788 */ + (unsigned long)handlerEntryPoint ); + /* Need to register this stub so it can be freed (once leaking ceases to + be ok on PalmOS) */ + + return (unsigned char*)stub; +} /* makeHandlerStub */ + +void +FrmSetEventHandler( FormType* formP, FormEventHandlerType* handler ) +{ + FUNC_HEADER(FrmSetEventHandler); + { + PNOState* sp = GET_CALLBACK_STATE(); + unsigned char* stub = makeHandlerStub( handler ); + STACK_START(unsigned char, stack, 8); + ADD_TO_STACK4(stack, formP, 0); + ADD_TO_STACK4(stack, stub, 4); + STACK_END(stack); + (*sp->call68KFuncP)( sp->emulStateP, + PceNativeTrapNo(sysTrapFrmSetEventHandler), + stack, 8 ); + } + FUNC_TAIL(FrmSetEventHandler); +} /* FrmSetEventHandler */ + +void +flipRect( RectangleType* rout, const RectangleType* rin ) +{ + rout->topLeft.x = Byte_Swap16(rin->topLeft.x); + rout->topLeft.y = Byte_Swap16(rin->topLeft.y); + rout->extent.x = Byte_Swap16(rin->extent.x); + rout->extent.y = Byte_Swap16(rin->extent.y); +} /* flipRect */ + +void +flipEngSocketFromArm( unsigned char* sout, const ExgSocketType* sin ) +{ + write_unaligned16( &sout[0], sin->libraryRef ); // UInt16 libraryRef; + write_unaligned32( &sout[2], sin->socketRef ); // UInt32 socketRef; + write_unaligned32( &sout[6], sin->target ); // UInt32 target; + write_unaligned32( &sout[10], sin->count ); // UInt32 count; + + write_unaligned32( &sout[14], sin->length ); // UInt32 length; + + write_unaligned32( &sout[18], sin->time ); // UInt32 time; + write_unaligned32( &sout[22], sin->appData ); // UInt32 appData; + write_unaligned32( &sout[26], sin->goToCreator ); // UInt32 goToCreator; + write_unaligned16( &sout[30], sin->goToParams.dbCardNo ); // UInt16 goToParams.dbCardNo; + write_unaligned32( &sout[32], sin->goToParams.dbID ); // LocalID goToParams.dbID; + write_unaligned16( &sout[36], sin->goToParams.recordNum ); // UInt16 goToParams.recordNum; + write_unaligned32( &sout[38], sin->goToParams.uniqueID ); // UInt32 goToParams.uniqueID; + write_unaligned32( &sout[42], sin->goToParams.matchCustom ); // UInt32 goToParams.matchCustom; + /* bitfield. All we can do is copy the whole thing, assuming it's 16 + bits, and pray that no arm code wants to to use it. */ + write_unaligned16( &sout[46], *(UInt16*)((unsigned char*)&sin->goToParams.matchCustom) + + sizeof(sin->goToParams.matchCustom) ); + write_unaligned32( &sout[48], (unsigned long)sin->description ); // Char *description; + write_unaligned32( &sout[52], (unsigned long)sin->type ); // Char *type; + write_unaligned32( &sout[56], (unsigned long)sin->name ); // Char *name; +} /* flipEngSocketFromArm */ + +void +flipEngSocketToArm( ExgSocketType* sout, const unsigned char* sin ) +{ + sout->libraryRef = read_unaligned16( &sin[0] ); + sout->socketRef = read_unaligned32( &sin[2] ); + sout->target = read_unaligned32( &sin[6] ); + sout->count = read_unaligned32( &sin[10] ); + sout->length = read_unaligned32( &sin[14] ); + sout->time = read_unaligned32( &sin[18] ); + sout->appData = read_unaligned32( &sin[22] ); + sout->goToCreator = read_unaligned32( &sin[26] ); + sout->goToParams.dbCardNo = read_unaligned16( &sin[30] ); + sout->goToParams.dbID = read_unaligned32( &sin[32] ); + sout->goToParams.recordNum = read_unaligned16( &sin[36] ); + sout->goToParams.uniqueID = read_unaligned32( &sin[38] ); + sout->goToParams.matchCustom = read_unaligned32( &sin[42] ); + /* bitfield. All we can do is copy the whole thing, assuming it's 16 + bits, and pray that no arm code wants to to use it. */ + *(UInt16*)(((unsigned char*)&sout->goToParams.matchCustom) + + sizeof(sout->goToParams.matchCustom)) = read_unaligned16( &sin[46] ); + sout->description = (Char*)read_unaligned32( &sin[48] ); + sout->type = (Char*)read_unaligned32( &sin[52] ); + sout->name = (Char*)read_unaligned32( &sin[56] ); +} /* flipEngSocketToArm */ + +void +flipFileInfoFromArm( unsigned char* fiout, const FileInfoType* fiin ) +{ + write_unaligned32( &fiout[0], fiin->attributes ); + write_unaligned32( &fiout[4], (unsigned long)fiin->nameP ); + write_unaligned16( &fiout[8], fiin->nameBufLen ); +} + +void +flipFileInfoToArm( FileInfoType* fout, const unsigned char* fin ) +{ + fout->attributes = read_unaligned32( &fin[0] ); + fout->nameP = (Char*)read_unaligned32( &fin[4] ); + fout->nameBufLen = read_unaligned16( &fin[8] ); +} /* flipFileInfo */ + +/* from file List.h */ +void +LstSetListChoices( ListType* listP, Char** itemsText, Int16 numItems ) +{ + FUNC_HEADER(LstSetListChoices); + /* var decls */ + /* swapIns */ + { + XP_U16 i; + PNOState* sp = GET_CALLBACK_STATE(); + STACK_START(unsigned char, stack, 10); + /* pushes */ + ADD_TO_STACK4(stack, listP, 0); + ADD_TO_STACK4(stack, itemsText, 4); + ADD_TO_STACK2(stack, numItems, 8); + STACK_END(stack); + + for ( i = 0; i < numItems; ++i ) { + itemsText[i] = (Char*)Byte_Swap32( (unsigned long)itemsText[i] ); + } + + (*sp->call68KFuncP)( sp->emulStateP, + PceNativeTrapNo(sysTrapLstSetListChoices), + stack, 10 ); + /* swapOuts */ + } + FUNC_TAIL(LstSetListChoices); + EMIT_NAME("LstSetListChoices","'L','s','t','S','e','t','L','i','s','t','C','h','o','i','c','e','s'"); +} /* LstSetListChoices */ + +static void +params68KtoParamsArm( SysNotifyParamType* paramsArm, + const unsigned char* params68K ) +{ + paramsArm->notifyType = read_unaligned32( ¶ms68K[0] ); + paramsArm->broadcaster = read_unaligned32( ¶ms68K[4] ); + paramsArm->notifyDetailsP = (void*)read_unaligned32( ¶ms68K[8] ); + paramsArm->userDataP = (void*)read_unaligned32( ¶ms68K[12] ); + paramsArm->handled = read_unaligned8( ¶ms68K[16] ); + + /* I don't do anything with the data passed in, so no need to swap it... + But that'd change for others: make an ARM-corrected copy of the + contents of notifyDetailsP if your handler will use it. */ + switch( paramsArm->notifyType ) { + case sysNotifyVolumeUnmountedEvent: + case sysNotifyVolumeMountedEvent: + break; +#ifdef FEATURE_SILK + case sysNotifyDisplayChangeEvent: + break; +#endif + } + +} /* params68KtoParamsArm */ + +static void +paramsArmtoParams68K( unsigned char* params68K, + const SysNotifyParamType* armParams ) +{ + write_unaligned8( ¶ms68K[16], armParams->handled ); +} /* paramsArmtoParams68K */ + +static unsigned long +notifyEntryPoint( const void* XP_UNUSED_DBG(emulStateP), + void* userData68KP, + Call68KFuncType* XP_UNUSED_DBG(call68KFuncP) ) +{ + unsigned long* data = (unsigned long*)userData68KP; + SysNotifyProcPtr callback + = (SysNotifyProcPtr)read_unaligned32( (unsigned char*)&data[0] ); + SysNotifyParamType armParams; + PNOState* state = getStorageLoc(); + unsigned long oldR10; + unsigned char* params68K; + Err result; + + /* set up stack here too? */ + asm( "mov %0, r10" : "=r" (oldR10) ); + asm( "mov r10, %0" : : "r" (state->gotTable) ); + + XP_ASSERT( emulStateP == state->emulStateP ); + XP_ASSERT( call68KFuncP == state->call68KFuncP ); + + params68K = (unsigned char*)read_unaligned32((unsigned char*)&data[1]); + params68KtoParamsArm( &armParams, params68K ); + + result = (*callback)(&armParams); + + /* at least need to write 'handled' back out... */ + paramsArmtoParams68K( params68K, &armParams ); + + asm( "mov r10, %0" : : "r" (oldR10) ); + + return (unsigned long)result; +} /* notifyEntryPoint */ + +/* The stub wants to look like this: + static Err SysNotifyProc(SysNotifyParamType *notifyParamsP) + { + unsigned long data[] = { armNotifyHandler, notifyParamsP }; + return (Err)PceNativeCall( handlerEntryPoint, (void*)data ); + } + */ +static unsigned char* +makeNotifyStub( SysNotifyProcPtr callback ) +{ + unsigned char* stub; + unsigned char code_68k[] = { + /* 0:*/ 0x4e, 0x56, 0xff, 0xf8, // linkw %fp,#-8 + /* 4:*/ 0x20, 0x2e, 0x00, 0x08, // movel %fp@(8),%d0 + /* 8:*/ 0x2d, 0x7c, 0x11, 0x22, 0x33, 0x44, // movel #287454020,%fp@(-8) + /*14:*/ 0xff, 0xf8, // ????? REQUIRED!!!! + /*16:*/ 0x2d, 0x40, 0xff, 0xfc, // movel %d0,%fp@(-4) + /*20:*/ 0x48, 0x6e, 0xff, 0xf8, // pea %fp@(-8) + /*24:*/ 0x2f, 0x3c, 0x55, 0x66, 0x77, 0x88, // movel #1432778632,%sp@- + /*30:*/ 0x4e, 0x4f, // trap #15 + /*32:*/ 0xa4, 0x5a, // 0122132 + /*34:*/ 0x4e, 0x5e, // unlk %fp + /*36:*/ 0x4e, 0x75 // rts + }; + + stub = MemPtrNew( sizeof(code_68k) ); + memcpy( stub, code_68k, sizeof(code_68k) ); + + write_unaligned32( &stub[10], + /* replace 0x11223344 */ + (unsigned long)callback ); + write_unaligned32( &stub[26], + /* replace 0x55667788 */ + (unsigned long)notifyEntryPoint ); + /* Need to register this stub so it can be freed (once leaking ceases to + be ok on PalmOS) */ + + return (unsigned char*)stub; +} /* makeNotifyStub */ + +/* from file NotifyMgr.h */ +Err +SysNotifyRegister( UInt16 cardNo, LocalID dbID, UInt32 notifyType, + SysNotifyProcPtr callbackP, Int8 priority, void* userDataP ) +{ + Err result; + FUNC_HEADER(SysNotifyRegister); + /* var decls */ + /* swapIns */ + { + PNOState* sp = GET_CALLBACK_STATE(); + unsigned char* stub = makeNotifyStub( callbackP ); + STACK_START(unsigned char, stack, 20); + /* pushes */ + ADD_TO_STACK2(stack, cardNo, 0); + ADD_TO_STACK4(stack, dbID, 2); + ADD_TO_STACK4(stack, notifyType, 6); + ADD_TO_STACK4(stack, stub, 10); + ADD_TO_STACK1(stack, priority, 14); + ADD_TO_STACK4(stack, userDataP, 16); + STACK_END(stack); + result = (Err)(*sp->call68KFuncP)( sp->emulStateP, + PceNativeTrapNo(sysTrapSysNotifyRegister), + stack, 20 ); + /* swapOuts */ + } + FUNC_TAIL(SysNotifyRegister); + EMIT_NAME("SysNotifyRegister","'S','y','s','N','o','t','i','f','y','R','e','g','i','s','t','e','r'"); + return result; +} /* SysNotifyRegister */ + +unsigned long +listDrawEntryPoint( const void* XP_UNUSED_DBG(emulStateP), + void* userData68KP, + Call68KFuncType* XP_UNUSED_DBG(call68KFuncP) ) +{ + unsigned long* data = (unsigned long*)userData68KP; + ListDrawDataFuncPtr listDrawProc + = (ListDrawDataFuncPtr)read_unaligned32( (unsigned char*)&data[0] ); + PNOState* state = getStorageLoc(); + unsigned long oldR10; + Int16 index; + char** itemsText; + RectangleType rectArm; + + /* set up stack here too? */ + asm( "mov %0, r10" : "=r" (oldR10) ); + asm( "mov r10, %0" : : "r" (state->gotTable) ); + + flipRect( &rectArm, (RectanglePtr)read_unaligned32( (unsigned char*)&data[2] ) ); + + XP_ASSERT( emulStateP == state->emulStateP ); + XP_ASSERT( call68KFuncP == state->call68KFuncP ); + + index = (Int16)read_unaligned32( (unsigned char*)&data[1] ); + itemsText = (char**)read_unaligned32( (unsigned char*)&data[3] ); + (*listDrawProc)( index, &rectArm, itemsText ); + + asm( "mov r10, %0" : : "r" (oldR10) ); + + return 0L; /* no result to return */ +} /* listDrawEntryPoint */ + +static unsigned char* +makeListDrawStub( ListDrawDataFuncPtr func ) +{ +/* called function looks like this: + void listDrawFunc(Int16 index, RectanglePtr bounds, char** itemsText) + { + unsigned long data[] = { func, index, + bounds, itemsText }; + return (Err)PceNativeCall( listDrawEntryPoint, (void*)data ); + } + */ + unsigned char* stub; + unsigned char code_68k[] = { + /* 0:*/ 0x4e, 0x56, 0xff, 0xf0, // linkw %fp,#-16 + /* 4:*/ 0x30, 0x2e, 0x00, 0x08, // movew %fp@(8),%d0 + /* 8:*/ 0x22, 0x2e, 0x00, 0x0a, // movel %fp@(10),%d1 + /* c:*/ 0x24, 0x2e, 0x00, 0x0e, // movel %fp@(14),%d2 + /*10:*/ 0x2d, 0x7c, 0x11, 0x22,0x33,0x44,// movel #287454020,%fp@(-16) + /*16:*/ 0xff, 0xf0, + /*18:*/ 0x30, 0x40, // moveaw %d0,%a0 + /*1a:*/ 0x2d, 0x48, 0xff, 0xf4, // movel %a0,%fp@(-12) + /*1e:*/ 0x2d, 0x41, 0xff, 0xf8, // movel %d1,%fp@(-8) + /*22:*/ 0x2d, 0x42, 0xff, 0xfc, // movel %d2,%fp@(-4) + /*26:*/ 0x48, 0x6e, 0xff, 0xf0, // pea %fp@(-16) + /*2a:*/ 0x2f, 0x3c, 0x55, 0x66, 0x77, 0x88, // movel #1432778632,%sp@- + /*30:*/ 0x4e, 0x4f, // trap #15 + /*32:*/ 0xa4, 0x5a, // 0122132 + /*34:*/ 0x4e, 0x5e, // unlk %fp + /*36:*/ 0x4e, 0x75 // rts + }; + stub = MemPtrNew( sizeof(code_68k) ); + memcpy( stub, code_68k, sizeof(code_68k) ); + + write_unaligned32( &stub[0x12], + /* replace 0x11223344 */ + (unsigned long)func ); + write_unaligned32( &stub[0x2c], + /* replace 0x55667788 */ + (unsigned long)listDrawEntryPoint ); + + return (unsigned char*)stub; +} /* makeListDrawStub */ + +/* from file List.h */ +void +LstSetDrawFunction( ListType* listP, ListDrawDataFuncPtr func ) +{ + FUNC_HEADER(LstSetDrawFunction); + /* var decls */ + /* swapIns */ + { + PNOState* sp = GET_CALLBACK_STATE(); + unsigned char* stub = makeListDrawStub( func ); + STACK_START(unsigned char, stack, 8); + /* pushes */ + ADD_TO_STACK4(stack, listP, 0); + ADD_TO_STACK4(stack, stub, 4); + STACK_END(stack); + (*sp->call68KFuncP)( sp->emulStateP, + PceNativeTrapNo(sysTrapLstSetDrawFunction), + stack, 8 ); + /* swapOuts */ + } + FUNC_TAIL(LstSetDrawFunction); + EMIT_NAME("LstSetDrawFunction","'L','s','t','S','e','t','D','r','a','w','F','u','n','c','t','i','o','n'"); +} /* LstSetDrawFunction */ + +void +flipDateTimeToArm( DateTimeType* out, const unsigned char* in ) +{ + const DateTimeType* inp = (DateTimeType*)in; + out->second = Byte_Swap16( inp->second ); + out->minute = Byte_Swap16( inp->minute ); + out->hour = Byte_Swap16( inp->hour ); + out->day = Byte_Swap16( inp->day ); + out->month = Byte_Swap16( inp->month ); + out->year = Byte_Swap16( inp->year ); + out->weekDay = Byte_Swap16( inp->weekDay ); +} + +/* from file NetMgr.h */ +NetHostInfoPtr +NetLibGetHostByName( UInt16 libRefNum, const Char* nameP, + NetHostInfoBufPtr bufP, Int32 timeout, Err* errP ) +{ + NetHostInfoPtr result; + FUNC_HEADER(NetLibGetHostByName); + /* var decls */ + /* swapIns */ + { + PNOState* sp = GET_CALLBACK_STATE(); + STACK_START(unsigned char, stack, 18); + /* pushes */ + ADD_TO_STACK2(stack, libRefNum, 0); + ADD_TO_STACK4(stack, nameP, 2); + ADD_TO_STACK4(stack, bufP, 6); + ADD_TO_STACK4(stack, timeout, 10); + ADD_TO_STACK4(stack, errP, 14); + STACK_END(stack); + result = (NetHostInfoPtr) + (*sp->call68KFuncP)( sp->emulStateP, + PceNativeTrapNo(netLibTrapGetHostByName), + stack, kPceNativeWantA0 | 18 ); + /* swapOuts */ + + if ( result != NULL ) { + result->nameP = Byte_Swap32( result->nameP ); + result->nameAliasesP = Byte_Swap32( result->nameAliasesP ); + result->addrType = Byte_Swap16( result->addrType ); + result->addrLen = Byte_Swap16( result->addrLen ); + result->addrListP = Byte_Swap32( result->addrListP ); + + /* we'll use only the first */ + result->addrListP[0] = Byte_Swap32( result->addrListP[0] ); + *(XP_U32*)result->addrListP[0] = + Byte_Swap32( *(XP_U32*)result->addrListP[0] ); + } + } + FUNC_TAIL(NetLibGetHostByName); + EMIT_NAME("NetLibGetHostByName","'N','e','t','L','i','b','G','e','t','H','o','s','t','B','y','N','a','m','e'"); + return result; +} /* NetLibGetHostByName */ + + +static unsigned long +exgWriteEntry( const void* XP_UNUSED_DBG(emulStateP), + void* userData68KP, + Call68KFuncType* XP_UNUSED_DBG(call68KFuncP) ) +{ + unsigned long* data = (unsigned long*)userData68KP; + unsigned long oldR10; + ExgDBWriteProcPtr exgProc + = (ExgDBWriteProcPtr)read_unaligned32( (unsigned char*)&data[0] ); + PNOState* state = getStorageLoc(); + UInt32* sizeP; + Err err; + + /* set up stack here too? */ + asm( "mov %0, r10" : "=r" (oldR10) ); + asm( "mov r10, %0" : : "r" (state->gotTable) ); + + XP_ASSERT( emulStateP == state->emulStateP ); + XP_ASSERT( call68KFuncP == state->call68KFuncP ); + + sizeP = (UInt32*)read_unaligned32( (unsigned char*)&data[2] ); + SWAP4_NON_NULL_OUT(sizeP); + err = (*exgProc)( (const void*)read_unaligned32((unsigned char*)&data[1]), + sizeP, + (void*)read_unaligned32( (unsigned char*)&data[3] ) ); + SWAP4_NON_NULL_IN(sizeP); + + asm( "mov r10, %0" : : "r" (oldR10) ); + + return (unsigned long)err; /* no result to return */ +} /* exgWriteEntry */ + +static void +makeExgWriteStub( ExgDBWriteProcPtr proc, unsigned char* stub, + XP_U16 XP_UNUSED_DBG(stubSize) ) +{ +/* Err ExgDBWriteProc( const void *dataP, UInt32 *sizeP, void *userDataP) */ +/* { */ +/* unsigned long data[] = { */ +/* (unsigned long)0x11223344, */ +/* (unsigned long)dataP, */ +/* (unsigned long)sizeP, */ +/* (unsigned long)userDataP */ +/* }; */ +/* return (Err)PceNativeCall( (void*)0x55667788, (void*)data ); */ +/* } */ + unsigned char code_68k[] = { + /* 0:*/ 0x4e, 0x56, 0xff, 0xf0, /* linkw %fp,#-16 */ + /* 4:*/ 0x20, 0x2e, 0x00, 0x08, /* movel %fp@(8),%d0 */ + /* 8:*/ 0x22, 0x2e, 0x00, 0x0c, /* movel %fp@(12),%d1 */ + /* c:*/ 0x24, 0x2e, 0x00, 0x10, /* movel %fp@(16),%d2 */ + /*10:*/ 0x2d, 0x7c, 0x11, 0x22, 0x33, 0x44,/*movel #287454020,%fp@(-16)*/ + /*16:*/ 0xff, 0xf0, + /*18:*/ 0x2d, 0x40, 0xff, 0xf4, /* movel %d0,%fp@(-12) */ + /*1c:*/ 0x2d, 0x41, 0xff, 0xf8, /* movel %d1,%fp@(-8) */ + /*20:*/ 0x2d, 0x42, 0xff, 0xfc, /* movel %d2,%fp@(-4) */ + /*24:*/ 0x48, 0x6e, 0xff, 0xf0, /* pea %fp@(-16) */ + /*28:*/ 0x2f, 0x3c, 0x55, 0x66, 0x77, 0x88, /* movel #1432778632,%sp@- */ + /*2e:*/ 0x4e, 0x4f, /* trap #15 */ + /*30:*/ 0xa4, 0x5a, /* 0122132 */ + /*32:*/ 0x4e, 0x5e, /* unlk %fp */ + /*34:*/ 0x4e, 0x75 /* rts */ + }; + XP_ASSERT( sizeof(code_68k) <= stubSize ); + memcpy( stub, code_68k, sizeof(code_68k) ); + + write_unaligned32( &stub[0x12], + /* replace 0x11223344 */ + (unsigned long)proc ); + write_unaligned32( &stub[0x2a], + /* replace 0x55667788 */ + (unsigned long)exgWriteEntry ); +} /* makeExgWriteStub */ + +/* from file ExgMgr.h */ +Err +ExgDBWrite( ExgDBWriteProcPtr writeProcP, void* userDataP, + const char* nameP, LocalID dbID, UInt16 cardNo ) +{ + Err result; + FUNC_HEADER(ExgDBWrite); + /* var decls */ + /* swapIns */ + { + PNOState* sp = GET_CALLBACK_STATE(); + unsigned char stub[0x36]; + STACK_START(unsigned char, stack, 18); + makeExgWriteStub( writeProcP, stub, sizeof(stub) ); + + /* pushes */ + ADD_TO_STACK4(stack, stub, 0); + ADD_TO_STACK4(stack, userDataP, 4); + ADD_TO_STACK4(stack, nameP, 8); + ADD_TO_STACK4(stack, dbID, 12); + ADD_TO_STACK2(stack, cardNo, 16); + STACK_END(stack); + result = (Err)(*sp->call68KFuncP)( sp->emulStateP, + PceNativeTrapNo(sysTrapExgDBWrite), + stack, 18 ); + /* swapOuts */ + } + FUNC_TAIL(ExgDBWrite); + EMIT_NAME("ExgDBWrite","'E','x','g','D','B','W','r','i','t','e'"); + return result; +} /* ExgDBWrite */ + +#ifdef XWFEATURE_BLUETOOTH +static void +btLibManagementEventType68K_TO_ARM( BtLibManagementEventType* out, + const unsigned char* in ) +{ + BtLibManagementEventEnum event + = (BtLibManagementEventEnum)read_unaligned8( &in[0] ); + out->event = event; + out->status = read_unaligned16( &in[2] ); + switch( event ) { + case btLibManagementEventACLConnectInbound: + case btLibManagementEventACLDisconnect: + memcpy( &out->eventData.bdAddr, &in[4], sizeof(out->eventData.bdAddr) ); + break; + case btLibManagementEventAccessibilityChange: + out->eventData.accessible + = (BtLibAccessibleModeEnum)read_unaligned8( &in[4] ); + break; + } +} + +static unsigned long +btLibManagementProcArmEntry( const void* emulStateP, + void* userData68KP, + Call68KFuncType* XP_UNUSED_DBG(call68KFuncP) ) +{ + unsigned long* data; + BtLibManagementProcPtr procPtr; + PNOState* state; + unsigned long oldR10; + BtLibManagementEventType mEvent; + const BtLibManagementEventType* mEventP; + UInt32 refCon; + + data = (unsigned long*)userData68KP; + state = getStorageLoc(); + + /* This won't be true if we're called in somebody else's context */ + if ( emulStateP == state->emulStateP ) { + /* set up stack here too? */ + asm( "mov %0, r10" : "=r" (oldR10) ); + asm( "mov r10, %0" : : "r" (state->gotTable) ); + + XP_ASSERT( emulStateP == state->emulStateP ); /* seems to fire when call's + incoming (or maybe: a + system alert is up). + What to do? */ + XP_ASSERT( call68KFuncP == state->call68KFuncP ); + + procPtr = (BtLibManagementProcPtr)read_unaligned32( &data[0] ); + mEventP = read_unaligned32( &data[1] ); + btLibManagementEventType68K_TO_ARM( &mEvent, (unsigned char*)mEventP ); + refCon = read_unaligned32( (unsigned char*)&data[2] ); + (*procPtr)( &mEvent, refCon ); + + asm( "mov r10, %0" : : "r" (oldR10) ); + } + + return 0L; /* no result to return */ +} /* btLibManagementProcArmEntry */ + +/* BtLibSocketProcPtr and BtLibManagementProc have the same signatures as far + as 68K code goes: */ +/* static void BtLibManagementProc( BtLibManagementEventType *mEvent, UInt32 refCon ) */ +/* { */ +/* unsigned long data[] = { callbackP, mEvent, refCon }; */ +/* (void)PceNativeCall( btLibManagementProcArmEntry, (void*)data ); */ +/* } */ + +static unsigned char* +make44stub( void* callbackP, NativeFuncType armFunc ) +{ + unsigned char* stub; + unsigned char code_68k[] = { + /* 0 */ 0x4e, 0x56, 0xff, 0xf4, // linkw %fp,#-12 + /* 4 */ 0x20, 0x2e, 0x00, 0x08, // movel %fp@(8),%d0 + /* 8 */ 0x22, 0x2e, 0x00, 0x0c, // movel %fp@(12),%d1 + /* C */ 0x2d, 0x7c, 0x11, 0x22, 0x33, 0x44, // movel #287454020,%fp@(-12) + /* 12 */ 0xff, 0xf4, + /* 14 */ 0x2d, 0x40, 0xff, 0xf8, // movel %d0,%fp@(-8) + /* 18 */ 0x2d, 0x41, 0xff, 0xfc, // movel %d1,%fp@(-4) + /* 1C */ 0x48, 0x6e, 0xff, 0xf4, // pea %fp@(-12) + /* 20 */ 0x2f, 0x3c, 0x55, 0x66, 0x77, 0x88, // movel #1432778632,%sp@- + /* 26 */ 0x4e, 0x4f, // trap #15 + /* 28 */ 0xa4, 0x5a, // 0122132 + /* 2A */ 0x50, 0x8f, // addql #8,%sp + /* 2C */ 0x4e, 0x5e, // unlk %fp + /* 2E */ 0x4e, 0x75 // rts + }; + + stub = MemPtrNew( sizeof(code_68k) ); + memcpy( stub, code_68k, sizeof(code_68k) ); + + write_unaligned32( &stub[0x0E], + /* replace 0x11223344 */ + (unsigned long)callbackP ); + write_unaligned32( &stub[0x22], + /* replace 0x55667788 */ + (unsigned long)armFunc ); + + return stub; +} /* findOrMakeBTProcStub */ + +static void +btLibSocketEventType68K_TO_ARM( BtLibSocketEventType* out, const unsigned char* in ) +{ + BtLibSocketEventEnum event = read_unaligned8( &in[0] ); /* enum? */ + out->event = event; + out->socket = read_unaligned16( &in[2] ); + out->status = read_unaligned16( &in[4] ); + + switch( event ) { + case btLibSocketEventConnectedInbound: + out->eventData.newSocket = read_unaligned16( &in[6] ); + break; + case btLibSocketEventData: + out->eventData.data.dataLen = read_unaligned16( &in[6] ); + out->eventData.data.data = read_unaligned32( &in[8] ); + break; + case btLibSocketEventSdpGetPsmByUuid: + out->eventData.sdpByUuid.param.psm = read_unaligned16( &in[10] ); + break; + + /* These use status and socket only (if anything) */ + case btLibSocketEventConnectRequest: + case btLibSocketEventConnectedOutbound: + case btLibSocketEventSendComplete: + case btLibSocketEventDisconnected: + case btLibL2DiscConnPsmUnsupported: + break; + default: /* shut up, compiler */ + XP_ASSERT(0); + break; + } +} + +static unsigned long +btSocketProcArmEntry( const void* emulStateP, void* userData68KP, + Call68KFuncType* XP_UNUSED_DBG(call68KFuncP) ) +{ + BtLibSocketEventType sEvent; + BtLibSocketEventType* sEventP; + UInt32 refCon; + + unsigned long* data = (unsigned long*)userData68KP; + unsigned long oldR10; + BtLibSocketProcPtr procPtr + = (BtLibSocketProcPtr)read_unaligned32( (unsigned char*)&data[0] ); + PNOState* state = getStorageLoc(); + + /* This won't be true if we're called in somebody else's context */ + if ( emulStateP == state->emulStateP ) { + + /* set up stack here too? */ + asm( "mov %0, r10" : "=r" (oldR10) ); + asm( "mov r10, %0" : : "r" (state->gotTable) ); + + XP_ASSERT( call68KFuncP == state->call68KFuncP ); + + sEventP = (BtLibSocketEventType*) + read_unaligned32( (unsigned char*)&data[1] ); + btLibSocketEventType68K_TO_ARM( &sEvent, (unsigned char*)sEventP ); + refCon = read_unaligned32( (unsigned char*)&data[2] ); + (*procPtr)( &sEvent, refCon ); + + asm( "mov r10, %0" : : "r" (oldR10) ); + } + return 0L; /* no result to return */ +} /* btSocketProcArmEntry */ + +/* from file BtLib.h */ +Err +BtLibRegisterManagementNotification( UInt16 btLibRefNum, + BtLibManagementProcPtr callbackP, + UInt32 refCon ) +{ + Err result; + FUNC_HEADER(BtLibRegisterManagementNotification); + /* var decls */ + /* swapIns */ + { + PNOState* sp = GET_CALLBACK_STATE(); + + unsigned char* stub = make44stub( callbackP, btLibManagementProcArmEntry ); + result = FtrSet( APPID, PACE_BT_CBK_FEATURE, stub ); + XP_LOGF( "%s: stub=%lx", __func__, stub ); + + STACK_START(unsigned char, stack, 10); + /* pushes */ + ADD_TO_STACK2(stack, btLibRefNum, 0); + ADD_TO_STACK4(stack, stub, 2); + ADD_TO_STACK4(stack, refCon, 6); + STACK_END(stack); + result = (Err)(*sp->call68KFuncP)( + sp->emulStateP, + PceNativeTrapNo(btLibTrapRegisterManagementNotification), + stack, 10 ); + /* swapOuts */ + } + FUNC_TAIL(BtLibRegisterManagementNotification); + EMIT_NAME("BtLibRegisterManagementNotification","'B','t','L','i','b','R','e','g','i','s','t','e','r','M','a','n','a','g','e','m','e','n','t','N','o','t','i','f','i','c','a','t','i','o','n'"); + return result; +} /* BtLibRegisterManagementNotification */ + +/* from file BtLib.h */ +Err +BtLibUnregisterManagementNotification( + UInt16 btLibRefNum, + BtLibManagementProcPtr XP_UNUSED_DBG(callbackP) ) +{ + Err result; + unsigned char* stub = NULL; + + FUNC_HEADER(BtLibUnregisterManagementNotification); + + result = FtrGet( APPID, PACE_BT_CBK_FEATURE, (UInt32*)&stub ); + XP_ASSERT( !!stub && errNone == result ); + XP_LOGF( "%s: stub=%lx", __func__, stub ); + +#ifdef DEBUG + { + XP_U32 bkwrds; + write_unaligned32( &bkwrds, callbackP ); + XP_ASSERT( 0 == XP_MEMCMP( &bkwrds, &stub[0x0E], sizeof(callbackP) ) ); + } +#endif + /* var decls */ + /* swapIns */ + { + PNOState* sp = GET_CALLBACK_STATE(); + STACK_START(unsigned char, stack, 6); + /* pushes */ + ADD_TO_STACK2(stack, btLibRefNum, 0); + ADD_TO_STACK4(stack, stub, 2); + STACK_END(stack); + result = (Err)(*sp->call68KFuncP)( sp->emulStateP, + PceNativeTrapNo(btLibTrapUnregisterManagementNotification), + stack, 6 ); + /* swapOuts */ + } + MemPtrFree( stub ); + XP_LOGF( "%s: stub freed", __func__ ); + + FUNC_TAIL(BtLibUnregisterManagementNotification); + EMIT_NAME("BtLibUnregisterManagementNotification","'B','t','L','i','b','U','n','r','e','g','i','s','t','e','r','M','a','n','a','g','e','m','e','n','t','N','o','t','i','f','i','c','a','t','i','o','n'"); + return result; +} /* BtLibUnregisterManagementNotification */ + +/* from file BtLib.h */ +Err +BtLibSocketCreate( UInt16 btLibRefNum, BtLibSocketRef* socketRefP, + BtLibSocketProcPtr callbackP, UInt32 refCon, + BtLibProtocolEnum socketProtocol ) +{ + Err result; + FUNC_HEADER(BtLibSocketCreate); + /* var decls */ + /* swapIns */ + /* SWAP2_NON_NULL_IN(socketRefP); */ + { + PNOState* sp = GET_CALLBACK_STATE(); + unsigned char* stub = make44stub( callbackP, btSocketProcArmEntry ); + + STACK_START(unsigned char, stack, 16); + /* pushes */ + ADD_TO_STACK2(stack, btLibRefNum, 0); + ADD_TO_STACK4(stack, socketRefP, 2); + ADD_TO_STACK4(stack, stub, 6); + ADD_TO_STACK4(stack, refCon, 10); + ADD_TO_STACK1(stack, socketProtocol, 14); + STACK_END(stack); + result = (Err) + (*sp->call68KFuncP)( sp->emulStateP, + PceNativeTrapNo(btLibTrapSocketCreate), + stack, 16 ); + /* swapOuts */ + SWAP2_NON_NULL_OUT(socketRefP); + } + FUNC_TAIL(BtLibSocketCreate); + EMIT_NAME("BtLibSocketCreate","'B','t','L','i','b','S','o','c','k','e','t','C','r','e','a','t','e'"); + return result; +} /* BtLibSocketCreate */ + +void +flipBtConnInfoArm268K( unsigned char* out, const BtLibSocketConnectInfoType* in ) +{ + write_unaligned32( out + 0, in->remoteDeviceP ); +#if defined BT_USE_L2CAP + write_unaligned16( out + 4, in->data.L2Cap.remotePsm ); + write_unaligned16( out + 6, in->data.L2Cap.minRemoteMtu ); + write_unaligned16( out + 8, in->data.L2Cap.localMtu ); +#elif defined BT_USE_RFCOMM + write_unaligned8( out + 4, in->data.RfComm.remoteService ); + write_unaligned16( out + 6, in->data.RfComm.maxFrameSize ); + write_unaligned8( out + 7, in->data.RfComm.advancedCredit ); +#endif +} + +/* from file BtLib.h */ + +void +flipBtSocketListenInfoArm268K( unsigned char* out, + const BtLibSocketListenInfoType* in) +{ + /* Some moron didn't put a type field in BtLibSocketListenInfoType. The + interpretation of the data depends on the protocol with which the + socket (param to BtLibSocketListen) was created. We *could* pass the + socket into this function and pull the protocol out, using that to + decide which overlapping union field to swap, but let's just hard-code + btLibL2CapProtocol (field data.L2Cap) since that's what I'm using.*/ + + write_unaligned16( out + 0, in->data.L2Cap.localPsm ); + write_unaligned16( out + 2, in->data.L2Cap.localMtu ); + write_unaligned16( out + 4, in->data.L2Cap.minRemoteMtu ); +} + +void +flipBtLibSdpUuidTypeArm268K( unsigned char* out, const BtLibSdpUuidType* in ) +{ + write_unaligned8( out + 0, in->size ); /* size 1 on 68K, 4 on ARM */ + memcpy( out + 1, &in->UUID, sizeof(in->UUID) ); /* offset 68K: 1; ARM: 4 */ +} + +void +flipBtLibFriendlyNameTypeArm268K( unsigned char* out, + const BtLibFriendlyNameType* in ) +{ + write_unaligned32( out + 0, in->name ); + write_unaligned8( out + 4, in->nameLength ); +} + +#endif /* XWFEATURE_BLUETOOTH */ diff --git a/xwords4/palm/pace_man.h b/xwords4/palm/pace_man.h new file mode 100644 index 000000000..6282863a6 --- /dev/null +++ b/xwords4/palm/pace_man.h @@ -0,0 +1,174 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2004-2007 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _PACE_MAN_H_ +#define _PACE_MAN_H_ + +#ifdef XW_TARGET_PNO + +#include +#include +#include +#include +#include +#include +#ifdef XWFEATURE_BLUETOOTH +# include +# include +#endif + +#include "pnostate.h" +#include "xptypes.h" + +void* memcpy( void* dest, const void* src, unsigned long n ); +extern Int16 StrPrintF( Char* s, const Char* formatStr, ... ); +extern Int16 StrVPrintF( Char* s, const Char* formatStr, _Palm_va_list arg ); +extern Boolean SysHandleEvent( EventPtr eventP ); +extern void FrmSetEventHandler( FormType* formP, + FormEventHandlerType* handler ); +extern void LstSetListChoices( ListType* listP, Char** itemsText, + Int16 numItems ); +extern Err SysNotifyRegister( UInt16 cardNo, LocalID dbID, + UInt32 notifyType, SysNotifyProcPtr callbackP, + Int8 priority, void* userDataP ); +extern void LstSetDrawFunction( ListType* listP, ListDrawDataFuncPtr func ); +extern Err ExgDBWrite( ExgDBWriteProcPtr writeProcP, void* userDataP, + const char* nameP, LocalID dbID, UInt16 cardNo ); + +#if 0 +# define FUNC_HEADER(n) XP_LOGF( #n " called" ) +# define FUNC_TAIL(n) XP_LOGF( #n " done" ) +#else +# define FUNC_HEADER(n) +# define FUNC_TAIL(n) +#endif + +#define STACK_START(typ, var, siz ) typ var[siz] +#define STACK_END(s) +#define ADD_TO_STACK4(s,val,offset) \ + write_unaligned32( &s[offset], (unsigned long)val ) +#define ADD_TO_STACK2(s,val,offset) \ + write_unaligned16( &s[offset], (unsigned short)val ) +#define ADD_TO_STACK1(s,val,offset) \ + s[offset] = (unsigned char)val; \ + s[offset+1] = 0 + +#define SWAP1_NON_NULL_IN(ptr) if ( !!(ptr) ) { *(ptr) = Byte_Swap16(*(ptr)); } +#define SWAP2_NON_NULL_IN(ptr) if ( !!(ptr) ) { *(ptr) = Byte_Swap16(*(ptr)); } +#define SWAP4_NON_NULL_IN(ptr) if ( !!(ptr) ) { *(ptr) = Byte_Swap32(*(ptr)); } +#define SWAP1_NON_NULL_OUT(ptr) SWAP1_NON_NULL_IN(ptr) +#define SWAP2_NON_NULL_OUT(ptr) SWAP2_NON_NULL_IN(ptr) +#define SWAP4_NON_NULL_OUT(ptr) SWAP4_NON_NULL_IN(ptr) + +#define SET_SEL_REG(trap, sp) ((unsigned long*)((sp)->emulStateP))[3] = (trap) + +void evt68k2evtARM( EventType* event, const unsigned char* evt68k ); +#define SWAP_EVENTTYPE_68K_TO_ARM( dp, sp ) evt68k2evtARM( (dp), (sp) ) +void evtArm2evt68K( unsigned char* evt68k, const EventType* event ); +#define SWAP_EVENTTYPE_ARM_TO_68K( dp, sp ) evtArm2evt68K( (dp), (sp) ) + +void flipRect( RectangleType* rout, const RectangleType* rin ); +#define SWAP_RECTANGLETYPE_ARM_TO_68K( dp, sp ) flipRect( (dp), (sp) ) +#define SWAP_RECTANGLETYPE_68K_TO_ARM SWAP_RECTANGLETYPE_ARM_TO_68K + +void flipFieldAttr( FieldAttrType* fout, const FieldAttrType* fin ); +#define SWAP_FIELDATTRTYPE_ARM_TO_68K( dp, sp ) flipFieldAttr( (dp), (sp) ) + +void flipEngSocketFromArm( unsigned char* sout, const ExgSocketType* sin ); +#define SWAP_EXGSOCKETTYPE_ARM_TO_68K( dp, sp ) flipEngSocketFromArm( (dp), (sp) ) +void flipEngSocketToArm( ExgSocketType* out, const unsigned char* sin ); +#define SWAP_EXGSOCKETTYPE_68K_TO_ARM( dp, sp ) flipEngSocketToArm( (dp), (sp) ) + +void flipFileInfoFromArm( unsigned char* fiout, const FileInfoType* fiin ); +#define SWAP_FILEINFOTYPE_ARM_TO_68K( dp, sp ) \ + flipFileInfoFromArm( (unsigned char*)(dp), (sp) ) +void flipFileInfoToArm( FileInfoType* fout, const unsigned char* fin ); +#define SWAP_FILEINFOTYPE_68K_TO_ARM( dp, sp ) flipFileInfoToArm( (dp), (sp) ) + + +#define SWAP_DATETIMETYPE_ARM_TO_68K( dp, sp ) /* nothing for now */ +void flipDateTimeToArm( DateTimeType* out, const unsigned char* in ); +#define SWAP_DATETIMETYPE_68K_TO_ARM( dp, sp ) flipDateTimeToArm( (dp), (sp) ) + +NetHostInfoPtr NetLibGetHostByName( UInt16 libRefNum, + const Char* nameP, NetHostInfoBufPtr bufP, + Int32 timeout, Err* errP ); + +#ifdef XWFEATURE_BLUETOOTH +void flipBtConnInfoArm268K( unsigned char* out, const BtLibSocketConnectInfoType* in ); +void flipBtSocketListenInfoArm268K( unsigned char* out, + const BtLibSocketListenInfoType* in); + +# define SWAP_BTLIBSOCKETCONNECTINFOTYPE_ARM_TO_68K( out, in ) \ + flipBtConnInfoArm268K( out, in ) +# define SWAP_BTLIBSOCKETCONNECTINFOTYPE_68K_TO_ARM( out, in ) /* nop */ +# define SWAP_BTLIBSOCKETLISTENINFOTYPE_ARM_TO_68K( out, in ) \ + flipBtSocketListenInfoArm268K( out, in ) +# define SWAP_BTLIBSOCKETLISTENINFOTYPE_68K_TO_ARM( out, in ) /* nop */ + +void flipBtLibSdpUuidTypeArm268K( unsigned char* out, + const BtLibSdpUuidType* in ); +void flipBtLibFriendlyNameTypeArm268K( unsigned char* out, + const BtLibFriendlyNameType* in ); + +# define SWAP_BTLIBSDPUUIDTYPE_ARM_TO_68K( out, in ) \ + flipBtLibSdpUuidTypeArm268K( out, in ) +# define SWAP_BTLIBSDPUUIDTYPE_68K_TO_ARM( out, in ) /* nothing */ +# define SWAP_BTLIBFRIENDLYNAMETYPE_ARM_TO_68K( out, in ) \ + flipBtLibFriendlyNameTypeArm268K( out, in ) +# define SWAP_BTLIBFRIENDLYNAMETYPE_68K_TO_ARM( out, in ) /* nothing? */ + +#endif /* XWFEATURE_BLUETOOTH */ + +PNOState* getStorageLoc(void); +#define GET_CALLBACK_STATE() getStorageLoc() + +#define crash() *(int*)1L = 1 + +unsigned long Byte_Swap32( unsigned long l ); +unsigned short Byte_Swap16( unsigned short l ); +void write_unaligned16( unsigned char* dest, unsigned short val ); +void write_unaligned32( unsigned char* dest, unsigned long val ); +#define write_unaligned8( p, v ) *(p) = v + +unsigned short read_unaligned16( const unsigned char* src ); + +#ifdef DEBUG +# define EMIT_NAME(name,bytes) \ + asm( "bal done_" name ); \ + asm( ".byte " bytes ); \ + asm( ".align" ); \ + asm( "done_" name ":" ) + +#else +# define EMIT_NAME(n,b) +#endif + +/* Temporary until can generate */ +#ifdef XWFEATURE_FIVEWAY +#include +extern Err HsNavDrawFocusRing( FormType* formP, UInt16 objectID, + Int16 extraInfo, + RectangleType* boundsInsideRingP, + HsNavFocusRingStyleEnum ringStyle, + Boolean forceRestore); +#endif + +#endif +#endif diff --git a/xwords4/palm/palmbt.c b/xwords4/palm/palmbt.c new file mode 100644 index 000000000..ea16c037b --- /dev/null +++ b/xwords4/palm/palmbt.c @@ -0,0 +1,1664 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; compile-command: "make ARCH=68K_ONLY MEMDEBUG=TRUE"; -*- */ +/* + * Copyright 2006-2007 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef XWFEATURE_BLUETOOTH + +#include "xptypes.h" +#include "palmbt.h" +#include "strutils.h" +#include "palmutil.h" + +# include +# include + +#if defined BT_USE_L2CAP +# define SEL_PROTO btLibL2CapProtocol +# define TRUE_IF_RFCOMM XP_FALSE +#elif defined BT_USE_RFCOMM +# define SEL_PROTO btLibRfCommProtocol +# define TRUE_IF_RFCOMM XP_TRUE +# define INITIAL_CREDIT 50 +#endif + +#define L2CAPSOCKETMTU 500 +#define SOCK_INVAL ((BtLibSocketRef)-1) + +#define DO_SERVICE_RECORD 1 +#define ACL_WAIT_INTERVAL 4 + +typedef enum { PBT_UNINIT = 0, PBT_MASTER, PBT_SLAVE } PBT_PicoRole; + +typedef enum { + PBT_ACT_NONE + , PBT_ACT_MASTER_RESET + , PBT_ACT_SLAVE_RESET + , PBT_ACT_SETUP_LISTEN + , PBT_ACT_CONNECT_ACL + , PBT_ACT_GETSDP /* slave only */ + , PBT_ACT_CONNECT_DATA /* l2cap or rfcomm */ + , PBT_ACT_TELLCONN + , PBT_ACT_GOTDATA /* can be duplicated */ + , PBT_ACT_TRYSEND +} PBT_ACTION; + +#define DUPLICATES_OK(a) ((a) >= PBT_ACT_GOTDATA) + +typedef enum { + PBTST_NONE + , PBTST_LISTENING /* master */ + , PBTST_ACL_CONNECTING /* slave */ + , PBTST_ACL_CONNECTED /* slave */ + , PBTST_SDP_QUERYING /* slave */ + , PBTST_SDP_QUERIED /* slave */ + , PBTST_DATA_CONNECTING /* slave; l2cap or rfcomm */ + , PBTST_DATA_CONNECTED /* slave; l2cap or rfcomm */ +} PBT_STATE; + +#define PBT_MAX_ACTS 8 /* six wasn't enough */ +#define HASWORK(s) ((s)->queueLen > 0) +#define MAX_PACKETS 4 + +typedef struct PBT_queue { + XP_U16 lens[MAX_PACKETS]; + XP_U8 bufs[L2CAPSOCKETMTU*2]; /* what's the mmu? */ +} PBT_queue; + +typedef struct PalmBTStuff { + PalmAppGlobals* globals; + + XP_U16 btLibRefNum; + + struct { + PBT_queue in; + PBT_queue out; + + XP_Bool sendInProgress; + } vol; + + /* peer's addr: passed in by UI in case of slave, received via connection + in case of master. Piconet master will need an array of these. */ + BtLibDeviceAddressType otherAddr; + BtLibSocketRef dataSocket; + PBT_PicoRole picoRole; + PBT_STATE p_connState; + BtLibAccessibleModeEnum accState; + + PBT_ACTION actQueue[PBT_MAX_ACTS]; + XP_U16 queueLen; + + struct /*union*/ { + struct { +#if defined BT_USE_L2CAP + BtLibL2CapPsmType remotePsm; +#elif defined BT_USE_RFCOMM + BtLibRfCommServerIdType remoteService; +#endif + BtLibSocketRef sdpSocket; + } slave; + struct { + BtLibSocketRef listenSocket; +#ifdef DO_SERVICE_RECORD + BtLibSdpRecordHandle sdpRecordH; +#endif + } master; + } u; + +#ifdef DEBUG + struct { + XP_U32 totalSent; + XP_U32 totalRcvd; + XP_U16 maxQueueLen; + } stats; +#endif +} PalmBTStuff; + +#ifdef DEBUG +static void palm_bt_log( const char* btfunc, const char* func, Err err ); +#define LOG_ERR(f,e) palm_bt_log( #f, __func__, e ) +#define CALL_ERR(e,f,...) \ + XP_LOGF( "%s: calling %s", __func__, #f ); \ + e = f(__VA_ARGS__); \ + LOG_ERR(f,e); \ + if ( e == btLibErrFailed ) { XP_WARNF( "%s=>btLibErrFailed", #f ); } +#else +#define CALL_ERR(e,f,...) e = f(__VA_ARGS__) +#endif + +static const BtLibSdpUuidType XWORDS_UUID = { + btLibUuidSize128, + XW_BT_UUID +}; + +static PalmBTStuff* pbt_checkInit( PalmAppGlobals* globals, + XP_Bool* userCancelled ); +static Err pbd_discover( PalmBTStuff* btStuff, BtLibDeviceAddressType* addr ); +static void pbt_setup_slave( PalmBTStuff* btStuff, const CommsAddrRec* addr ); +static void pbt_takedown_slave( PalmBTStuff* btStuff ); +static void pbt_setup_master( PalmBTStuff* btStuff ); +static void pbt_takedown_master( PalmBTStuff* btStuff ); +static void pbt_do_work( PalmBTStuff* btStuff, BtCbEvtProc proc ); +static void pbt_postpone( PalmBTStuff* btStuff, PBT_ACTION act ); +static XP_S16 pbt_enqueue( PBT_queue* queue, const XP_U8* data, XP_S16 len, + XP_Bool addLen, XP_Bool append ); +static void pbt_handoffIncoming( PalmBTStuff* btStuff, BtCbEvtProc proc ); + +static void waitACL( PalmBTStuff* btStuff ); +static void pbt_reset_buffers( PalmBTStuff* btStuff ); +static void pbt_killLinks( PalmBTStuff* btStuff ); +static XP_Bool pbt_checkAddress( PalmBTStuff* btStuff, const CommsAddrRec* addr ); +static Err pbt_nameForAddr( PalmBTStuff* btStuff, + const BtLibDeviceAddressType* addr, + char* const out, XP_U16 outlen ); + +#ifdef DEBUG +static void pbt_setstate( PalmBTStuff* btStuff, PBT_STATE newState, + const char* whence ); +# define SET_STATE(b,s) pbt_setstate((b),(s),__func__) +#else +# define SET_STATE(b,s) (b)->p_connState = (s) +#endif +#define GET_STATE(b) ((b)->p_connState) + +#ifdef DEBUG +static const char* btErrToStr( Err err ); +static const char* btEvtToStr( BtLibSocketEventEnum evt ); +static const char* mgmtEvtToStr( BtLibManagementEventEnum event ); +static const char* actToStr(PBT_ACTION act); +static const char* stateToStr(PBT_STATE st); +static const char* connEnumToStr( BtLibAccessibleModeEnum mode ); +static const char* proleToString( PBT_PicoRole r ); + +#else +# define btErrToStr( err ) "" +# define btEvtToStr( evt ) "" +# define mgmtEvtToStr( evt ) "" +# define actToStr(act) "" +# define stateToStr(st) "" +# define connEnumToStr(mode) "" +# define proleToString(r) "" +#endif + +/* callbacks */ +static void libMgmtCallback( BtLibManagementEventType* mEvent, UInt32 refCon ); +static void socketCallback( BtLibSocketEventType* sEvent, UInt32 refCon ); + +XP_Bool +palm_bt_init( PalmAppGlobals* globals, XP_Bool* userCancelled ) +{ + XP_Bool inited; + PalmBTStuff* btStuff; + + LOG_FUNC(); + + btStuff = globals->btStuff; + if ( !btStuff ) { + btStuff = pbt_checkInit( globals, userCancelled ); + } else { + pbt_reset_buffers( btStuff ); + pbt_killLinks( btStuff ); + } + + /* Don't try starting master or slave: we don't know which we are yet. + Wait for the first send attempt.*/ + + inited = !!btStuff; + if ( inited ) { + btStuff->picoRole = PBT_UNINIT; + } + LOG_RETURNF( "%d", (XP_U16)inited ); + return inited; +} /* palm_bt_init */ + +void +palm_bt_reset( PalmAppGlobals* globals ) +{ + PalmBTStuff* btStuff = globals->btStuff; + + if ( !!btStuff ) { + if ( btStuff->picoRole == PBT_MASTER ) { + pbt_takedown_master( btStuff ); + pbt_postpone( btStuff, PBT_ACT_MASTER_RESET ); + } else if ( btStuff->picoRole == PBT_SLAVE ) { + pbt_takedown_slave( btStuff ); + pbt_postpone( btStuff, PBT_ACT_SLAVE_RESET ); + } + } +} + +void +palm_bt_close( PalmAppGlobals* globals ) +{ + PalmBTStuff* btStuff = globals->btStuff; + + if ( !!btStuff ) { + XP_U16 btLibRefNum = btStuff->btLibRefNum; + if ( btLibRefNum != 0 ) { + Err err; + + if ( btStuff->picoRole == PBT_MASTER ) { + pbt_takedown_master( btStuff ); + } else if ( btStuff->picoRole == PBT_SLAVE ) { + pbt_takedown_slave( btStuff ); + } + + /* Need to unregister callbacks */ + CALL_ERR( err, BtLibUnregisterManagementNotification, btLibRefNum, + libMgmtCallback ); + XP_ASSERT( errNone == err ); + CALL_ERR( err, BtLibClose, btLibRefNum ); + XP_ASSERT( errNone == err ); + } + XP_FREE( globals->mpool, btStuff ); + globals->btStuff = NULL; + } else { + XP_LOGF( "%s: btStuff null", __func__ ); + } +} /* palm_bt_close */ + +void +palm_bt_amendWaitTicks( PalmAppGlobals* globals, Int32* result ) +{ + PalmBTStuff* btStuff = globals->btStuff; + if ( !!btStuff && HASWORK(btStuff) ) { + *result = 0; + } +} + +XP_Bool +palm_bt_doWork( PalmAppGlobals* globals, BtCbEvtProc proc, BtUIState* btUIStateP ) +{ + PalmBTStuff* btStuff = globals->btStuff; + XP_Bool haveWork = !!btStuff && HASWORK(btStuff); + + if ( haveWork ) { + pbt_do_work( btStuff, proc ); + } + if ( !!btStuff && !!btUIStateP ) { + BtUIState btUIState = BTUI_NONE; /* default */ + switch( GET_STATE(btStuff) ) { + case PBTST_NONE: + break; + case PBTST_LISTENING: + btUIState = BTUI_LISTENING; + break; + case PBTST_ACL_CONNECTING: + case PBTST_ACL_CONNECTED: + case PBTST_SDP_QUERYING: + case PBTST_SDP_QUERIED: + case PBTST_DATA_CONNECTING: + btUIState = BTUI_CONNECTING; + break; + case PBTST_DATA_CONNECTED: + btUIState = btStuff->picoRole == PBT_MASTER? + BTUI_SERVING : BTUI_CONNECTED; + break; + default: + XP_ASSERT(0); /* Don't add new states without handling here */ + break; + } + *btUIStateP = btUIState; + } + return haveWork; +} /* palm_bt_doWork */ + +void +palm_bt_addrString( PalmAppGlobals* globals, const XP_BtAddr* btAddr, + XP_BtAddrStr* str ) +{ + PalmBTStuff* btStuff = pbt_checkInit( globals, NULL ); + str->chars[0] = '\0'; + if ( !!btStuff ) { + Err err; + CALL_ERR( err, BtLibAddrBtdToA, btStuff->btLibRefNum, + (BtLibDeviceAddressType*)btAddr, + (char*)str, sizeof(*str) ); + XP_LOGF( "BtLibAddrBtdToA=>%s from:", str ); + LOG_HEX( btAddr, sizeof(*btAddr), "" ); + } +} /* palm_bt_addrString */ + +XP_Bool +palm_bt_browse_device( PalmAppGlobals* globals, XP_BtAddr* btAddr, + XP_UCHAR* out, XP_U16 len ) +{ + XP_Bool success = XP_FALSE; + PalmBTStuff* btStuff; + + LOG_FUNC(); + + btStuff = pbt_checkInit( globals, NULL ); + if ( NULL != btStuff ) { + BtLibDeviceAddressType addr; + Err err = pbd_discover( btStuff, &addr ); + + if ( errNone == err ) { + XP_MEMCPY( btAddr, &addr, sizeof(addr) ); + LOG_HEX( &btAddr, sizeof(btAddr), __func__ ); + + err = pbt_nameForAddr( btStuff, &addr, out, len ); + } + success = errNone == err; + } + LOG_RETURNF( "%d", (XP_U16)success ); + return success; +} /* palm_bt_browse_device */ + +#ifdef DEBUG +void +palm_bt_getStats( PalmAppGlobals* globals, XWStreamCtxt* stream ) +{ + PalmBTStuff* btStuff = globals->btStuff; + if ( !btStuff ) { + stream_putString( stream, "bt not initialized" ); + } else { + char buf[64]; + XP_U16 cur; + + XP_SNPRINTF( buf, sizeof(buf), "Role: %s\n", + btStuff->picoRole == PBT_MASTER? "master": + (btStuff->picoRole == PBT_SLAVE? "slave":"unknown") ); + stream_putString( stream, buf ); + XP_SNPRINTF( buf, sizeof(buf), "State: %s\n", + stateToStr( GET_STATE(btStuff)) ); + stream_putString( stream, buf ); + + XP_SNPRINTF( buf, sizeof(buf), "%d actions queued:\n", + btStuff->queueLen ); + stream_putString( stream, buf ); + for ( cur = 0; cur < btStuff->queueLen; ++cur ) { + XP_SNPRINTF( buf, sizeof(buf), " - %s\n", + actToStr( btStuff->actQueue[cur] ) ); + stream_putString( stream, buf ); + } + + XP_SNPRINTF( buf, sizeof(buf), "total sent: %ld\n", + btStuff->stats.totalSent ); + stream_putString( stream, buf ); + XP_SNPRINTF( buf, sizeof(buf), "total rcvd: %ld\n", + btStuff->stats.totalRcvd ); + stream_putString( stream, buf ); + XP_SNPRINTF( buf, sizeof(buf), "max act queue len seen: %d\n", + btStuff->stats.maxQueueLen ); + stream_putString( stream, buf ); + } +} +#endif + +static Err +pbt_nameForAddr( PalmBTStuff* btStuff, const BtLibDeviceAddressType* addr, + char* const out, XP_U16 outlen ) +{ + Err err; + UInt8 name[PALM_BT_NAME_LEN]; + BtLibFriendlyNameType nameType = { + .name = name, + .nameLength = sizeof(name) + }; + + CALL_ERR( err, BtLibGetRemoteDeviceName, btStuff->btLibRefNum, + (BtLibDeviceAddressType*)addr, &nameType, + btLibCachedThenRemote ); + if ( errNone == err ) { + XP_LOGF( "%s: got name %s", __func__, nameType.name ); + + XP_ASSERT( outlen >= nameType.nameLength ); + XP_MEMCPY( out, nameType.name, nameType.nameLength ); + } + return err; +} + +static XP_U16 +pbt_peekQueue( const PBT_queue* queue, const XP_U8** bufp ) +{ + XP_U16 len = queue->lens[0]; + if ( len > 0 ) { + *bufp = &queue->bufs[0]; + } + LOG_RETURNF( "%d", len ); + return len; +} + +static XP_U16 +pbt_shiftQueue( PBT_queue* queue ) +{ + XP_U16 len = queue->lens[0]; + XP_ASSERT( len != 0 ); + XP_MEMCPY( &queue->lens[0], &queue->lens[1], + sizeof(queue->lens) - sizeof(queue->lens[0]) ); + queue->lens[MAX_PACKETS-1] = 0; /* be safe */ + XP_MEMCPY( queue->bufs, queue->bufs + len, + sizeof(queue->bufs) - len ); + return len; +} /* pbt_shiftQueue */ + +static void +pbt_send_pending( PalmBTStuff* btStuff ) +{ + Err err; + LOG_FUNC(); + + if ( !btStuff->vol.sendInProgress && (SOCK_INVAL != btStuff->dataSocket)) { + const XP_U8* buf; + XP_U16 len = pbt_peekQueue( &btStuff->vol.out, &buf ); + if ( len > 0 ) { +#ifdef LOG_BTIO + LOG_HEX( buf, len, "to BtLibSocketSend" ); +#endif + XP_LOGF( "sending on socket %d", btStuff->dataSocket ); + CALL_ERR( err, BtLibSocketSend, btStuff->btLibRefNum, + btStuff->dataSocket, (char*)buf, len ); + if ( btLibErrPending == err ) { + btStuff->vol.sendInProgress = XP_TRUE; + } + } + } + LOG_RETURN_VOID(); +} /* pbt_send_pending */ + +XP_S16 +palm_bt_send( const XP_U8* buf, XP_U16 len, const CommsAddrRec* addr, + PalmAppGlobals* globals, XP_Bool* userCancelled ) +{ + XP_S16 nSent = -1; + PalmBTStuff* btStuff; + CommsAddrRec remoteAddr; + PBT_PicoRole picoRole; + XP_LOGF( "%s(len=%d)", __func__, len); + + XP_ASSERT( !!globals->game.comms ); + + btStuff = pbt_checkInit( globals, userCancelled ); + if ( !!btStuff ) { + /* addr is NULL when client has not established connection to host */ + if ( !addr ) { + comms_getAddr( globals->game.comms, &remoteAddr ); + addr = &remoteAddr; + } + XP_ASSERT( !!addr ); + + picoRole = btStuff->picoRole; + XP_LOGF( "%s: role=%s", __func__, proleToString(picoRole) ); + if ( picoRole == PBT_UNINIT ) { + XP_Bool amMaster = comms_getIsServer( globals->game.comms ); + picoRole = amMaster? PBT_MASTER : PBT_SLAVE; + } + + (void)pbt_checkAddress( btStuff, addr ); + + if ( picoRole == PBT_MASTER ) { + pbt_setup_master( btStuff ); + } else { + pbt_setup_slave( btStuff, addr ); + } + + nSent = pbt_enqueue( &btStuff->vol.out, buf, len, TRUE_IF_RFCOMM, XP_FALSE ); + pbt_send_pending( btStuff ); + } + LOG_RETURNF( "%d", nSent ); + return nSent; +} /* palm_bt_send */ + + +#ifdef DO_SERVICE_RECORD +static XP_Bool +setupServiceRecord( PalmBTStuff* btStuff ) +{ + Err err; + /* 3. BtLibSdpServiceRecordCreate: allocate a memory chunk that + represents an SDP service record. */ + CALL_ERR( err, BtLibSdpServiceRecordCreate, + btStuff->btLibRefNum, &btStuff->u.master.sdpRecordH ); + + /* 4. BtLibSdpServiceRecordSetAttributesForSocket: initialize an + SDP memory record so it can represent the newly-created L2CAP + listener socket as a service */ + if ( errNone == err ) { + CALL_ERR( err, BtLibSdpServiceRecordSetAttributesForSocket, + btStuff->btLibRefNum, btStuff->u.master.listenSocket, + (BtLibSdpUuidType*)&XWORDS_UUID, 1, XW_BT_NAME, + StrLen(XW_BT_NAME), btStuff->u.master.sdpRecordH ); + + /* 5. BtLibSdpServiceRecordStartAdvertising: make an SDP memory + record representing a local SDP service record visible to + remote devices. */ + if ( errNone == err ) { + CALL_ERR( err, BtLibSdpServiceRecordStartAdvertising, + btStuff->btLibRefNum, btStuff->u.master.sdpRecordH ); + } + } + /* If this fails commonly, need to free the structure and try again */ + XP_ASSERT( errNone == err ); + return errNone == err; +} /* setupServiceRecord */ +#else +# define setupServiceRecord(b) XP_TRUE +#endif + +static void +pbt_setup_master( PalmBTStuff* btStuff ) +{ + if ( btStuff->picoRole == PBT_SLAVE ) { + pbt_takedown_slave( btStuff ); + } + btStuff->picoRole = PBT_MASTER; + + if ( btStuff->u.master.listenSocket == SOCK_INVAL ) { + /* Will eventually want to create a piconet here for more than two + devices to play.... */ + + Err err; + BtLibSocketListenInfoType listenInfo; + + /* 1. BtLibSocketCreate: create an L2CAP socket. */ + CALL_ERR( err, BtLibSocketCreate, btStuff->btLibRefNum, + &btStuff->u.master.listenSocket, socketCallback, + (UInt32)btStuff, SEL_PROTO ); + XP_ASSERT( errNone == err ); + + /* 2. BtLibSocketListen: set up an L2CAP socket as a listener. */ + XP_MEMSET( &listenInfo, 0, sizeof(listenInfo) ); +#if defined BT_USE_L2CAP + listenInfo.data.L2Cap.localPsm = BT_L2CAP_RANDOM_PSM; + listenInfo.data.L2Cap.localMtu = L2CAPSOCKETMTU; + listenInfo.data.L2Cap.minRemoteMtu = L2CAPSOCKETMTU; +#elif defined BT_USE_RFCOMM + // remoteService: assigned by rfcomm + listenInfo.data.RfComm.maxFrameSize = BT_RF_DEFAULT_FRAMESIZE; + listenInfo.data.RfComm.advancedCredit = INITIAL_CREDIT; +#endif + /* Doesn't send events; returns errNone unless no resources avail. */ + CALL_ERR( err, BtLibSocketListen, btStuff->btLibRefNum, + btStuff->u.master.listenSocket, &listenInfo ); + if ( (errNone == err) && setupServiceRecord( btStuff ) ) { + /* Set state here to indicate I'm available, at least for + debugging? */ + SET_STATE( btStuff, PBTST_LISTENING ); + } else { + CALL_ERR( err, BtLibSocketClose, btStuff->btLibRefNum, + btStuff->u.master.listenSocket ); + btStuff->u.master.listenSocket = SOCK_INVAL; + pbt_postpone( btStuff, PBT_ACT_SETUP_LISTEN ); + } + } + XP_ASSERT( NULL != btStuff->u.master.sdpRecordH ); +} /* pbt_setup_master */ + +static void +pbt_close_datasocket( PalmBTStuff* btStuff ) +{ + if ( SOCK_INVAL != btStuff->dataSocket ) { + Err err; + CALL_ERR( err, BtLibSocketClose, btStuff->btLibRefNum, + btStuff->dataSocket ); + XP_ASSERT( err == errNone ); + btStuff->dataSocket = SOCK_INVAL; + } +} + +static void +pbt_close_sdpsocket( PalmBTStuff* btStuff ) +{ + XP_ASSERT( PBT_SLAVE == btStuff->picoRole ); + if ( SOCK_INVAL != btStuff->u.slave.sdpSocket ) { + Err err; + CALL_ERR( err, BtLibSocketClose, btStuff->btLibRefNum, btStuff->u.slave.sdpSocket ); + btStuff->u.slave.sdpSocket = SOCK_INVAL; + } +} + +static void +pbt_takedown_master( PalmBTStuff* btStuff ) +{ + XP_U16 btLibRefNum; + Err err; + + LOG_FUNC(); + + XP_ASSERT( btStuff->picoRole == PBT_MASTER ); + btLibRefNum = btStuff->btLibRefNum; + + pbt_close_datasocket( btStuff ); + +#ifdef DO_SERVICE_RECORD + if ( !!btStuff->u.master.sdpRecordH ) { + CALL_ERR( err, BtLibSdpServiceRecordStopAdvertising, + btLibRefNum, btStuff->u.master.sdpRecordH ); + XP_ASSERT( errNone == err ); /* no errors if it was being advertised */ + + CALL_ERR( err, BtLibSdpServiceRecordDestroy, btLibRefNum, + btStuff->u.master.sdpRecordH ); + btStuff->u.master.sdpRecordH = NULL; + } +#endif + + if ( SOCK_INVAL != btStuff->u.master.listenSocket ) { + CALL_ERR( err, BtLibSocketClose, btLibRefNum, + btStuff->u.master.listenSocket ); + btStuff->u.master.listenSocket = SOCK_INVAL; + XP_ASSERT( err == errNone ); + } + + btStuff->picoRole = PBT_UNINIT; + SET_STATE( btStuff, PBTST_NONE ); + LOG_RETURN_VOID(); +} /* pbt_takedown_master */ + +#if 0 +static void +debug_logQueue( const PalmBTStuff* const btStuff ) +{ + XP_U16 i; + XP_U16 len = btStuff->queueLen; + XP_LOGF( "%s: queue len = %d", __func__, len ); + for ( i = 0; i < len; ++i ) { + XP_LOGF( "\t%d: %s", i, actToStr( btStuff->actQueue[i] ) ); + } +} +#else +#define debug_logQueue( bts ) +#endif + +static void +pbt_do_work( PalmBTStuff* btStuff, BtCbEvtProc proc ) +{ + PBT_ACTION act; + Err err; + XP_U16 btLibRefNum = btStuff->btLibRefNum; + BtCbEvtInfo info; + + debug_logQueue( btStuff ); + + act = btStuff->actQueue[0]; + --btStuff->queueLen; + XP_MEMCPY( &btStuff->actQueue[0], &btStuff->actQueue[1], + btStuff->queueLen * sizeof(btStuff->actQueue[0]) ); + + XP_LOGF( "%s: evt=%s; state=%s", __func__, actToStr(act), + stateToStr(GET_STATE(btStuff)) ); + + switch( act ) { + case PBT_ACT_MASTER_RESET: + pbt_setup_master( btStuff ); + break; + case PBT_ACT_SLAVE_RESET: + pbt_setup_slave( btStuff, NULL ); + break; + case PBT_ACT_SETUP_LISTEN: + pbt_setup_master( btStuff ); + break; + + case PBT_ACT_CONNECT_ACL: + XP_ASSERT( PBT_SLAVE == btStuff->picoRole ); + if ( GET_STATE(btStuff) == PBTST_NONE ) { + UInt8 name[PALM_BT_NAME_LEN]; + (void)pbt_nameForAddr( btStuff, &btStuff->otherAddr, + name, sizeof(name) ); + info.evt = BTCBEVT_CONFIRM; + info.u.confirm.hostName = name; + info.u.confirm.confirmed = XP_TRUE; + (*proc)( btStuff->globals, &info ); + if ( !info.u.confirm.confirmed ) { + break; + } + + /* sends btLibManagementEventACLConnectOutbound */ + CALL_ERR( err, BtLibLinkConnect, btLibRefNum, + &btStuff->otherAddr ); + if ( btLibErrPending == err ) { + SET_STATE( btStuff, PBTST_ACL_CONNECTING ); + } else if ( btLibErrAlreadyConnected == err ) { + SET_STATE( btStuff, PBTST_ACL_CONNECTED ); + pbt_postpone( btStuff, PBT_ACT_GETSDP ); + } + } + break; + + case PBT_ACT_GETSDP: + if ( PBTST_ACL_CONNECTED == GET_STATE(btStuff) ) { + XP_ASSERT( SOCK_INVAL == btStuff->u.slave.sdpSocket ); + CALL_ERR( err, BtLibSocketCreate, btStuff->btLibRefNum, + &btStuff->u.slave.sdpSocket, socketCallback, (UInt32)btStuff, + btLibSdpProtocol ); + if ( err == errNone ) { +#if defined BT_USE_L2CAP + XP_LOGF( "sending on sdpSocket socket %d", btStuff->u.slave.sdpSocket ); + CALL_ERR( err, BtLibSdpGetPsmByUuid, btStuff->btLibRefNum, + btStuff->u.slave.sdpSocket, &btStuff->otherAddr, + (BtLibSdpUuidType*)&XWORDS_UUID, 1 ); +#elif defined BT_USE_RFCOMM + CALL_ERR( err, BtLibSdpGetServerChannelByUuid, + btStuff->btLibRefNum, btStuff->u.slave.sdpSocket, + &btStuff->otherAddr, + (BtLibSdpUuidType*)&XWORDS_UUID, 1 ); +#endif + if ( err == errNone ) { + SET_STATE( btStuff, PBTST_SDP_QUERIED ); + pbt_postpone( btStuff, PBT_ACT_CONNECT_DATA ); + break; + } else if ( err == btLibErrPending ) { + SET_STATE( btStuff, PBTST_SDP_QUERYING ); + break; + } else if ( err == btLibErrNoAclLink ) { + /* fall through to waitACL below */ + } else { + XP_ASSERT(0); + } + } + } + /* Presumably state's been reset since PBT_ACT_GETSDP issued */ + XP_LOGF( "aborting b/c state wrong" ); + XP_ASSERT( PBT_SLAVE == btStuff->picoRole ); + pbt_close_sdpsocket( btStuff ); + SET_STATE( btStuff, PBTST_NONE ); + waitACL( btStuff ); + break; + + case PBT_ACT_CONNECT_DATA: + XP_ASSERT( btStuff->picoRole == PBT_SLAVE ); + if ( GET_STATE(btStuff) == PBTST_SDP_QUERIED ) { + pbt_close_datasocket( btStuff ); + CALL_ERR( err, BtLibSocketCreate, btLibRefNum, + &btStuff->dataSocket, + socketCallback, (UInt32)btStuff, SEL_PROTO ); + + if ( btLibErrNoError == err ) { + BtLibSocketConnectInfoType connInfo; + connInfo.remoteDeviceP = &btStuff->otherAddr; +#if defined BT_USE_L2CAP + connInfo.data.L2Cap.remotePsm = btStuff->u.slave.remotePsm; + connInfo.data.L2Cap.localMtu = L2CAPSOCKETMTU; + connInfo.data.L2Cap.minRemoteMtu = L2CAPSOCKETMTU; +#elif defined BT_USE_RFCOMM + connInfo.data.RfComm.remoteService + = btStuff->u.slave.remoteService; + connInfo.data.RfComm.maxFrameSize = BT_RF_DEFAULT_FRAMESIZE; + connInfo.data.RfComm.advancedCredit = INITIAL_CREDIT; +#else + XP_ASSERT(0); +#endif + /* sends btLibSocketEventConnectedOutbound */ + CALL_ERR( err, BtLibSocketConnect, btLibRefNum, + btStuff->dataSocket, &connInfo ); + if ( errNone == err ) { + SET_STATE( btStuff, PBTST_DATA_CONNECTED ); + } else if ( btLibErrPending == err ) { + SET_STATE( btStuff, PBTST_DATA_CONNECTING ); + } else { + SET_STATE( btStuff, PBTST_NONE ); + waitACL( btStuff ); + } + } else { + btStuff->dataSocket = SOCK_INVAL; + } + } + + break; + + case PBT_ACT_GOTDATA: +#ifdef BT_USE_RFCOMM + CALL_ERR( err, BtLibSocketAdvanceCredit, btLibRefNum, btStuff->dataSocket, 5 ); +#endif + pbt_handoffIncoming( btStuff, proc ); + break; + + case PBT_ACT_TRYSEND: + pbt_send_pending( btStuff ); + break; + + case PBT_ACT_TELLCONN: + XP_ASSERT( !!proc ); + info.evt = BTCBEVT_CONN; + (*proc)( btStuff->globals, &info ); + break; + + default: + XP_ASSERT( 0 ); + } + LOG_RETURN_VOID(); +} /* pbt_do_work */ + +static void +pbt_postpone( PalmBTStuff* btStuff, PBT_ACTION act ) +{ + postEmptyEvent( noopEvent ); + + XP_LOGF( "%s(%s)", __func__, actToStr(act) ); + + if ( DUPLICATES_OK(act) + || (btStuff->queueLen == 0) + || (act != btStuff->actQueue[btStuff->queueLen-1]) ) { + btStuff->actQueue[ btStuff->queueLen++ ] = act; + XP_ASSERT( btStuff->queueLen < PBT_MAX_ACTS ); +#ifdef DEBUG + if ( btStuff->queueLen > btStuff->stats.maxQueueLen ) { + btStuff->stats.maxQueueLen = btStuff->queueLen; + } +#endif + } else { + XP_LOGF( "%s already at tail of queue; not adding", actToStr(act) ); + } + debug_logQueue( btStuff ); +} + +static void +getSizeIndex( PBT_queue* queue, XP_U16* index, XP_U16* totalP ) +{ + XP_U16 i; + XP_U16 total = 0; + for ( i = 0; i < MAX_PACKETS; ++i ) { + XP_U16 curlen = queue->lens[i]; + if ( !curlen ) { + break; + } + total += curlen; + } + XP_LOGF( "%s=>index:%d, total: %d", __func__, i, total ); + *index = i; + *totalP = total; +} /* getSizeIndex */ + +static XP_S16 +pbt_enqueue( PBT_queue* queue, const XP_U8* data, const XP_S16 len, + XP_Bool addLen, XP_Bool append ) +{ + XP_S16 result; + XP_U16 index; + XP_U16 total = 0; + XP_U16 lensiz = 0; + + XP_ASSERT( len > 0 || !addLen ); + + if ( addLen ) { + lensiz = sizeof(len); + } + + getSizeIndex( queue, &index, &total ); + + if ( append ) { + XP_ASSERT( index > 0 ); + --index; + } + + if ( (index < MAX_PACKETS) && ((total + len + lensiz) < sizeof(queue->bufs)) ) { + if ( !append ) { + queue->lens[index] = 0; + } + + queue->lens[index] += len + lensiz; + if ( addLen ) { + XP_U16 plen = XP_HTONS(len); + XP_LOGF( "writing plen: %x", plen ); + XP_MEMCPY( &queue->bufs[total], &plen, sizeof(plen) ); + total += sizeof(plen); + } + XP_MEMCPY( &queue->bufs[total], data, len ); +/* XP_LOGF( "%s: adding %d; total now %d (%d packets)", + __func__, */ +/* len, len+total, i+1 ); */ + result = len; + } else { + XP_LOGF( "%s: dropping packet of len %d", __func__, len ); + result = -1; + } + return result; +} /* pbt_enqueue */ + +static void +pbt_handoffIncoming( PalmBTStuff* btStuff, BtCbEvtProc proc ) +{ + const XP_U8* buf; + XP_U16 len; + + len = pbt_peekQueue( &btStuff->vol.in, &buf ); + + if ( len > 0 ) { + BtCbEvtInfo info; + CommsAddrRec fromAddr; + + XP_ASSERT( !!proc ); + + fromAddr.conType = COMMS_CONN_BT; + XP_MEMCPY( &fromAddr.u.bt.btAddr, &btStuff->otherAddr, + sizeof(fromAddr.u.bt.btAddr) ); + + info.evt = BTCBEVT_DATA; + info.u.data.fromAddr = &fromAddr; +#ifdef BT_USE_RFCOMM + XP_LOGF( "plen=%d; len=%d", *(XP_U16*)buf, len-2 ); + XP_ASSERT( *(XP_U16*)buf == len - sizeof(XP_U16) ); /* firing */ + info.u.data.len = len - sizeof(XP_U16); + info.u.data.data = buf + sizeof(XP_U16); +#else + info.u.data.len = len; + info.u.data.data = buf; +#endif + (*proc)( btStuff->globals, &info ); + + pbt_shiftQueue( &btStuff->vol.in ); + } +} /* pbt_handoffIncoming */ + +static void +pbt_reset_buffers( PalmBTStuff* btStuff ) +{ + LOG_FUNC(); + XP_MEMSET( &btStuff->vol, 0, sizeof(btStuff->vol) ); + + LOG_RETURN_VOID(); +} + +static XP_Bool +btTimerProc( void* closure, XWTimerReason why ) +{ + PalmBTStuff* btStuff; + XP_ASSERT( why == TIMER_ACL_BACKOFF ); + btStuff = (PalmBTStuff*)closure; + if ( GET_STATE(btStuff) != PBTST_NONE ) { + XP_LOGF( "%s ignoring; have changed states", __func__ ); + } else if ( PBT_SLAVE != btStuff->picoRole ) { + XP_LOGF( "%s ignoring; have changed roles", __func__ ); + } else { + pbt_postpone( btStuff, PBT_ACT_CONNECT_ACL ); + } + return XP_TRUE; +} + +static void +waitACL( PalmBTStuff* btStuff ) +{ + util_setTimer( &btStuff->globals->util, TIMER_ACL_BACKOFF, + ACL_WAIT_INTERVAL, btTimerProc, btStuff ); +} + +static Err +pbd_discover( PalmBTStuff* btStuff, BtLibDeviceAddressType* addr ) +{ + Err err; + const BtLibClassOfDeviceType deviceFilter + = btLibCOD_ServiceAny + | btLibCOD_Major_Any // btLibCOD_Major_Computer + | btLibCOD_Minor_Comp_Any; //btLibCOD_Minor_Comp_Palm; + + CALL_ERR( err, BtLibDiscoverSingleDevice, btStuff->btLibRefNum, + "Crosswords host", (BtLibClassOfDeviceType*)&deviceFilter, 1, + addr, false, false ); + return err; +} /* pbd_discover */ + +static void +pbt_setup_slave( PalmBTStuff* btStuff, const CommsAddrRec* addr ) +{ + XP_LOGF( "%s; state=%s", __func__, stateToStr(GET_STATE(btStuff))); + + if ( btStuff->picoRole == PBT_MASTER ) { + pbt_takedown_master( btStuff ); + } + btStuff->picoRole = PBT_SLAVE; + btStuff->u.slave.sdpSocket = SOCK_INVAL; + + if ( !!addr ) { + char buf[64]; + if ( errNone == + BtLibAddrBtdToA( btStuff->btLibRefNum, + (BtLibDeviceAddressType*)&addr->u.bt.btAddr, + buf, sizeof(buf) ) ) { + XP_LOGF( "%s(%s)", __func__, buf ); + } + } else { + XP_LOGF( "null addr" ); + } + + if ( GET_STATE(btStuff) == PBTST_NONE ) { + pbt_postpone( btStuff, PBT_ACT_CONNECT_ACL ); + } else { + XP_LOGF( "%s: doing nothing", __func__ ); + } + LOG_RETURN_VOID(); +} /* pbt_setup_slave */ + +static void +pbt_takedown_slave( PalmBTStuff* btStuff ) +{ + pbt_killLinks( btStuff ); + btStuff->picoRole = PBT_UNINIT; +} + +static PalmBTStuff* +pbt_checkInit( PalmAppGlobals* globals, XP_Bool* userCancelledP ) +{ + PalmBTStuff* btStuff = globals->btStuff; + XP_Bool userCancelled = XP_FALSE; + if ( !btStuff ) { + Err err; + XP_U16 btLibRefNum; + + CALL_ERR( err, SysLibFind, btLibName, &btLibRefNum ); + if ( errNone == err ) { + CALL_ERR( err, BtLibOpen, btLibRefNum, false ); + + userCancelled = err == btLibErrBluetoothOff; + + /* BT is probably off if this fails */ + if ( errNone == err ) { + btStuff = XP_MALLOC( globals->mpool, sizeof(*btStuff) ); + XP_ASSERT( !!btStuff ); + globals->btStuff = btStuff; + + XP_MEMSET( btStuff, 0, sizeof(*btStuff) ); + btStuff->globals = globals; + btStuff->btLibRefNum = btLibRefNum; + + btStuff->dataSocket = SOCK_INVAL; + btStuff->u.master.listenSocket = SOCK_INVAL; + btStuff->u.slave.sdpSocket = SOCK_INVAL; + + CALL_ERR( err, BtLibRegisterManagementNotification, + btLibRefNum, libMgmtCallback, (UInt32)btStuff ); + } + } + } + + if ( !!userCancelledP ) { + *userCancelledP = userCancelled; + } + + return btStuff; +} /* pbt_checkInit */ + +static void +pbt_killLinks( PalmBTStuff* btStuff ) +{ + Err err; + + pbt_close_datasocket( btStuff ); + + if ( PBT_SLAVE == btStuff->picoRole ) { + pbt_close_sdpsocket( btStuff ); + } + + /* Harm in calling this when not connected? */ + if ( GET_STATE(btStuff) != PBTST_NONE ) { + SET_STATE( btStuff, PBTST_NONE ); /* set first */ + /* sends btLibManagementEventACLDisconnect */ + CALL_ERR( err, BtLibLinkDisconnect, btStuff->btLibRefNum, + &btStuff->otherAddr ); + } +} /* pbt_killLinks */ + +static XP_Bool +pbt_checkAddress( PalmBTStuff* btStuff, const CommsAddrRec* addr ) +{ + XP_Bool addrOk; + LOG_FUNC(); + XP_ASSERT( !!addr ); + + addrOk = 0 == XP_MEMCMP( &btStuff->otherAddr, &addr->u.bt.btAddr.bits, + sizeof(addr->u.bt.btAddr.bits) ); + if ( !addrOk ) { + LOG_HEX( &btStuff->otherAddr, sizeof(addr->u.bt.btAddr.bits), + "cur" ); + LOG_HEX( &addr->u.bt.btAddr.bits, sizeof(addr->u.bt.btAddr.bits), + "new" ); + + pbt_killLinks( btStuff ); + + XP_MEMCPY( &btStuff->otherAddr, &addr->u.bt.btAddr, + sizeof(btStuff->otherAddr) ); + } + LOG_RETURNF( "%d", (int)addrOk ); + return addrOk; +} /* pbt_checkAddress */ + +#ifdef DEBUG +static void +pbt_setstate( PalmBTStuff* btStuff, PBT_STATE newState, const char* whence ) +{ + btStuff->p_connState = newState; + XP_LOGF( "setting state to %s, from %s", stateToStr(newState), whence ); +} +#endif + +#ifdef BT_USE_RFCOMM + +static XP_U16 +pbt_packetPending( PalmBTStuff* btStuff ) +{ + XP_U16 pending; + XP_U16 index, total; + PBT_queue* queue = &btStuff->vol.in; + + /* Packet consists of two bytes of len plus len bytes of data. An + incomplete packet has len but less than len bytes of data. When we + write the len we add a packet but that's all. + + Total and index get us beyond the last packet written. If index is 0, + nothing's been written so packet is pending. Otherwise we can back + index off and look at the buffer there. Buffer starts at total - lens[--index]. + Len will be written there. If lens[index] is less, it's pending. + */ + getSizeIndex( queue, &index, &total ); + if ( total < sizeof(XP_U16) ) { + XP_ASSERT( total == 0 ); + pending = 0; + } else { + XP_U16 curLen, plen; + XP_U8* curStart; + --index; + curLen = queue->lens[index]; + curStart = &queue->bufs[total-curLen]; + plen = *(XP_U16*)curStart; + pending = plen - (curLen - sizeof(plen)); + } + LOG_RETURNF( "%d", pending ); + return pending; +} + +static void +pbt_assemble( PalmBTStuff* btStuff, unsigned char* data, XP_U16 len ) +{ + XP_U16 bytesPending = pbt_packetPending( btStuff); + LOG_FUNC(); + if ( bytesPending == 0 ) { + XP_U16 plen; + /* will need to handle case where len comes in separate packets!!!! */ + XP_ASSERT( len >= sizeof(plen) ); + plen = *(XP_U16*)data; + data += sizeof(plen); + len -= sizeof(plen); + bytesPending = XP_NTOHS(plen); + + /* Start the packet */ + pbt_enqueue( &btStuff->vol.in, (XP_U8*)&bytesPending, sizeof(bytesPending), + XP_FALSE, XP_FALSE ); + } + + /* if len is >= bytesPending, we have a packet. Add bytesPending bytes, + then recurse with remaining bytes. If len is < bytesPending, just + consume the bytes and return. */ + pbt_enqueue( &btStuff->vol.in, data, XP_MIN( len, bytesPending ), + XP_FALSE, XP_TRUE ); + + if ( len >= bytesPending ) { + len -= bytesPending; + data += bytesPending; + pbt_postpone( btStuff, PBT_ACT_GOTDATA ); + if ( len > 0 ) { + pbt_assemble( btStuff, data, len ); + } + } +} +#endif + +static void +socketCallback( BtLibSocketEventType* sEvent, UInt32 refCon ) +{ + PalmBTStuff* btStuff = (PalmBTStuff*)refCon; + BtLibSocketEventEnum event = sEvent->event; + Err err; + + XP_LOGF( "%s(%s); status:%s", __func__, btEvtToStr(event), + btErrToStr(sEvent->status) ); + + switch( event ) { + case btLibSocketEventConnectRequest: + if ( btStuff->picoRole == PBT_MASTER ) { + /* sends btLibSocketEventConnectedInbound */ + CALL_ERR( err, BtLibSocketRespondToConnection, btStuff->btLibRefNum, + sEvent->socket, true ); + } else { + XP_LOGF( "ignoring b/c not master" ); + } + break; + case btLibSocketEventConnectedInbound: + XP_ASSERT( btStuff->picoRole == PBT_MASTER ); + if ( sEvent->status == errNone ) { + btStuff->dataSocket = sEvent->eventData.newSocket; + XP_LOGF( "we have a data socket!!!" ); + pbt_postpone( btStuff, PBT_ACT_TELLCONN ); + SET_STATE( btStuff, PBTST_DATA_CONNECTED ); + } + break; + case btLibSocketEventConnectedOutbound: + if ( errNone == sEvent->status ) { + SET_STATE( btStuff, PBTST_DATA_CONNECTED ); + pbt_postpone( btStuff, PBT_ACT_TELLCONN ); + } + break; + case btLibSocketEventData: + XP_ASSERT( sEvent->status == errNone ); + XP_ASSERT( sEvent->socket == btStuff->dataSocket ); + { + XP_U8* data = sEvent->eventData.data.data; + XP_U16 len = sEvent->eventData.data.dataLen; +#ifdef LOG_BTIO + LOG_HEX( data, len, "btLibSocketEventData" ); +#endif +#if defined BT_USE_RFCOMM + pbt_assemble( btStuff, data, len ); +#else + if ( 0 < pbt_enqueue( &btStuff->vol.in, data, len, XP_FALSE, XP_FALSE ) ) { + pbt_postpone( btStuff, PBT_ACT_GOTDATA ); + } +#endif +#ifdef DEBUG + btStuff->stats.totalRcvd += sEvent->eventData.data.dataLen; +#endif + } + break; + + case btLibSocketEventSendComplete: + btStuff->vol.sendInProgress = XP_FALSE; +#ifdef DEBUG + btStuff->stats.totalSent += +#endif + pbt_shiftQueue( &btStuff->vol.out ); + pbt_postpone( btStuff, PBT_ACT_TRYSEND ); /* in case there's more */ + break; + + case btLibSocketEventDisconnected: + if ( PBT_SLAVE == btStuff->picoRole ) { + pbt_killLinks( btStuff ); + waitACL( btStuff ); + } else if ( PBT_MASTER == btStuff->picoRole ) { + pbt_close_datasocket( btStuff ); + SET_STATE( btStuff, PBTST_LISTENING ); + } + break; + +#if defined BT_USE_L2CAP + case btLibSocketEventSdpGetPsmByUuid: +#elif defined BT_USE_RFCOMM + case btLibSocketEventSdpGetServerChannelByUuid: +#endif + XP_ASSERT( sEvent->socket == btStuff->u.slave.sdpSocket ); + pbt_close_sdpsocket( btStuff ); + if ( sEvent->status == errNone ) { +#if defined BT_USE_L2CAP + btStuff->u.slave.remotePsm = sEvent->eventData.sdpByUuid.param.psm; +#elif defined BT_USE_RFCOMM + btStuff->u.slave.remoteService + = sEvent->eventData.sdpByUuid.param.channel; + XP_LOGF( "got remoteService of %d", + btStuff->u.slave.remoteService ); +#endif + SET_STATE( btStuff, PBTST_SDP_QUERIED ); + pbt_postpone( btStuff, PBT_ACT_CONNECT_DATA ); + } else if ( sEvent->status == btLibErrSdpQueryDisconnect ) { + /* Maybe we can just ignore this... */ + XP_ASSERT( GET_STATE(btStuff) == PBTST_NONE ); /* still still firing!!! */ + pbt_killLinks( btStuff ); + waitACL( btStuff ); + } else { + if ( sEvent->status == btLibErrSdpAttributeNotSet ) { + XP_LOGF( "**** Host not running!!! ****" ); + } + /* try again???? */ + SET_STATE( btStuff, PBTST_ACL_CONNECTED ); + pbt_postpone( btStuff, PBT_ACT_GETSDP ); + } + /* Do we want to try again in a few seconds? Is this where the timer + belongs? */ + break; + + case btLibL2DiscConnPsmUnsupported: + XP_ASSERT( 0 ); /* is this getting called? It's an event *and* status??? */ + /* Probably need to warn the user when this happens since not having + established trust will be a common error. Or: figure out if + there's a way to fall back and establish trust programatically. + For alpha just do the error message. :-) Also, no point in + continuing to try to connect. User will have to quit in order to + establish trust. So warn once per inited session. */ + break; + + default: + break; + } + LOG_RETURN_VOID(); +} /* socketCallback */ + +/*********************************************************************** + * Callbacks + ***********************************************************************/ +static void +libMgmtCallback( BtLibManagementEventType* mEvent, UInt32 refCon ) +{ + PalmBTStuff* btStuff = (PalmBTStuff*)refCon; + BtLibManagementEventEnum event = mEvent->event; + XP_LOGF( "%s(%s); status=%s", __func__, mgmtEvtToStr(event), + btErrToStr(mEvent->status) ); + + switch( event ) { + case btLibManagementEventAccessibilityChange: + XP_LOGF( "%s", connEnumToStr(mEvent->eventData.accessible) ); + btStuff->accState = mEvent->eventData.accessible; + break; + case btLibManagementEventRadioState: +/* XP_LOGF( "status: %s", btErrToStr(mEvent->status) ); */ + break; + case btLibManagementEventACLConnectOutbound: + if ( btLibErrNoError == mEvent->status ) { + SET_STATE( btStuff, PBTST_ACL_CONNECTED ); + XP_LOGF( "successful ACL connection to master!" ); + pbt_postpone( btStuff, PBT_ACT_GETSDP ); + } else { + SET_STATE( btStuff, PBTST_NONE ); + waitACL( btStuff ); + } + break; + + case btLibManagementEventACLConnectInbound: + if ( btLibErrNoError == mEvent->status ) { + XP_LOGF( "successful ACL connection!" ); + XP_MEMCPY( &btStuff->otherAddr, + &mEvent->eventData.bdAddr, + sizeof(btStuff->otherAddr) ); + SET_STATE( btStuff, PBTST_ACL_CONNECTED ); + } + break; + case btLibManagementEventACLDisconnect: + if ( mEvent->status == btLibMeStatusLocalTerminated ) { + /* We caused this, probably switching roles. Perhaps we've already + done what's needed, e.g. opened socket to listen */ + } else { + /* This is getting called from inside the BtLibLinkDisconnect + call!!!! */ + XP_ASSERT( 0 == XP_MEMCMP( &mEvent->eventData.bdAddr, + &btStuff->otherAddr, 6 ) ); + pbt_close_datasocket( btStuff ); + SET_STATE( btStuff, PBTST_NONE ); + /* See comment at btLibSocketEventDisconnected */ + if ( PBT_SLAVE == btStuff->picoRole ) { + waitACL( btStuff ); + } + } + break; + default: + XP_LOGF( "%s: %s not handled", __func__, mgmtEvtToStr(event)); + break; + } + LOG_RETURN_VOID(); +} /* libMgmtCallback */ + +/*********************************************************************** + * Debug helpers for verbose logging + ***********************************************************************/ +#ifdef DEBUG +# define CASESTR(e) case(e): return #e + +static const char* +stateToStr(PBT_STATE st) +{ + switch( st ) { + CASESTR(PBTST_NONE); + CASESTR(PBTST_LISTENING); + CASESTR(PBTST_ACL_CONNECTING); + CASESTR(PBTST_SDP_QUERYING); + CASESTR(PBTST_SDP_QUERIED); + CASESTR(PBTST_ACL_CONNECTED); + CASESTR(PBTST_DATA_CONNECTING); + CASESTR(PBTST_DATA_CONNECTED); + default: + XP_ASSERT(0); + return ""; + } +} /* stateToStr */ + +static const char* +actToStr(PBT_ACTION act) +{ + switch( act ) { + CASESTR(PBT_ACT_NONE); + CASESTR(PBT_ACT_MASTER_RESET); + CASESTR(PBT_ACT_SLAVE_RESET); + CASESTR(PBT_ACT_SETUP_LISTEN); + CASESTR(PBT_ACT_CONNECT_ACL); + CASESTR(PBT_ACT_GETSDP); + CASESTR(PBT_ACT_CONNECT_DATA); + CASESTR(PBT_ACT_GOTDATA); + CASESTR(PBT_ACT_TRYSEND); + CASESTR(PBT_ACT_TELLCONN); + default: + XP_ASSERT(0); + return ""; + } +} /* actToStr */ + +static const char* +connEnumToStr( BtLibAccessibleModeEnum mode ) +{ + switch( mode ) { + CASESTR(btLibNotAccessible); + CASESTR(btLibConnectableOnly); + CASESTR(btLibDiscoverableAndConnectable); + case 0x0006: + /* I've seen this on 68K even. Seems to happen when the other + device changes roles (temporarily). */ + return "undoc_06"; + case 0x00F8: /* seen on ARM only */ + return "undoc_F8"; + default: + XP_ASSERT(0); + XP_LOGF( "%s: got 0x%x", __func__, mode ); + return ""; + } +} + +static const char* +btEvtToStr( BtLibSocketEventEnum evt ) +{ + switch( evt ) { + CASESTR(btLibSocketEventConnectRequest); + CASESTR(btLibSocketEventConnectedOutbound); + CASESTR(btLibSocketEventConnectedInbound); + CASESTR(btLibSocketEventDisconnected); + CASESTR(btLibSocketEventData); + CASESTR(btLibSocketEventSendComplete); + CASESTR(btLibSocketEventSdpServiceRecordHandle); + CASESTR(btLibSocketEventSdpGetAttribute); + CASESTR(btLibSocketEventSdpGetStringLen); + CASESTR(btLibSocketEventSdpGetNumListEntries); + CASESTR(btLibSocketEventSdpGetNumLists); + CASESTR(btLibSocketEventSdpGetRawAttribute); + CASESTR(btLibSocketEventSdpGetRawAttributeSize); + CASESTR(btLibSocketEventSdpGetServerChannelByUuid); + CASESTR(btLibSocketEventSdpGetPsmByUuid); + default: + XP_ASSERT(0); + return ""; + } +} /* btEvtToStr */ + +static const char* +mgmtEvtToStr( BtLibManagementEventEnum event ) +{ + switch( event ) { + CASESTR(btLibManagementEventRadioState); + CASESTR(btLibManagementEventInquiryResult); + CASESTR(btLibManagementEventInquiryComplete); + CASESTR(btLibManagementEventInquiryCanceled); + CASESTR(btLibManagementEventACLDisconnect); + CASESTR(btLibManagementEventACLConnectInbound); + CASESTR(btLibManagementEventACLConnectOutbound); + CASESTR(btLibManagementEventPiconetCreated); + CASESTR(btLibManagementEventPiconetDestroyed); + CASESTR(btLibManagementEventModeChange); + CASESTR(btLibManagementEventAccessibilityChange); + CASESTR(btLibManagementEventEncryptionChange); + CASESTR(btLibManagementEventRoleChange); + CASESTR(btLibManagementEventNameResult); + CASESTR(btLibManagementEventLocalNameChange); + CASESTR(btLibManagementEventAuthenticationComplete); + CASESTR(btLibManagementEventPasskeyRequest); + CASESTR(btLibManagementEventPasskeyRequestComplete); + CASESTR(btLibManagementEventPairingComplete); + default: + XP_ASSERT(0); + return "unknown"; + } +} /* mgmtEvtToStr */ + +static const char* +btErrToStr( Err err ) +{ + switch ( err ) { + CASESTR(errNone); + CASESTR(btLibErrError); + CASESTR(btLibErrNotOpen); + CASESTR(btLibErrBluetoothOff); + CASESTR(btLibErrNoPrefs); + CASESTR(btLibErrAlreadyOpen); + CASESTR(btLibErrOutOfMemory); + CASESTR(btLibErrFailed); + CASESTR(btLibErrInProgress); + CASESTR(btLibErrParamError); + CASESTR(btLibErrTooMany); + CASESTR(btLibErrPending); + CASESTR(btLibErrNotInProgress); + CASESTR(btLibErrRadioInitFailed); + CASESTR(btLibErrRadioFatal); + CASESTR(btLibErrRadioInitialized); + CASESTR(btLibErrRadioSleepWake); + CASESTR(btLibErrNoConnection); + CASESTR(btLibErrAlreadyRegistered); + CASESTR(btLibErrNoAclLink); + CASESTR(btLibErrSdpRemoteRecord); + CASESTR(btLibErrSdpAdvertised); + CASESTR(btLibErrSdpFormat); + CASESTR(btLibErrSdpNotAdvertised); + CASESTR(btLibErrSdpQueryVersion); + CASESTR(btLibErrSdpQueryHandle); + CASESTR(btLibErrSdpQuerySyntax); + CASESTR(btLibErrSdpQueryPduSize); + CASESTR(btLibErrSdpQueryContinuation); + CASESTR(btLibErrSdpQueryResources); + CASESTR(btLibErrSdpQueryDisconnect); + CASESTR(btLibErrSdpInvalidResponse); + CASESTR(btLibErrSdpAttributeNotSet); + CASESTR(btLibErrSdpMapped); + CASESTR(btLibErrSocket); + CASESTR(btLibErrSocketProtocol); + CASESTR(btLibErrSocketRole); + CASESTR(btLibErrSocketPsmUnavailable); + CASESTR(btLibErrSocketChannelUnavailable); + CASESTR(btLibErrSocketUserDisconnect); + CASESTR(btLibErrCanceled); + CASESTR(btLibErrBusy); + CASESTR(btLibMeStatusUnknownHciCommand); + CASESTR(btLibMeStatusNoConnection); + CASESTR(btLibMeStatusHardwareFailure); + CASESTR(btLibMeStatusPageTimeout); + CASESTR(btLibMeStatusAuthenticateFailure); + CASESTR(btLibMeStatusMissingKey); + CASESTR(btLibMeStatusMemoryFull); + CASESTR(btLibMeStatusConnnectionTimeout); + CASESTR(btLibMeStatusMaxConnections); + CASESTR(btLibMeStatusMaxScoConnections); + CASESTR(btLibMeStatusMaxAclConnections); + CASESTR(btLibMeStatusCommandDisallowed); + CASESTR(btLibMeStatusLimitedResources); + CASESTR(btLibMeStatusSecurityError); + CASESTR(btLibMeStatusPersonalDevice); + CASESTR(btLibMeStatusHostTimeout); + CASESTR(btLibMeStatusUnsupportedFeature); + CASESTR(btLibMeStatusInvalidHciParam); + CASESTR(btLibMeStatusUserTerminated); + CASESTR(btLibMeStatusLowResources); + CASESTR(btLibMeStatusPowerOff); + CASESTR(btLibMeStatusLocalTerminated); + CASESTR(btLibMeStatusRepeatedAttempts); + CASESTR(btLibMeStatusPairingNotAllowed); + CASESTR(btLibMeStatusUnknownLmpPDU); + CASESTR(btLibMeStatusUnsupportedRemote); + CASESTR(btLibMeStatusScoOffsetRejected); + CASESTR(btLibMeStatusScoIntervalRejected); + CASESTR(btLibMeStatusScoAirModeRejected); + CASESTR(btLibMeStatusInvalidLmpParam); + CASESTR(btLibMeStatusUnspecifiedError); + CASESTR(btLibMeStatusUnsupportedLmpParam); + CASESTR(btLibMeStatusRoleChangeNotAllowed); + CASESTR(btLibMeStatusLmpResponseTimeout); + CASESTR(btLibMeStatusLmpTransdCollision); + CASESTR(btLibMeStatusLmpPduNotAllowed); + CASESTR(btLibL2DiscReasonUnknown); + CASESTR(btLibL2DiscUserRequest); + CASESTR(btLibL2DiscRequestTimeout); + CASESTR(btLibL2DiscLinkDisc); + CASESTR(btLibL2DiscQosViolation); + CASESTR(btLibL2DiscSecurityBlock); + CASESTR(btLibL2DiscConnPsmUnsupported); + CASESTR(btLibL2DiscConnSecurityBlock); + CASESTR(btLibL2DiscConnNoResources); + CASESTR(btLibL2DiscConfigUnacceptable); + CASESTR(btLibL2DiscConfigReject); + CASESTR(btLibL2DiscConfigOptions); + CASESTR(btLibServiceShutdownAppUse); + CASESTR(btLibServiceShutdownPowerCycled); + CASESTR(btLibServiceShutdownAclDrop); + CASESTR(btLibServiceShutdownTimeout); + CASESTR(btLibServiceShutdownDetached); + CASESTR(btLibErrInUseByService); + CASESTR(btLibErrNoPiconet); + CASESTR(btLibErrRoleChange); + CASESTR(btLibErrSdpNotMapped); + CASESTR(btLibErrAlreadyConnected); + CASESTR(btLibErrStackNotOpen); + CASESTR(btLibErrBatteryTooLow); + CASESTR(btLibErrNotFound); + CASESTR(btLibNotYetSupported); + default: + return "unknown err"; + } +} /* btErrToStr */ + +static const char* +proleToString( PBT_PicoRole r ) +{ + switch ( r ) { + CASESTR(PBT_UNINIT); + CASESTR(PBT_MASTER); + CASESTR(PBT_SLAVE); + default: + XP_ASSERT(0); + return ""; + } +} + +static void +palm_bt_log( const char* btfunc, const char* func, Err err ) +{ +/* if ( errNone != err ) { */ + XP_LOGF( "%s=>%s (in %s)", btfunc, btErrToStr(err), func ); +/* } */ +} + +#endif /* DEBUG */ + +/* +use piconet? With that, HOST sets it up and clients join. That establishes +ACL links that can then be used to open sockets. I think. How to get from +piconet advertising to clients connecting? + +See http://www.palmos.com/dev/support/docs/palmos/BTCompanion.html + +NOTE: I've read conflicting reports on whether a listening socket is good for +accepting more than one inbound connection. Confirm. Or just do a piconet. + +*/ +#endif /* #ifdef XWFEATURE_BLUETOOTH */ diff --git a/xwords4/palm/palmbt.h b/xwords4/palm/palmbt.h new file mode 100644 index 000000000..0c6e0a395 --- /dev/null +++ b/xwords4/palm/palmbt.h @@ -0,0 +1,88 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2006 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _PALMBT_H_ +#define _PALMBT_H_ + +#ifdef XWFEATURE_BLUETOOTH + +#include "comms.h" +#include "palmmain.h" + +#define PALM_BT_NAME_LEN 48 + +/* Needed: feedback to main so status can be posted on the board. Events we + * might care about: + * + * - BT disconnected -- not available + * - Server up and socket available + * - client trying to connect (includes having ACL conn) + * - client has a connection (L2CAP socket open) + * - received data + * - sending data + * - done sending data + */ + +/** + * palm_bt_appendWaitTicks + * reduce waitTicks if have work to do + */ + + +void palm_bt_amendWaitTicks( PalmAppGlobals* globals, Int32* result ); + +XP_Bool palm_bt_init( PalmAppGlobals* globals, XP_Bool* userCancelled ); +void palm_bt_reset( PalmAppGlobals* globals ); +void palm_bt_close( PalmAppGlobals* globals ); + +typedef enum { + BTCBEVT_CONFIRM, BTCBEVT_CONN, BTCBEVT_DATA +} BtCbEvt; +typedef struct BtCbEvtInfo { + BtCbEvt evt; + union { + struct { + const char* hostName; + XP_Bool confirmed; + } confirm; + struct { + const void* data; + const CommsAddrRec* fromAddr; + XP_U8 len; + } data; + } u; +} BtCbEvtInfo; + +typedef void (*BtCbEvtProc)( PalmAppGlobals* globals, BtCbEvtInfo* evt ); +XP_Bool palm_bt_doWork( PalmAppGlobals* globals, BtCbEvtProc proc, BtUIState* btState ); + +void palm_bt_addrString( PalmAppGlobals* globals, const XP_BtAddr* btAddr, + XP_BtAddrStr* str ); + +XP_S16 palm_bt_send( const XP_U8* buf, XP_U16 len, const CommsAddrRec* addr, + PalmAppGlobals* globals, XP_Bool* userCancelled ); + +XP_Bool palm_bt_browse_device( PalmAppGlobals* globals, XP_BtAddr* btAddr, + XP_UCHAR* out, XP_U16 len ); + +#ifdef DEBUG +void palm_bt_getStats( PalmAppGlobals* globals, XWStreamCtxt* stream ); +#endif +#endif /* XWFEATURE_BLUETOOTH */ +#endif diff --git a/xwords4/palm/palmdbg.c b/xwords4/palm/palmdbg.c new file mode 100644 index 000000000..c709fa263 --- /dev/null +++ b/xwords4/palm/palmdbg.c @@ -0,0 +1,320 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2006 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef DEBUG + +#include "palmdbg.h" +#include "palmmain.h" +#include "xwords4defines.h" + +#define CASESTR(s) case s: return #s + +#define FUNC(f) #f + +const char* +frmObjId_2str( XP_U16 id ) +{ + switch( id ) { + CASESTR( XW_MAIN_FORM ); + CASESTR( XW_NEWGAMES_FORM ); + CASESTR( XW_ERROR_ALERT_ID ); + CASESTR( XW_DICTINFO_FORM ); + CASESTR( XW_ASK_FORM_ID ); + CASESTR( XW_PASSWORD_DIALOG_ID ); + CASESTR( XW_BLANK_DIALOG_ID ); + CASESTR( XW_COLORPREF_DIALOG_ID ); + CASESTR( XW_PREFS_FORM ); + CASESTR( XW_SAVEDGAMES_DIALOG_ID ); + CASESTR( XW_HINTCONFIG_FORM_ID ); + CASESTR( XW_CONNS_FORM ); +#ifdef FOR_GREMLINS + CASESTR( XW_GREMLIN_WARN_FORM_ID ); + CASESTR( GREMLIN_BOARD_GADGET_IDAUTOID ); + CASESTR( GREMLIN_TRAY_GADGET_IDAUTOID ); + CASESTR( XW_GREMLIN_DIVIDER_RIGHT ); + CASESTR( XW_GREMLIN_DIVIDER_LEFT ); + CASESTR( XW_GREMLIN_WARN_FIELD_ID ); +#endif +/* CASESTR( XW_ASK_MENU_ID ); */ +/* CASESTR( ASK_COPY_PULLDOWN_ID ); */ +/* CASESTR( ASK_SELECTALL_PULLDOWN_ID ); */ +/* CASESTR( XW_MAIN_MENU_ID ); */ + CASESTR( XW_MAIN_FLIP_BUTTON_ID ); + CASESTR( XW_MAIN_VALUE_BUTTON_ID ); + CASESTR( XW_MAIN_TRAY_BUTTON_ID ); + CASESTR( XW_MAIN_SCROLLBAR_ID ); + CASESTR( XW_MAIN_DONE_BUTTON_ID ); + CASESTR( XW_MAIN_JUGGLE_BUTTON_ID ); + CASESTR( XW_MAIN_TRADE_BUTTON_ID ); + CASESTR( XW_MAIN_HIDE_BUTTON_ID ); + CASESTR( XW_MAIN_HINT_BUTTON_ID ); + CASESTR( XW_MAIN_SHOWTRAY_BUTTON_ID ); +#ifdef FOR_GREMLINS +#endif + CASESTR( XW_NEWGAME_PULLDOWN_ID ); + CASESTR( XW_SAVEDGAMES_PULLDOWN_ID ); + CASESTR( XW_BEAMDICT_PULLDOWN_ID ); + CASESTR( XW_BEAMBOARD_PULLDOWN_ID ); + CASESTR( XW_PREFS_PULLDOWN_ID ); + CASESTR( XW_ABOUT_PULLDOWN_ID ); + CASESTR( XW_TILEVALUES_PULLDOWN_ID ); + CASESTR( XW_TILESLEFT_PULLDOWN_ID ); + CASESTR( XW_PASSWORDS_PULLDOWN_ID ); + CASESTR( XW_HISTORY_PULLDOWN_ID ); + CASESTR( XW_FINISH_PULLDOWN_ID ); + CASESTR( XW_RESENDIR_PULLDOWN_ID ); + CASESTR( XW_HINT_PULLDOWN_ID ); + CASESTR( XW_NEXTHINT_PULLDOWN_ID ); + CASESTR( XW_HINTCONFIG_PULLDOWN_ID ); + CASESTR( XW_UNDOCUR_PULLDOWN_ID ); + CASESTR( XW_UNDOLAST_PULLDOWN_ID ); + CASESTR( XW_DONE_PULLDOWN_ID ); + CASESTR( XW_JUGGLE_PULLDOWN_ID ); + CASESTR( XW_TRADEIN_PULLDOWN_ID ); + CASESTR( XW_HIDESHOWTRAY_PULLDOWN_ID ); +#ifdef FEATURE_DUALCHOOSE + CASESTR( XW_RUN68K_PULLDOWN_ID ); + CASESTR( XW_RUNARM_PULLDOWN_ID ); +#endif + CASESTR( XW_LOGFILE_PULLDOWN_ID ); + CASESTR( XW_LOGMEMO_PULLDOWN_ID ); + CASESTR( XW_CLEARLOGS_PULLDOWN_ID ); + CASESTR( XW_NETSTATS_PULLDOWN_ID ); + CASESTR( XW_MEMSTATS_PULLDOWN_ID ); + CASESTR( XW_BTSTATS_PULLDOWN_ID ); + CASESTR( XW_DICT_SELECTOR_ID ); + CASESTR( XW_OK_BUTTON_ID ); + CASESTR( XW_CANCEL_BUTTON_ID ); + CASESTR( XW_PLAYERNAME_1_FIELD_ID ); + CASESTR( XW_ROBOT_1_CHECKBOX_ID ); + CASESTR( XW_REMOTE_1_CHECKBOX_ID ); + CASESTR( XW_PLAYERPASSWD_1_TRIGGER_ID ); + CASESTR( XW_PLAYERNAME_2_FIELD_ID ); + CASESTR( XW_ROBOT_2_CHECKBOX_ID ); + CASESTR( XW_REMOTE_2_CHECKBOX_ID ); + CASESTR( XW_PLAYERPASSWD_2_TRIGGER_ID ); + CASESTR( XW_PLAYERNAME_3_FIELD_ID ); + CASESTR( XW_ROBOT_3_CHECKBOX_ID ); + CASESTR( XW_REMOTE_3_CHECKBOX_ID ); + CASESTR( XW_PLAYERPASSWD_3_TRIGGER_ID ); + CASESTR( XW_PLAYERNAME_4_FIELD_ID ); + CASESTR( XW_ROBOT_4_CHECKBOX_ID ); + CASESTR( XW_REMOTE_4_CHECKBOX_ID ); + CASESTR( XW_PLAYERPASSWD_4_TRIGGER_ID ); + CASESTR( XW_NPLAYERS_LIST_ID ); + CASESTR( XW_NPLAYERS_SELECTOR_ID ); + CASESTR( XW_PREFS_BUTTON_ID ); + CASESTR( XW_GINFO_JUGGLE_ID ); + CASESTR( XW_SOLO_GADGET_ID ); + CASESTR( XW_SERVER_GADGET_ID ); + CASESTR( XW_CLIENT_GADGET_ID ); + CASESTR( XW_SERVERTYPES_LIST_ID ); + CASESTR( XW_LOCAL_LABEL_ID ); + CASESTR( XW_TOTALP_FIELD_ID ); + CASESTR( XW_LOCALP_LABEL_ID ); + CASESTR( REFCON_GADGET_ID ); + CASESTR( XW_ASK_TXT_FIELD_ID ); + CASESTR( XW_ASK_YES_BUTTON_ID ); + CASESTR( XW_ASK_NO_BUTTON_ID ); + CASESTR( XW_ASK_SCROLLBAR_ID ); + CASESTR( XW_PASSWORD_CANCEL_BUTTON ); + CASESTR( XW_PASSWORD_NAME_LABEL ); + CASESTR( XW_PASSWORD_NEWNAME_LABEL ); + CASESTR( XW_PASSWORD_NAME_FIELD ); + CASESTR( XW_PASSWORD_PASS_FIELD ); + CASESTR( XW_PASSWORD_OK_BUTTON ); + CASESTR( XW_BLANK_LIST_ID ); + CASESTR( XW_BLANK_LABEL_FIELD_ID ); + CASESTR( XW_BLANK_OK_BUTTON_ID ); + CASESTR( XW_BLANK_PICK_BUTTON_ID ); + CASESTR( XW_BLANK_BACKUP_BUTTON_ID ); + CASESTR( XW_COLORS_FACTORY_BUTTON_ID ); + CASESTR( XW_COLORS_OK_BUTTON_ID ); + CASESTR( XW_COLORS_CANCEL_BUTTON_ID ); + CASESTR( XW_DICTINFO_LIST_ID ); + CASESTR( XW_DICTINFO_TRIGGER_ID ); + CASESTR( XW_PHONIES_TRIGGER_ID ); + CASESTR( XW_PHONIES_LABLE_ID ); + CASESTR( XW_PHONIES_LIST_ID ); + CASESTR( XW_DICTINFO_DONE_BUTTON_ID ); + CASESTR( XW_DICTINFO_BEAM_BUTTON_ID ); + CASESTR( XW_DICTINFO_CANCEL_BUTTON_ID ); + CASESTR( XW_PREFS_ALLGAMES_GADGET_ID ); + CASESTR( XW_PREFS_ONEGAME_GADGET_ID ); + CASESTR( XW_PREFS_TYPES_LIST_ID ); + CASESTR( XW_PREFS_PLAYERCOLORS_CHECKBOX_ID ); + CASESTR( XW_PREFS_PROGRESSBAR_CHECKBOX_ID ); + CASESTR( XW_PREFS_SHOWGRID_CHECKBOX_ID ); + CASESTR( XW_PREFS_SHOWARROW_CHECKBOX_ID ); + CASESTR( XW_PREFS_ROBOTSCORE_CHECKBOX_ID ); + CASESTR( XW_PREFS_HIDETRAYVAL_CHECKBOX_ID ); + CASESTR( XW_PREFS_ROBOTSMART_CHECKBOX_ID ); + CASESTR( XW_PREFS_PHONIES_LABEL_ID ); + CASESTR( XW_PREFS_PHONIES_TRIGGER_ID ); + CASESTR( XW_PREFS_BDSIZE_LABEL_ID ); + CASESTR( XW_PREFS_BDSIZE_SELECTOR_ID ); + CASESTR( XW_PREFS_NOHINTS_CHECKBOX_ID ); + CASESTR( XW_PREFS_TIMERON_CHECKBOX_ID ); + CASESTR( XW_PREFS_TIMER_FIELD_ID ); + CASESTR( XW_PREFS_PICKTILES_CHECKBOX_ID ); + CASESTR( XW_PREFS_HINTRECT_CHECKBOX_ID ); +#ifdef XWFEATURE_FIVEWAY + CASESTR( XW_BOARD_GADGET_ID ); + CASESTR( XW_SCOREBOARD_GADGET_ID ); + CASESTR( XW_TRAY_GADGET_ID ); +#endif + CASESTR( XW_PREFS_PHONIES_LIST_ID ); + CASESTR( XW_PREFS_BDSIZE_LIST_ID ); + CASESTR( XW_PREFS_CANCEL_BUTTON_ID ); + CASESTR( XW_PREFS_OK_BUTTON_ID ); + CASESTR( XW_SAVEDGAMES_LIST_ID ); + CASESTR( XW_SAVEDGAMES_NAME_FIELD ); + CASESTR( XW_SAVEDGAMES_USE_BUTTON ); + CASESTR( XW_SAVEDGAMES_DUPE_BUTTON ); + CASESTR( XW_SAVEDGAMES_DELETE_BUTTON ); + CASESTR( XW_SAVEDGAMES_OPEN_BUTTON ); + CASESTR( XW_SAVEDGAMES_DONE_BUTTON ); + CASESTR( XW_CONNS_CANCEL_BUTTON_ID ); + CASESTR( XW_CONNS_OK_BUTTON_ID ); + CASESTR( XW_CONNS_TYPE_TRIGGER_ID ); + CASESTR( XW_CONNS_TYPE_LIST_ID ); +#ifdef XWFEATURE_RELAY + CASESTR( XW_CONNS_RELAY_LABEL_ID ); + CASESTR( XW_CONNS_COOKIE_FIELD_ID ); + CASESTR( XW_CONNS_COOKIE_LABEL_ID ); + CASESTR( XW_CONNS_PORT_LABEL_ID ); + CASESTR( XW_CONNS_RELAY_FIELD_ID ); + CASESTR( XW_CONNS_PORT_FIELD_ID ); +#endif +#ifdef XWFEATURE_BLUETOOTH + CASESTR( XW_CONNS_BT_HOSTNAME_LABEL_ID ); + CASESTR( XW_CONNS_BT_HOSTTRIGGER_ID ); +#endif + CASESTR( XW_HINTCONFIG_MINLIST_ID ); + CASESTR( XW_HINTCONFIG_MAXLIST_ID ); + CASESTR( XW_HINTCONFIG_MAXSELECTOR_ID ); + CASESTR( XW_HINTCONFIG_MINSELECTOR_ID ); + CASESTR( XW_HINTCONFIG_OK_ID ); + CASESTR( XW_HINTCONFIG_CANCEL_ID ); + default: return FUNC(__func__) " unknown"; + } +} + +const char* +eType_2str( eventsEnum eType ) +{ + switch( eType ) { + CASESTR(nilEvent); + CASESTR(penDownEvent); + CASESTR(penUpEvent); + CASESTR(penMoveEvent); + CASESTR(keyDownEvent); + CASESTR(winEnterEvent); + CASESTR(winExitEvent); + CASESTR(ctlEnterEvent); + CASESTR(ctlExitEvent); + CASESTR(ctlSelectEvent); + CASESTR(ctlRepeatEvent); + CASESTR(lstEnterEvent); + CASESTR(lstSelectEvent); + CASESTR(lstExitEvent); + CASESTR(popSelectEvent); + CASESTR(fldEnterEvent); + CASESTR(fldHeightChangedEvent); + CASESTR(fldChangedEvent); + CASESTR(tblEnterEvent); + CASESTR(tblSelectEvent); + CASESTR(daySelectEvent); + CASESTR(menuEvent); + CASESTR(appStopEvent); + CASESTR(frmLoadEvent); + CASESTR(frmOpenEvent); + CASESTR(frmGotoEvent); + CASESTR(frmUpdateEvent); + CASESTR(frmSaveEvent); + CASESTR(frmCloseEvent); + CASESTR(frmTitleEnterEvent); + CASESTR(frmTitleSelectEvent); + CASESTR(tblExitEvent); + CASESTR(sclEnterEvent); + CASESTR(sclExitEvent); + CASESTR(sclRepeatEvent); + CASESTR(tsmConfirmEvent); + CASESTR(tsmFepButtonEvent); + CASESTR(tsmFepModeEvent); + CASESTR(attnIndicatorEnterEvent); + CASESTR(attnIndicatorSelectEvent); + CASESTR(menuCmdBarOpenEvent); + CASESTR(menuOpenEvent); + CASESTR(menuCloseEvent); + CASESTR(frmGadgetEnterEvent); + CASESTR(frmGadgetMiscEvent); + + CASESTR(firstINetLibEvent); + CASESTR(firstWebLibEvent); + CASESTR(telAsyncReplyEvent); + + CASESTR(keyUpEvent); + CASESTR(keyHoldEvent); + CASESTR(frmObjectFocusTakeEvent); + CASESTR(frmObjectFocusLostEvent); + + CASESTR(firstLicenseeEvent); + CASESTR(lastLicenseeEvent); + + CASESTR(lastUserEvent); + + CASESTR( dictSelectedEvent ); + CASESTR( newGameOkEvent ); + CASESTR( newGameCancelEvent); + CASESTR( loadGameEvent); + CASESTR( prefsChangedEvent); + CASESTR( openSavedGameEvent); +#ifdef XWFEATURE_FIVEWAY + CASESTR( updateAfterFocusEvent); +#endif + CASESTR( DOWN_ARROW_RESID ); + CASESTR( RIGHT_ARROW_RESID ); + CASESTR( FLIP_BUTTON_BMP_RES_ID ); + CASESTR( VALUE_BUTTON_BMP_RES_ID ); + CASESTR( HINT_BUTTON_BMP_RES_ID ); + CASESTR( TRAY_BUTTONS_BMP_RES_ID ); + CASESTR( SHOWTRAY_BUTTON_BMP_RES_ID ); + CASESTR( STAR_BMP_RES_ID ); + +#ifdef XWFEATURE_BLUETOOTH + CASESTR( BTSTATUS_NONE_RES_ID ); + CASESTR( BTSTATUS_LISTENING_RES_ID ); + CASESTR( BTSTATUS_SEEKING_RES_ID ); + CASESTR( BTSTATUS_CONNECTED_RES_ID ); +#endif + +#ifdef FEATURE_SILK + CASESTR( doResizeWinEvent ); +#endif + default: + return ""; + break; + } +} /* eType_2str */ + +#undef CASESTR +#undef FUNC + +#endif diff --git a/xwords4/palm/palmdbg.h b/xwords4/palm/palmdbg.h new file mode 100644 index 000000000..736ff142c --- /dev/null +++ b/xwords4/palm/palmdbg.h @@ -0,0 +1,38 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2006-2007 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _PALMDBG_H_ +#define _PALMDBG_H_ + +#include "comtypes.h" +#include + +const char* frmObjId_2str( XP_U16 id ); +const char* eType_2str( eventsEnum eType ); + +/* Useful for writing pace_man functions. */ +#define LOG_OFFSET( s, f ) \ + { s _s; \ + XP_LOGF( "offset of " #f " in " #s \ + ": %d (size: %ld)", OFFSET_OF( s, f ), \ + sizeof(_s.f) ); \ + } + +#endif diff --git a/xwords4/palm/palmdict.c b/xwords4/palm/palmdict.c new file mode 100644 index 000000000..02084f39d --- /dev/null +++ b/xwords4/palm/palmdict.c @@ -0,0 +1,475 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4;-*- */ +/* + * Copyright 1997-2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include + +#include "dictnryp.h" +#include "dawg.h" +#include "strutils.h" +#include "palmdict.h" +#include "dictlist.h" +#include "dictui.h" +#include "palmmain.h" +#include "pace_man.h" /* READ_UNALIGNED16 */ +#include "LocalizedStrIncludes.h" + +typedef struct DictStart { + unsigned long indexStart; + array_edge* array; +} DictStart; + + +#define NO_REFCOUNT -2 + +struct PalmDictionaryCtxt { + DictionaryCtxt super; + dawg_header* headerRecP; + DmOpenRef dbRef; + XP_U16 nRecords; + UInt16 cardNo; /* added to track VFS imported file for */ + LocalID dbID; /* deletion later */ + XP_UCHAR location; /* VFS or storage mem */ + XP_S8 refCount; + PalmDictList* dl; + DictStart dictStarts[1]; +}; + +#ifdef NODE_CAN_4 +typedef unsigned short FaceType; +#else +typedef unsigned char FaceType; +#endif + +static void palm_dictionary_destroy( DictionaryCtxt* dict ); +static XP_U16 countSpecials( FaceType* ptr, UInt16 nChars ); +static void setupSpecials( MPFORMAL PalmDictionaryCtxt* ctxt, + Xloc_specialEntry* specialStart, XP_U16 nSpecials ); +static array_edge* palm_dict_edge_for_index_multi( const DictionaryCtxt* dict, + XP_U32 index ); + +DictionaryCtxt* +palm_dictionary_make( MPFORMAL PalmAppGlobals* globals, + const XP_UCHAR* dictName, PalmDictList* dl ) +{ + Boolean found; + UInt16 cardNo; + LocalID dbID; + DmOpenRef dbRef; + PalmDictionaryCtxt tDictBuf; + PalmDictionaryCtxt* ctxt; + MemHandle tmpH; + dawg_header* headerRecP; + unsigned char* charPtr; + UInt16 nChars, nSpecials; + XP_U32 offset; + DictListEntry* dle; + Err err; + XP_U16 i; + FaceType* facePtr; +#ifdef NODE_CAN_4 + XP_U16 flags; + XP_U16 nodeSize = 3; /* init to satisfy compiler */ +#endif + XP_U32 totalSize; + + /* check and see if there's already a dict for this name. If yes, + increment its refcount and return. */ + if ( !!dictName && getDictWithName( dl, dictName, &dle ) ) { + PalmDictionaryCtxt* dict = (PalmDictionaryCtxt*)dle->dict; + if ( !!dict ) { + ++dict->refCount; + return (DictionaryCtxt*)dict; + } + } + + if ( !!dictName ) { + ctxt = &tDictBuf; + } else { + /* If the name's NULL, we still need to create a dict, as the server + may be called to fill it in from the stream later. */ + ctxt = XP_MALLOC( mpool, sizeof(*ctxt) ); + } + + XP_MEMSET( ctxt, 0, sizeof(*ctxt) ); + MPASSIGN( ctxt->super.mpool, mpool ); + + dict_super_init( (DictionaryCtxt*)ctxt ); + + if ( !!dictName ) { + XP_ASSERT( XP_STRLEN((const char*)dictName) > 0 ); + + ctxt->super.name = copyString( mpool, dictName ); + ctxt->super.destructor = palm_dictionary_destroy; + + found = getDictWithName( dl, dictName, &dle ); + + if ( !found ) { + goto errExit; + } + + if ( dle->location == DL_VFS ) { + const XP_UCHAR* str = getResString( globals, STR_DICT_COPY_EXPL ); + WinDrawChars( str, XP_STRLEN(str), 5, 40 ); + + err = VFSImportDatabaseFromFile( dle->u.vfsData.volNum, + (const char*)dle->path, + &cardNo, &dbID ); + if ( err != errNone ) { + goto errExit; + } + } else { + cardNo = dle->u.dmData.cardNo; + dbID = dle->u.dmData.dbID; + } + + ctxt->refCount = 1; + ctxt->dl = dl; + ctxt->dbID = dbID; + ctxt->cardNo = cardNo; + ctxt->location = dle->location; + + ctxt->dbRef = dbRef = DmOpenDatabase( cardNo, dbID, dmModeReadOnly ); + tmpH = DmQueryRecord( dbRef, 0 ); // <- should be a constant + ctxt->headerRecP = headerRecP = (dawg_header*)MemHandleLock( tmpH ); + XP_ASSERT( MemHandleLockCount(tmpH) == 1 ); + +#ifdef NODE_CAN_4 + flags = XP_NTOHS( headerRecP->flags ); + if ( flags == 0x0002 ) { + XP_ASSERT( nodeSize == 3 ); + } else if ( flags == 0x0003 ) { + nodeSize = 4; + } else { + XP_WARNF( "got flags of %d", flags ); + XP_ASSERT(0); + return NULL; + } +#endif + + tmpH = DmQueryRecord( dbRef, headerRecP->charTableRecNum ); + XP_ASSERT( !!tmpH ); + facePtr = (FaceType*)MemHandleLock( tmpH ); + XP_ASSERT( MemHandleLockCount( tmpH ) == 1 ); + ctxt->super.nFaces = nChars = MemPtrSize(facePtr) / sizeof(*facePtr); + ctxt->super.faces16 = + XP_MALLOC( mpool, nChars * sizeof(ctxt->super.faces16[0])); + XP_ASSERT( !!ctxt->super.faces16 ); + for ( i = 0; i < nChars; ++i ) { +#ifdef NODE_CAN_4 + ctxt->super.faces16[i] = READ_UNALIGNED16( &facePtr[i] ); +#else + ctxt->super.faces16[i] = facePtr[i]; +#endif + } + nSpecials = countSpecials( facePtr, nChars ); + MemPtrUnlock( facePtr ); + + tmpH = DmQueryRecord( dbRef, headerRecP->valTableRecNum ); + charPtr = (unsigned char*)MemHandleLock(tmpH); + XP_ASSERT( MemHandleLockCount( tmpH ) == 1 ); + // use 2; ARM thinks sizeof(Xloc_header) is 4. + ctxt->super.langCode = *charPtr; + ctxt->super.countsAndValues = charPtr + 2; + + /* for those dicts with special chars */ + if ( nSpecials > 0 ) { + Xloc_specialEntry* specialStart = (Xloc_specialEntry*) + (ctxt->super.countsAndValues + (ctxt->super.nFaces*2)); + setupSpecials( MPPARM(mpool) ctxt, specialStart, nSpecials ); + } + + if ( headerRecP->firstEdgeRecNum == 0 ) { /* emtpy dict */ + ctxt->super.topEdge = NULL; + ctxt->nRecords = 0; + } else { + MemHandle record; + XP_U16 size; + short index; + XP_U16 nRecords; + + nRecords = DmNumRecords(dbRef) - headerRecP->firstEdgeRecNum; + + /* alloacate now that we know the size. */ + ctxt = XP_MALLOC( mpool, + sizeof(*ctxt) + (nRecords * sizeof(DictStart))); + XP_MEMCPY( ctxt, &tDictBuf, sizeof(tDictBuf) ); + ctxt->nRecords = nRecords; +#ifdef NODE_CAN_4 + ctxt->super.nodeSize = (XP_U8)nodeSize; + ctxt->super.is_4_byte = nodeSize == 4; +#endif + + totalSize = 0; + for ( index = 0; index < nRecords; ++index ) { + record = DmQueryRecord( dbRef, index + + headerRecP->firstEdgeRecNum); + totalSize += MemHandleSize( record ); + } + + /* NOTE: need to use more than one feature to support having + multiple dicts open at once. */ + err = ~errNone; /* so test below will pass if nRecords == 1 */ +#ifdef XWFEATURE_COMBINEDAWG + if ( nRecords > 1 ) { + void* dawgBase; + err = FtrPtrNew( APPID, DAWG_STORE_FEATURE, totalSize, + &dawgBase ); + if ( err == errNone ) { + for ( index = 0, offset = 0; index < nRecords; ++index ) { + record = DmQueryRecord( dbRef, index + + headerRecP->firstEdgeRecNum ); + size = MemHandleSize( record ); + err = DmWrite( dawgBase, offset, + MemHandleLock( record ), size ); + XP_ASSERT( err == errNone ); + MemHandleUnlock( record ); + offset += size; +#ifdef DEBUG +#ifdef NODE_CAN_4 + ctxt->super.numEdges += size / nodeSize; +#else + ctxt->super.numEdges += size / 3; +#endif +#endif + } + + ctxt->super.base = dawgBase; + ctxt->super.topEdge = dawgBase; + } else { + XP_LOGF( "unable to use Ftr for dict; err=%d", err ); + } + } +#endif + + if ( err != errNone ) { + offset = 0; + for ( index = 0; index < nRecords; ++index ) { + record = DmQueryRecord( dbRef, index + headerRecP->firstEdgeRecNum ); + size = MemHandleSize( record ); + + ctxt->dictStarts[index].indexStart = offset; + + /* cast to short to avoid libc call */ + XP_ASSERT( size < 0xFFFF ); +#ifdef NODE_CAN_4 + XP_ASSERT( 0 == (size % nodeSize) ); + offset += size / nodeSize; +#else + XP_ASSERT( ((unsigned short)size % 3 )==0); + offset += ((unsigned short)size) / 3; +#endif + ctxt->dictStarts[index].array = + (array_edge*)MemHandleLock( record ); + XP_ASSERT( MemHandleLockCount(record) == 1 ); + } + + XP_ASSERT( index == ctxt->nRecords ); + ctxt->dictStarts[index].indexStart = 0xFFFFFFFFL; + + ctxt->super.topEdge = ctxt->dictStarts[0].array; +#ifdef DEBUG + ctxt->super.numEdges = offset; +#endif + ctxt->super.func_edge_for_index + = palm_dict_edge_for_index_multi; + } + } + + setBlankTile( (DictionaryCtxt*)ctxt ); + + cacheDictForName( dl, dictName, (DictionaryCtxt*)ctxt ); + } else { + ctxt->refCount = NO_REFCOUNT; + } + + return (DictionaryCtxt*)ctxt; + + errExit: + if ( !!ctxt ) { + XP_ASSERT( ctxt == &tDictBuf ); + } + return NULL; +} /* palm_dictionary_make */ + +static XP_U16 +countSpecials( FaceType* ptr, UInt16 nChars ) +{ + XP_U16 result = 0; + + while ( nChars-- ) { + FaceType face; +#ifdef NODE_CAN_4 + XP_ASSERT( sizeof(face) == 2 ); + face = READ_UNALIGNED16( ptr ); + ++ptr; +#else + face = *ptr++; +#endif + if ( IS_SPECIAL(face) ) { + ++result; + } + } + return result; +} /* countSpecials */ + +/* We need an array of ptrs to bitmaps plus an array of ptrs to the text + * equivalents. + */ +static void +setupSpecials( MPFORMAL PalmDictionaryCtxt* ctxt, + Xloc_specialEntry* specialStart, XP_U16 nSpecials ) +{ + XP_U16 i; + char* base; + XP_UCHAR** chars; + SpecialBitmaps* bitmaps; + + base = (char*)specialStart; + chars = XP_MALLOC( mpool, nSpecials * sizeof(*chars) ); + bitmaps = XP_MALLOC( mpool, nSpecials * sizeof(*bitmaps) ); + + XP_MEMSET( bitmaps, 0, nSpecials * sizeof(*bitmaps ) ); + + for ( i = 0; i < nSpecials; ++i ) { + XP_U16 hasLarge, hasSmall; + + chars[i] = specialStart->textVersion; + + /* This may not work! Get rid of NTOHS???? */ + hasLarge = READ_UNALIGNED16( &specialStart->hasLarge ); + if ( hasLarge ) { + bitmaps[i].largeBM = base + hasLarge; + } + hasSmall = READ_UNALIGNED16( &specialStart->hasSmall ); + if ( hasSmall ) { + bitmaps[i].smallBM = base + hasSmall; + } + ++specialStart; + } + + ctxt->super.bitmaps = bitmaps; + ctxt->super.chars = chars; +} /* setupSpecials */ + +static void +palm_dictionary_destroy( DictionaryCtxt* dict ) +{ + PalmDictionaryCtxt* ctxt = (PalmDictionaryCtxt*)dict; + + if ( ctxt->refCount != NO_REFCOUNT && --ctxt->refCount == 0 ) { + short i; + DmOpenRef dbRef; + dawg_header* headerRecP; + + if ( !!ctxt->super.name ) { + /* Remove from dict list */ + removeFromDictCache( ctxt->dl, ctxt->super.name ); + } + + dbRef = ctxt->dbRef; + headerRecP = ctxt->headerRecP; + + XP_ASSERT( !!dbRef ); + + MemPtrUnlock( ctxt->super.countsAndValues - 2 );//sizeof(Xloc_header) ); + + XP_FREE( dict->mpool, ctxt->super.faces16 ); + +#ifdef XWFEATURE_COMBINEDAWG + /* Try first to delete the feature. */ + if ( FtrPtrFree( APPID, DAWG_STORE_FEATURE ) == ftrErrNoSuchFeature ) { +#endif + for ( i = 0; i < ctxt->nRecords; ++i ) { + XP_ASSERT( !!ctxt->dictStarts[i].array ); + MemPtrUnlock( ctxt->dictStarts[i].array ); + } +#ifdef XWFEATURE_COMBINEDAWG + } else { + XP_ASSERT( ctxt->dictStarts[0].array == NULL ); + } +#endif + + MemPtrUnlock( headerRecP ); + + DmCloseDatabase( dbRef ); + + if ( !!ctxt->super.name ) { + XP_FREE( dict->mpool, ctxt->super.name ); + } + if ( !!ctxt->super.bitmaps ) { + XP_FREE( dict->mpool, ctxt->super.bitmaps ); + } + if ( !!ctxt->super.chars ) { + XP_FREE( dict->mpool, ctxt->super.chars ); + } + + /* if we copied the db to memory we should nuke it, since user would + have copied it himself if he wanted it here permanently */ + if ( ctxt->location == DL_VFS ) { + DmDeleteDatabase( ctxt->cardNo, ctxt->dbID ); + } + + XP_FREE( dict->mpool, dict ); + } +} /* palm_dictionary_destroy */ + +#ifdef OVERRIDE_EDGE_FOR_INDEX +static array_edge* +palm_dict_edge_for_index_multi( const DictionaryCtxt* dict, XP_U32 index ) +{ + PalmDictionaryCtxt* ctxt = (PalmDictionaryCtxt*)dict; + array_edge* result; + + XP_ASSERT( index < dict->numEdges ); + + if ( index == 0 ) { + result = NULL; + } else { + DictStart* headerP; + for ( headerP = &ctxt->dictStarts[1]; + index >= headerP->indexStart; + ++headerP ) { + /* do nothing */ + } + --headerP; + index -= headerP->indexStart; + + /* To avoid linking in __mulsi3, do the math without a variable */ + if ( 0 ) { +#ifdef NODE_CAN_4 + } else if ( dict->nodeSize == 4 ) { + index *= 4; /* better to shift (<<= 2) here? */ +# ifdef DEBUG + } else if ( dict->nodeSize != 3 ) { + XP_ASSERT( 0 ); +# endif +#endif + } else { + index *= 3; + } + result = headerP->array + index; + } + + return result; +} /* dict_edge_for_index */ + +#endif diff --git a/xwords4/palm/palmdict.h b/xwords4/palm/palmdict.h new file mode 100644 index 000000000..5a3a81f85 --- /dev/null +++ b/xwords4/palm/palmdict.h @@ -0,0 +1,39 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _PALMDICT_H_ +#define _PALMDICT_H_ + +#include "dictnry.h" +#include "mempool.h" +#include "dictui.h" + +DictionaryCtxt* palm_dictionary_make( MPFORMAL PalmAppGlobals* globals, + const XP_UCHAR* dictName, /* copied */ + PalmDictList* dl ); + +#ifdef NODE_CAN_4 +void offerConvertOldDicts( PalmAppGlobals* globals ); +#else +# define offerConvertOldDicts(g) +#endif + +typedef struct PalmDictionaryCtxt PalmDictionaryCtxt; + +#endif /* _PALMDICT_H_ */ diff --git a/xwords4/palm/palmdraw.c b/xwords4/palm/palmdraw.c new file mode 100644 index 000000000..b09b362b0 --- /dev/null +++ b/xwords4/palm/palmdraw.c @@ -0,0 +1,1566 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; compile-command: "make ARCH=68K_ONLY MEMDEBUG=TRUE";-*- */ +/* + * Copyright 1999 - 2008 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#ifdef XWFEATURE_FIVEWAY +# include +#endif + +#include "draw.h" +#include "dbgutil.h" + +#include "palmmain.h" +#include "xwords4defines.h" +#include "LocalizedStrIncludes.h" + +#define CHARRECT_WIDTH 12 +#define CHARRECT_HEIGHT 14 + +#define FONT_HEIGHT 8 +#define LINE_SPACING 1 + +#define SCORE_SEP ':' +#define SCORE_SEPSTR ":" + +#define TILE_SUBSCRIPT 1 /* draw tile with numbers below letters? */ + +/* Let's try both for a while */ +#define DRAW_FOCUS_FRAME 1 +#ifdef DRAW_FOCUS_FRAME +# define TREAT_AS_CURSOR(d,f) ((((f) & CELL_ISCURSOR) != 0) && !(d)->topFocus ) +# define FOCUS_BORDER_WIDTH 6 +#else +# define TREAT_AS_CURSOR(d,f) (((f) & CELL_ISCURSOR) != 0) +#endif + +static XP_Bool palm_common_draw_drawCell( DrawCtx* p_dctx, const XP_Rect* rect, + const XP_UCHAR* letters, + XP_Bitmap bitmap, Tile tile, + XP_S16 owner, XWBonusType bonus, + HintAtts hintAtts, CellFlags flags ); +static void palm_bnw_draw_score_drawPlayer( DrawCtx* p_dctx, + const XP_Rect* rInner, + const XP_Rect* rOuter, + const DrawScoreInfo* dsi ); +static XP_Bool palm_bnw_draw_trayBegin( DrawCtx* p_dctx, const XP_Rect* rect, + XP_U16 owner, DrawFocusState dfs ); +static void palm_clr_draw_clearRect( DrawCtx* p_dctx, const XP_Rect* rectP ); +static void palm_draw_drawMiniWindow( DrawCtx* p_dctx, const XP_UCHAR* text, + const XP_Rect* rect, void** closureP ); +static void doDrawPlayer( PalmDrawCtx* dctx, const DrawScoreInfo* dsi, + const XP_Rect* rInner ); + +#define HIGHRES_PUSH_LOC( dctx ) \ + { \ + XP_U16 oldVal = 0; \ + if ( (dctx)->doHiRes ) { \ + oldVal = WinSetCoordinateSystem( kCoordinatesNative ); \ + } +#define HIGHRES_POP_LOC(dctx) \ + if ( (dctx)->doHiRes ) { \ + (void)WinSetCoordinateSystem( oldVal ); \ + (dctx)->oldCoord = 0; \ + } \ + } +#define HIGHRES_PUSH_NOPOP( dctx ) \ + if ( (dctx)->doHiRes ) { \ + WinSetCoordinateSystem( kCoordinatesNative ); \ + } +#define HIGHRES_PUSH( dctx ) \ + if ( (dctx)->doHiRes ) { \ + XP_ASSERT( (dctx)->oldCoord == 0 ); \ + (dctx)->oldCoord = WinSetCoordinateSystem( kCoordinatesNative ); \ + } +#define HIGHRES_POP(dctx) \ + if ( (dctx)->doHiRes ) { \ + (void)WinSetCoordinateSystem( (dctx)->oldCoord ); \ + (dctx)->oldCoord = 0; \ + } + +static void +pmEraseRect( /* PalmDrawCtx* dctx, */const XP_Rect* rect ) +{ + WinEraseRectangle( (const RectangleType*)rect, 0 ); +} /* pmEraseRect */ + +static void +insetRect2( XP_Rect* rect, XP_S16 byX, XP_S16 byY ) +{ + XP_U16 i; + rect->left += byX; + rect->top += byY; + for ( i = 0; i < 2; ++i ) { + rect->width -= byX; + rect->height -= byY; + } +} /* insetRect */ + +static void +insetRect( XP_Rect* rect, XP_S16 by ) +{ + insetRect2( rect, by, by ); +} /* insetRect */ + +static void +drawBitmapAt( DrawCtx* XP_UNUSED(p_dctx), Int16 resID, Int16 x, Int16 y ) +{ + MemHandle handle; + handle = DmGetResource( bitmapRsc, resID ); + XP_ASSERT( handle != NULL ); + + if ( handle != NULL ) { + WinDrawBitmap( (BitmapPtr)MemHandleLock(handle), x, y ); + XP_ASSERT( MemHandleLockCount(handle ) == 1 ); + MemHandleUnlock( handle ); + DmReleaseResource( handle ); + } +} /* drawBitmapAt */ + +static void +bitmapInRect( PalmDrawCtx* dctx, Int16 resID, const XP_Rect* rectP ) +{ + XP_U16 left = rectP->left; + XP_U16 top = rectP->top; + if ( dctx->globals->gState.showGrid ) { + ++left; + ++top; + } + drawBitmapAt( (DrawCtx*)dctx, resID, left, top ); +} /* bitmapInRect */ + +# define BMP_WIDTH 16 +# define BMP_HT 16 + +static void +measureFace( PalmDrawCtx* dctx, XP_UCHAR face, PalmFontHtInfo* fhi ) +{ + WinHandle win; + BitmapType* bitmap; + Coord x, y; + Err err; + XP_Bool gotIt; + XP_U16 top = 0; + XP_U16 bottom = 0; + XP_UCHAR ch = (XP_UCHAR)face; + + bitmap = BmpCreate( BMP_WIDTH, BMP_HT, 1, NULL, &err ); + if ( err == errNone ) { + win = WinCreateBitmapWindow( bitmap, &err ); + if ( err == errNone ) { + WinHandle oldWin = WinSetDrawWindow( win ); + + WinSetBackColor( 0 ); /* white */ + (void)WinSetTextColor( 1 ); /* black */ + (void)WinSetDrawMode( winOverlay ); + + WinDrawChars( &ch, 1, 0, 0 ); + + /* Scan from top for top, then from bottom for botton */ + gotIt = XP_FALSE; + for ( y = 0; !gotIt && y < BMP_HT; ++y ) { + for ( x = 0; !gotIt && x < BMP_WIDTH; ++x ) { + IndexedColorType pxl = WinGetPixel( x, y ); + if ( pxl != 0 ) { + top = y; + gotIt = XP_TRUE; + } + } + } + + gotIt = XP_FALSE; + for ( y = BMP_HT - 1; !gotIt && y >= 0; --y ) { + for ( x = 0; !gotIt && x < BMP_WIDTH; ++x ) { + IndexedColorType pxl = WinGetPixel( x, y ); + if ( pxl != 0 ) { + bottom = y; + gotIt = XP_TRUE; + } + } + } + + (void)WinSetDrawWindow( oldWin ); + WinDeleteWindow( win, false ); + + /* There should be a way to avoid this, but HIGHRES_PUSH after + WinSetDrawWindow isn't working... Fix this... */ + if ( dctx->doHiRes ) { + top *= 2; + bottom *= 2; + } + + fhi->topOffset = top; + fhi->height = bottom - top + 1; + + } + BmpDelete( bitmap ); + } +} /* measureFace */ + +static void +checkFontOffsets( PalmDrawCtx* dctx, const DictionaryCtxt* dict ) +{ + XP_LangCode code; + XP_U16 nFaces; + Tile tile; + + code = dict_getLangCode( dict ); + if ( code != dctx->fontLangCode ) { + + if ( !!dctx->fontHtInfo ) { + XP_FREE( dctx->mpool, dctx->fontHtInfo ); + } + + nFaces = dict_numTileFaces( dict ); + dctx->fontHtInfo = XP_MALLOC( dctx->mpool, + nFaces * sizeof(*dctx->fontHtInfo) ); + + for ( tile = 0; tile < nFaces; ++tile ) { + XP_UCHAR face[2]; + if ( 1 == dict_tilesToString( dict, &tile, 1, + face, sizeof(face) ) ) { + measureFace( dctx, face[0], &dctx->fontHtInfo[tile] ); + } + } + + dctx->fontLangCode = code; + } +} + +static XP_Bool +palm_common_draw_boardBegin( DrawCtx* p_dctx, const XP_Rect* rect, + DrawFocusState dfs ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + PalmAppGlobals* globals = dctx->globals; + if ( !globals->gState.showGrid ) { + WinDrawRectangleFrame(rectangleFrame, (RectangleType*)rect); + } + +#ifdef DRAW_FOCUS_FRAME + dctx->topFocus = dfs == DFS_TOP; +#endif + return XP_TRUE; +} /* palm_common_draw_boardBegin */ + +#ifdef COLOR_SUPPORT +static XP_Bool +palm_clr_draw_boardBegin( DrawCtx* p_dctx, const XP_Rect* rect, + DrawFocusState dfs ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + + WinPushDrawState(); + + WinSetForeColor( dctx->drawingPrefs->drawColors[COLOR_BLACK] ); + WinSetTextColor( dctx->drawingPrefs->drawColors[COLOR_BLACK] ); + WinSetBackColor( dctx->drawingPrefs->drawColors[COLOR_WHITE] ); + + HIGHRES_PUSH_NOPOP(dctx); + + palm_common_draw_boardBegin( p_dctx, rect, dfs ); + + return XP_TRUE; +} /* palm_clr_draw_boardBegin */ + +static void +palm_draw_objFinished( DrawCtx* p_dctx, BoardObjectType typ, + const XP_Rect* rect, DrawFocusState dfs ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; +#if defined XWFEATURE_FIVEWAY && defined DRAW_FOCUS_FRAME + if ( (dfs == DFS_TOP) && (typ == OBJ_BOARD) ) { + XP_Rect r; + XP_U16 i; + IndexedColorType oldColor; + + oldColor + = WinSetForeColor( dctx->drawingPrefs->drawColors[COLOR_CURSOR] ); + + r.left = rect->left + 1; + r.top = rect->top + 1; + r.width = rect->width - 1; + r.height = rect->height - 1; + + for ( i = dctx->doHiRes?FOCUS_BORDER_WIDTH:FOCUS_BORDER_WIDTH/2; + i > 0; --i ) { + insetRect( &r, 1 ); + WinDrawRectangleFrame(rectangleFrame, (RectangleType*)&r ); + } + (void)WinSetForeColor( oldColor ); + } +#endif + + if ( typ == OBJ_BOARD ) { + WinPopDrawState(); + } else if ( typ == OBJ_TRAY ) { + WinSetClip( &dctx->oldTrayClip ); + WinPopDrawState(); + } else if ( typ == OBJ_SCORE ) { + WinSetClip( &dctx->oldScoreClip ); + HIGHRES_POP(dctx); + } +} /* palm_draw_objFinished */ + +static XP_Bool +palm_clr_draw_drawCell( DrawCtx* p_dctx, const XP_Rect* rect, + const XP_UCHAR* letters, XP_Bitmap bitmap, + Tile tile, XP_S16 owner, XWBonusType bonus, + HintAtts hintAtts, CellFlags flags ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + IndexedColorType color; + XP_U16 index; + XP_Bool isCursor = TREAT_AS_CURSOR( dctx, flags ); + XP_Bool isPending = ((flags & CELL_HIGHLIGHT) != 0); + XP_Bool dragSrc = ((flags & CELL_DRAGSRC) != 0); + + if ( isCursor ) { + index = COLOR_CURSOR; + } else if ( isPending && !dragSrc ) { + /* don't color background if will invert */ + index = COLOR_WHITE; + } else if ( (!!bitmap || !!letters) && !dragSrc ) { + index = COLOR_TILE; + } else if ( bonus == BONUS_NONE ) { + index = COLOR_EMPTY; + } else { + index = COLOR_DBL_LTTR + bonus - 1; + } + color = dctx->drawingPrefs->drawColors[index]; + WinSetBackColor( color ); + + if ( !!letters ) { + if ( (owner >= 0) && !isPending ) { + index = COLOR_PLAYER1 + owner; + } else { + index = COLOR_BLACK; + } + + color = dctx->drawingPrefs->drawColors[index]; + WinSetTextColor( color ); + } + + return palm_common_draw_drawCell( p_dctx, rect, letters, bitmap, + tile, owner, bonus, hintAtts, flags ); +} /* palm_clr_draw_drawCell */ + +static void +palm_clr_draw_score_drawPlayer( DrawCtx* p_dctx, const XP_Rect* rInner, + const XP_Rect* rOuter, + const DrawScoreInfo* dsi ) +{ + XP_U16 playerNum = dsi->playerNum; + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + XP_U16 txtIndex; + IndexedColorType newColor; + + WinPushDrawState(); + + if ( dsi->flags && CELL_ISCURSOR ) { + WinSetBackColor( dctx->drawingPrefs->drawColors[COLOR_CURSOR] ); + pmEraseRect( rOuter ); + } + + if ( dsi->selected ) { + XP_Rect r = *rInner; + insetRect2( &r, (r.width - rOuter->width) >> 2, + (r.height - rOuter->height) >> 2 ); + WinSetBackColor( dctx->drawingPrefs->drawColors[COLOR_BLACK] ); + pmEraseRect( &r ); + txtIndex = COLOR_WHITE; + } else { + txtIndex = COLOR_PLAYER1+playerNum; + } + + newColor = dctx->drawingPrefs->drawColors[txtIndex]; + (void)WinSetTextColor( newColor ); + (void)WinSetForeColor( newColor ); + + doDrawPlayer( dctx, dsi, rInner ); + + WinPopDrawState(); +} /* palm_clr_draw_score_drawPlayer */ +#endif /* #ifdef COLOR_SUPPORT */ + +static void +palmDrawHintBorders( PalmDrawCtx* dctx, const XP_Rect* rect, + HintAtts hintAtts ) +{ + if ( 0 != (HINT_BORDER_EDGE & hintAtts) ) { + XP_Rect frame = *rect; + XP_U16 width = dctx->doHiRes ? 4 : 2; + XP_Bool showGrid = dctx->globals->gState.showGrid; + if ( showGrid ) { + ++width; + } + + if ( (hintAtts & HINT_BORDER_LEFT) != 0 ) { + XP_Rect r = frame; + r.width = width; + WinDrawRectangle( (RectangleType*)&r, 0 ); + } + if ( (hintAtts & HINT_BORDER_TOP) != 0 ) { + XP_Rect r = frame; + r.height = width; + WinDrawRectangle( (RectangleType*)&r, 0 ); + } + if ( (hintAtts & HINT_BORDER_RIGHT) != 0 ) { + XP_Rect r = frame; + r.left += r.width - width; + if ( showGrid ) { + ++r.left; + } + r.width = width; + WinDrawRectangle( (RectangleType*)&r, 0 ); + } + if ( (hintAtts & HINT_BORDER_BOTTOM) != 0 ) { + XP_Rect r = frame; + r.top += r.height - width; + if ( showGrid ) { + ++r.top; + } + r.height = width; + WinDrawRectangle( (RectangleType*)&r, 0 ); + } + } +} /* palmDrawHintBorders */ + +static XP_Bool +palm_common_draw_drawCell( DrawCtx* p_dctx, const XP_Rect* rect, + const XP_UCHAR* letters, XP_Bitmap bitmap, + Tile tile, XP_S16 owner, XWBonusType bonus, + HintAtts hintAtts, CellFlags flags ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + GraphicsAbility able = dctx->able; + XP_Rect localR = *rect; + XP_U16 len; + RectangleType saveClip, intersectR; + PalmAppGlobals* globals = dctx->globals; + Boolean showGrid = globals->gState.showGrid; + Boolean showBonus = bonus != BONUS_NONE; + Boolean complete; + XP_Bool showEmpty = 0 != (flags & (CELL_ISEMPTY|CELL_DRAGSRC)); + + if ( showGrid ) { + ++localR.width; + ++localR.height; + } + + WinGetClip( &saveClip ); + + RctGetIntersection( &saveClip, (RectangleType*)&localR, &intersectR ); + + /* If there's no rect left inside the clip rgn, exit. But if the rect's + only partial go ahead and draw, but still return false indicating that + we'd like to be allowed to draw again. This is necessary when a cell + needs to be redrawn for two reasons, e.g. because its bottom half + overlaps a form that's gone away (and that supplied the clip region) + and because its top is covered by the trading miniwindow that's also + going away. Under no circumstances draw outside the clip region or + risk overdrawing menus, other forms, etc. */ + + if ( intersectR.extent.x == 0 || intersectR.extent.y == 0 ) { + return XP_FALSE; + } else if ( intersectR.extent.x < localR.width || + intersectR.extent.y < localR.height ) { + complete = XP_FALSE; + } else { + complete = XP_TRUE; + } + WinSetClip( (RectangleType*)&intersectR ); + + if ( showGrid ) { + insetRect( &localR, 1 ); + } + + pmEraseRect( &localR ); + + if ( showEmpty ) { + /* do nothing */ + } else if ( !!letters ) { + len = XP_STRLEN( (const char*)letters ); + XP_ASSERT( len > 0 ); + if ( len > 0 ) { + XP_S16 strWidth = FntCharsWidth( (const char*)letters, len ); + XP_U16 x, y; + x = localR.left + ((localR.width-strWidth) / 2); + y = localR.top - dctx->fontHtInfo[tile].topOffset; + /* '+ 1' below causes us to round up. Without this "Q" is drawn + with its top higher than other ASCII letters because it's + taller; looks bad. */ + y += (localR.height + 1 - dctx->fontHtInfo[tile].height) / 2; + if ( len == 1 ) { + ++x; + } + + WinDrawChars( (const char*)letters, len, x, y ); + + showBonus = XP_FALSE; + } + } else if ( !!bitmap ) { + XP_Bool doColor = (able == COLOR) && (owner >= 0); + XP_U16 x = localR.left+1; + XP_U16 y = localR.top+1; + /* cheating again; this belongs in a palm_clr method. But the + special bitmaps are rare enough that we shouldn't change the palm + draw state every time. */ + if ( doColor ) { + WinSetForeColor( + dctx->drawingPrefs->drawColors[COLOR_PLAYER1+owner] ); + } + + if ( dctx->doHiRes ) { + ++x; + ++y; + } + + WinDrawBitmap( (BitmapPtr)bitmap, x, y ); + if ( doColor ) { + WinSetForeColor( dctx->drawingPrefs->drawColors[COLOR_BLACK] ); + } + showBonus = doColor; /* skip bonus in B&W case; can't draw both! */ + } + + if ( ((flags & CELL_ISSTAR) != 0) && showEmpty ) { + bitmapInRect( dctx, STAR_BMP_RES_ID, rect ); + } else if ( showBonus && (able == ONEBIT) ) { + /* this is my one refusal to totally factor bandw and color + code */ + WinSetPattern( (const CustomPatternType*) + &dctx->u.bnw.valuePatterns[bonus-1] ); + WinFillRectangle( (RectangleType*)&localR, 0 ); + } else if ( !showBonus && showEmpty && !showGrid ) { + /* should this be in the v-table so I don't have to test each + time? */ + RectangleType r; + r.topLeft.x = localR.left + ((PALM_BOARD_SCALE-1)/2); + r.topLeft.y = localR.top + ((PALM_BOARD_SCALE-1)/2); + + if ( dctx->doHiRes ) { + r.topLeft.x += PALM_BOARD_SCALE/2; + r.topLeft.y += PALM_BOARD_SCALE/2; + } + + if ( globals->romVersion >= 35 ) { + WinDrawPixel( r.topLeft.x, r.topLeft.y ); + } else { + r.extent.x = r.extent.y = 1; + WinDrawRectangle( &r, 0 ); + } + } + + if ( !showEmpty && (flags & CELL_HIGHLIGHT) != 0 ) { + if ( !TREAT_AS_CURSOR( dctx, flags ) ) { + XP_ASSERT( !!bitmap || + (!!letters && XP_STRLEN((const char*)letters)>0)); + WinInvertRectangle( (RectangleType*)&localR, 0 ); + } + } + + if ( showGrid ) { + WinDrawRectangleFrame(rectangleFrame, (RectangleType*)&localR); + } + + if ( ((flags & CELL_ISBLANK) != 0) && !showEmpty ) { + WinEraseRectangleFrame( roundFrame, (RectangleType*)&localR ); + } + + palmDrawHintBorders( dctx, rect, hintAtts ); + + WinSetClip( &saveClip ); + return complete; +} /* palm_common_draw_drawCell */ + +void +palm_draw_destroyCtxt( DrawCtx* p_dctx ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + + XP_FREE( dctx->mpool, p_dctx->vtable ); + if ( !!dctx->fontHtInfo ) { + XP_FREE( dctx->mpool, dctx->fontHtInfo ); + } + XP_FREE( dctx->mpool, dctx ); +} /* palm_draw_destroyCtxt */ + + +static void +palm_draw_dictChanged( DrawCtx* p_dctx, const DictionaryCtxt* dict ) +{ + checkFontOffsets( (PalmDrawCtx*)p_dctx, dict ); +} + +static void +palm_draw_invertCell( DrawCtx* p_dctx, const XP_Rect* rect ) +{ + XP_Rect localR = *rect; + /* insetRect( &localR, 3 ); */ + localR.top += 3; + localR.left += 3; + localR.width -= 5; + localR.height -= 5; + + HIGHRES_PUSH_LOC( (PalmDrawCtx*)p_dctx ); + WinInvertRectangle( (RectangleType*)&localR, 0 ); + HIGHRES_POP_LOC( (PalmDrawCtx*)p_dctx ); +} /* palm_draw_invertCell */ + +static XP_Bool +palm_clr_draw_trayBegin( DrawCtx* p_dctx, const XP_Rect* rect, + XP_U16 owner, DrawFocusState dfs ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + + dctx->trayOwner = owner; + + WinPushDrawState(); + WinSetBackColor( dctx->drawingPrefs->drawColors[COLOR_TILE] ); + WinSetTextColor( dctx->drawingPrefs->drawColors[COLOR_PLAYER1+owner] ); + WinSetForeColor( dctx->drawingPrefs->drawColors[COLOR_PLAYER1+owner] ); + + HIGHRES_PUSH_NOPOP(dctx); + + palm_bnw_draw_trayBegin( p_dctx, rect, owner, dfs ); + return XP_TRUE; +} /* palm_clr_draw_trayBegin */ + +static XP_Bool +palm_bnw_draw_trayBegin( DrawCtx* p_dctx, const XP_Rect* rect, + XP_U16 XP_UNUSED(owner), + DrawFocusState dfs ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + + WinGetClip( &dctx->oldTrayClip ); + WinSetClip( (RectangleType*)rect ); + dctx->topFocus = dfs == DFS_TOP; + return XP_TRUE; +} /* palm_bnw_draw_trayBegin */ + +static void +smallBoldStringAt( const char* str, XP_U16 len, XP_S16 x, XP_U16 y ) +{ + UInt32 oldMode = WinSetScalingMode( kTextScalingOff ); + FontID curFont = FntGetFont(); + FntSetFont( boldFont ); + + /* negative x means position backwards from it */ + if ( x < 0 ) { + x = (-x) - FntCharsWidth( str, len ); + } + + WinDrawChars( str, len, x, y ); + + FntSetFont( curFont ); + WinSetScalingMode( oldMode ); +} /* smallBoldStringAt */ + +static void +palm_draw_drawTile( DrawCtx* p_dctx, const XP_Rect* rect, + const XP_UCHAR* letters, XP_Bitmap bitmap, + XP_S16 val, CellFlags flags ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + char valBuf[3]; + XP_Rect localR = *rect; + XP_U16 len, width; + XP_U16 doubler = 1; + const XP_Bool cursor = (flags & CELL_ISCURSOR) != 0; + XP_Bool empty = (flags & CELL_ISEMPTY) != 0; + + WinPushDrawState(); + + if ( dctx->doHiRes ) { + doubler = 2; + } + + draw_clearRect( p_dctx, &localR ); + + if ( cursor ) { + (void)WinSetBackColor( dctx->drawingPrefs->drawColors[COLOR_CURSOR] ); + if ( dctx->topFocus ) { + WinEraseRectangle( (const RectangleType*)&localR, 0 ); + } + } + + localR.height -= 3 * doubler; + localR.top += 2 * doubler; + localR.width -= 3 * doubler; + localR.left += 2 * doubler; + + if ( (cursor && !dctx->topFocus) || (!empty && !cursor) ) { + /* this will fill it with the tile background color */ + WinEraseRectangle( (const RectangleType*)&localR, 0 ); + } + + if ( !empty ) { + /* Draw the number before the letter. Some PalmOS version don't + honor the winOverlay flag and erase. Better to erase the value + than the letter. */ + if ( val >= 0 ) { + (void)StrPrintF( valBuf, "%d", val ); + len = XP_STRLEN((const char*)valBuf); + + if ( dctx->doHiRes && dctx->oneDotFiveAvail ) { + smallBoldStringAt( valBuf, len, + -(localR.left + localR.width), + localR.top + localR.height - dctx->fntHeight + - 1 ); + } else { + width = FntCharsWidth( valBuf, len ); + WinDrawChars( valBuf, len, localR.left + localR.width - width, + localR.top + localR.height - (10*doubler) ); + } + } + + if ( !!letters ) { + if ( *letters != LETTER_NONE ) { /* blank */ + FontID curFont = FntSetFont( largeFont ); + + HIGHRES_PUSH_LOC( dctx ); + + WinDrawChars( (char*)letters, 1, + localR.left + (1*doubler), + localR.top + (0*doubler) ); + + HIGHRES_POP_LOC(dctx); + + FntSetFont( curFont ); + } + } else if ( !!bitmap ) { + WinDrawBitmap( (BitmapPtr)bitmap, localR.left+(2*doubler), + localR.top+(2*doubler) ); + } + + WinDrawRectangleFrame( rectangleFrame, (RectangleType*)&localR ); + if ( (flags & CELL_HIGHLIGHT) != 0 ) { + insetRect( &localR, 1 ); + WinDrawRectangleFrame(rectangleFrame, (RectangleType*)&localR ); + if ( dctx->doHiRes ) { + insetRect( &localR, 1 ); + WinDrawRectangleFrame(rectangleFrame, + (RectangleType*)&localR ); + } + } + } + WinPopDrawState(); +} /* palm_draw_drawTile */ + +#ifdef POINTER_SUPPORT +static void +palm_draw_drawTileMidDrag( DrawCtx* p_dctx, const XP_Rect* rect, + const XP_UCHAR* letters, XP_Bitmap bitmap, + XP_S16 val, XP_U16 owner, CellFlags flags ) +{ + /* let trayBegin code take care of pushing color env changes. */ + draw_trayBegin( p_dctx, rect, owner, DFS_NONE ); + palm_draw_drawTile( p_dctx, rect, letters, bitmap, val, flags ); + draw_objFinished( p_dctx, OBJ_TRAY, rect, DFS_NONE ); +} +#endif + +static void +palm_draw_drawTileBack( DrawCtx* p_dctx, const XP_Rect* rect, CellFlags flags ) +{ + palm_draw_drawTile( p_dctx, rect, (unsigned char*)"?", (XP_Bitmap)NULL, + -1, flags & CELL_ISCURSOR ); +} /* palm_draw_drawTileBack */ + +static void +palm_draw_drawTrayDivider( DrawCtx* p_dctx, const XP_Rect* rect, + CellFlags flags ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + XP_Rect lRect = *rect; + XP_Bool selected = (flags & CELL_HIGHLIGHT) != 0; + XP_Bool cursor = TREAT_AS_CURSOR(dctx, flags); + + if ( cursor ) { + (void)WinSetBackColor( dctx->drawingPrefs->drawColors[COLOR_CURSOR] ); + } + WinEraseRectangle( (const RectangleType*)&lRect, 0 ); + + ++lRect.left; + --lRect.width; + if ( cursor ) { + insetRect( &lRect, 2 ); + } + + if ( selected ) { + short pattern[] = { 0xFF00, 0xFF00, 0xFF00, 0xFF00 }; + + WinSetPattern( (const CustomPatternType*)&pattern ); + WinFillRectangle( (RectangleType*)&lRect, 0 ); + + } else { + WinDrawRectangle( (RectangleType*)&lRect, 0 ); + } +} /* palm_draw_drawTrayDivider */ + +static void +palm_bnw_draw_clearRect( DrawCtx* XP_UNUSED(p_dctx), const XP_Rect* rectP ) +{ + pmEraseRect( rectP ); +} /* palm_draw_clearRect */ + +static void +palm_clr_draw_clearRect( DrawCtx* p_dctx, const XP_Rect* rectP ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + IndexedColorType oldColor; + + oldColor = WinSetBackColor( dctx->drawingPrefs->drawColors[COLOR_WHITE] ); + pmEraseRect( rectP ); + WinSetBackColor( oldColor ); +} /* palm_clr_draw_clearRect */ + +static void +palm_clr_draw_drawMiniWindow( DrawCtx* p_dctx, const XP_UCHAR* text, + const XP_Rect* rect, void** closureP ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + WinSetBackColor( dctx->drawingPrefs->drawColors[COLOR_WHITE] ); + WinSetTextColor( dctx->drawingPrefs->drawColors[COLOR_BLACK] ); + + palm_draw_drawMiniWindow( p_dctx, text, rect, closureP ); +} /* palm_clr_draw_drawMiniWindow */ + +static void +palm_draw_drawBoardArrow( DrawCtx* p_dctx, const XP_Rect* rectP, + XWBonusType XP_UNUSED(cursorBonus), XP_Bool vertical, + HintAtts hintAtts, CellFlags flags ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + RectangleType oldClip; + + Int16 resID = vertical? DOWN_ARROW_RESID:RIGHT_ARROW_RESID; + + WinGetClip( &oldClip ); + WinSetClip( (RectangleType*)rectP ); + + bitmapInRect( dctx, resID, rectP ); + palmDrawHintBorders( dctx, rectP, hintAtts ); + + WinSetClip( &oldClip ); +} /* palm_draw_drawBoardArrow */ + +#ifdef COLOR_SUPPORT +static void +palm_clr_draw_drawBoardArrow( DrawCtx* p_dctx, const XP_Rect* rectP, + XWBonusType cursorBonus, XP_Bool vertical, + HintAtts hintAtts, CellFlags flags ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + XP_U16 index; + + if ( TREAT_AS_CURSOR( dctx, flags ) ) { + index = COLOR_CURSOR; + } else if ( cursorBonus == BONUS_NONE ) { + index = COLOR_EMPTY; + } else { + index = COLOR_DBL_LTTR + cursorBonus - 1; + } + + WinSetBackColor( dctx->drawingPrefs->drawColors[index] ); + palm_draw_drawBoardArrow( p_dctx, rectP, cursorBonus, vertical, + hintAtts, flags ); +} /* palm_clr_draw_drawBoardArrow */ +#endif + +static void +palm_draw_scoreBegin( DrawCtx* p_dctx, const XP_Rect* rect, + XP_U16 XP_UNUSED(numPlayers), + DrawFocusState XP_UNUSED(dfs) ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + + HIGHRES_PUSH( dctx ); + + WinGetClip( &dctx->oldScoreClip ); + WinSetClip( (RectangleType*)rect ); + + pmEraseRect( rect ); +} /* palm_draw_scoreBegin */ + +/* rectContainsRect: Dup of something in board.c. They could share if I were + * willing to link from here out. + */ +static XP_Bool +rectContainsRect( const XP_Rect* rect1, const XP_Rect* rect2 ) +{ + return ( rect1->top <= rect2->top + && rect1->left <= rect2->left + && rect1->top + rect1->height >= rect2->top + rect2->height + && rect1->left + rect1->width >= rect2->left + rect2->width ); +} /* rectContainsRect */ + +static XP_Bool +palm_draw_vertScrollBoard( DrawCtx* XP_UNUSED(p_dctx), XP_Rect* rect, + XP_S16 dist, DrawFocusState dfs ) +{ + RectangleType clip; + XP_Bool canDoIt; + + /* if the clip rect doesn't contain the scroll rect we can't do anything + right now: WinScrollRectangle won't do its job. */ + WinGetClip( &clip ); + canDoIt = rectContainsRect( (XP_Rect*)&clip, rect ); + + if ( canDoIt ) { + RectangleType vacated; + WinDirectionType dir; + + if ( dist >= 0 ) { + dir = winUp; + } else { + dir = winDown; + dist = -dist; + } + +#ifdef PERIMETER_FOCUS + if ( dfs == DFS_TOP ) { + rect->height -= FOCUS_BORDER_WIDTH; + if ( dir == winDown ) { + rect->top += FOCUS_BORDER_WIDTH; + } + } +#endif + + WinScrollRectangle( (RectangleType*)rect, dir, dist, &vacated ); + *rect = *(XP_Rect*)&vacated; + } + return canDoIt; +} /* palm_draw_vertScrollBoard */ + +/* Given some text, determine its bounds and draw it if requested, else + * return the bounds. If the width of the string exceeds that of the rect in + * which it can be fit, split it at ':'. + */ +static void +palmMeasureDrawText( PalmDrawCtx* dctx, XP_Rect* bounds, XP_UCHAR* txt, + XP_Bool vertical, XP_Bool isTurn, XP_UCHAR skipChar, + XP_Bool draw ) +{ + XP_U16 len = XP_STRLEN( (const char*)txt ); + XP_U16 widths[2]; + XP_U16 maxWidth, height; + XP_U16 nLines = 1; + XP_U16 secondLen = 0; + XP_UCHAR* second = NULL; + XP_U16 doubler = 1; + + if ( dctx->doHiRes ) { + doubler = 2; + } + + widths[0] = FntCharsWidth( (const char*)txt, len ) + 1; + + if ( widths[0] > bounds->width ) { + XP_UCHAR ch[2]; + ch[0] = skipChar; + ch[1] = '\0'; + + XP_ASSERT( skipChar ); + second = (XP_UCHAR*)StrStr( (const char*)txt, (const char*)ch ); + XP_ASSERT( !!second ); + ++second; /* colon's on the first line */ + secondLen = XP_STRLEN( (const char*)second ); + + len -= secondLen; + + if ( skipChar ) { + --len; + } + + widths[0] = FntCharsWidth( (const char*)txt, len ); + widths[1] = FntCharsWidth( (const char*)second, secondLen ); + maxWidth = XP_MAX( widths[0], widths[1] ); + ++nLines; + } else { + maxWidth = widths[0]; + } + + height = nLines * FONT_HEIGHT + ( LINE_SPACING * (nLines-1) ); + if ( vertical && isTurn ) { + height += 5; /* for the horizontal bars */ + } + height *= doubler; + + XP_ASSERT( height <= bounds->height ); + XP_ASSERT( maxWidth <= bounds->width ); + + if ( draw ) { + XP_U16 x, y; + + /* Center what we'll be drawing by advancing the appropriate + coordinate to eat up half the extra space */ + x = bounds->left + 1;// + (bounds->width - widths[0]) / 2; + y = bounds->top; + if ( vertical && isTurn ) { + y += 1; + } else { + y -= 2; + if ( dctx->doHiRes ) { + --y; /* tweak it up one high-res pixel */ + } + } + + WinDrawChars( (const char*)txt, len, x, y ); + if ( nLines == 2 ) { + XP_ASSERT( vertical ); + y += (FONT_HEIGHT + LINE_SPACING) * doubler; + x = bounds->left + ((bounds->width - widths[1]) / 2); + WinDrawChars( (const char*)second, secondLen, x, y ); + } + + } else { + /* return the measurements */ + bounds->width = maxWidth; + bounds->height = height; + } + +} /* palmMeasureDrawText */ + +static void +palmFormatRemText( PalmDrawCtx* dctx, XP_UCHAR* buf, XP_S16 nTilesLeft ) +{ + const XP_UCHAR* remStr = (*dctx->getResStrFunc)(dctx->globals, + STR_REMTILES); + if ( nTilesLeft < 0 ) { + nTilesLeft = 0; + } + (void)StrPrintF( (char*)buf, (const char*)remStr, nTilesLeft ); +} /* palmFormatRemText */ + +static void +palm_draw_measureRemText( DrawCtx* p_dctx, const XP_Rect* rect, + XP_S16 nTilesLeft, XP_U16* widthP, XP_U16* heightP ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + PalmAppGlobals* globals = dctx->globals; + XP_UCHAR buf[10]; + XP_Rect localRect; + XP_Bool isVertical = !globals->gState.showGrid; + + palmFormatRemText( dctx, buf, nTilesLeft ); + + localRect = *rect; + palmMeasureDrawText( dctx, &localRect, buf, isVertical, XP_FALSE, + ':', XP_FALSE ); + + *widthP = localRect.width; + *heightP = localRect.height; +} /* palm_draw_measureRemText */ + +static void +palm_draw_drawRemText( DrawCtx* p_dctx, const XP_Rect* rInner, + const XP_Rect* rOuter, XP_S16 nTilesLeft, + XP_Bool focussed ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + PalmAppGlobals* globals = dctx->globals; + XP_UCHAR buf[10]; + + XP_Bool isVertical = !globals->gState.showGrid; + + if ( focussed ) { + WinPushDrawState(); + WinSetBackColor( dctx->drawingPrefs->drawColors[COLOR_CURSOR] ); + pmEraseRect( rOuter ); + } + + palmFormatRemText( dctx, buf, nTilesLeft ); + + palmMeasureDrawText( dctx, (XP_Rect*)rInner, buf, isVertical, XP_FALSE, + ':', XP_TRUE ); + + if ( focussed ) { + WinPopDrawState(); + } +} /* palm_draw_drawRemText */ + +/* Measure text that'll be drawn for player. If vertical, it'll often get + * split into two lines, esp after the number of remaining tiles appears. + */ +static void +palmFormatScore( char* buf, const DrawScoreInfo* dsi, XP_Bool vertical ) +{ + char borders[] = {'•', '\0'}; + char remBuf[10]; + char* remPart = remBuf; + + if ( vertical || !dsi->isTurn ) { + borders[0] = '\0'; + } + + if ( dsi->nTilesLeft >= 0 ) { + StrPrintF( remPart, SCORE_SEPSTR "%d", dsi->nTilesLeft ); + } else { + *remPart = '\0'; + } + + (void)StrPrintF( buf, "%s%d%s%s", borders, dsi->totalScore, + remPart, borders ); +} /* palmFormatScore */ + +static void +palm_draw_measureScoreText( DrawCtx* p_dctx, const XP_Rect* rect, + const DrawScoreInfo* dsi, + XP_U16* widthP, XP_U16* heightP ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + PalmAppGlobals* globals = dctx->globals; + char buf[20]; + /* FontID oldFont = 0; */ + XP_Bool vertical = !globals->gState.showGrid; + XP_Rect localRect = *rect; + + /* if ( !vertical && dsi->selected ) { */ + /* oldFont = FntGetFont(); */ + /* FntSetFont( boldFont ); */ + /* } */ + + palmFormatScore( buf, dsi, vertical ); + palmMeasureDrawText( dctx, &localRect, (XP_UCHAR*)buf, dsi->isTurn, + vertical, SCORE_SEP, XP_FALSE ); + + *widthP = localRect.width; + *heightP = localRect.height; + + /* result = widthAndText( buf, score, nTilesInTray, isTurn, */ + /* !globals->gState.showGrid, &ignore, ignoreLines ); */ + + /* if ( !vertical && dsi->selected ) { */ + /* FntSetFont( oldFont ); */ + /* } */ +} /* palm_draw_measureScoreText */ + +static void +doDrawPlayer( PalmDrawCtx* dctx, const DrawScoreInfo* dsi, + const XP_Rect* rInner ) +{ + PalmAppGlobals* globals = dctx->globals; + XP_Bool vertical = !globals->gState.showGrid; + XP_UCHAR scoreBuf[20]; + + palmFormatScore( (char*)scoreBuf, dsi, vertical ); + palmMeasureDrawText( dctx, (XP_Rect*)rInner, (XP_UCHAR*)scoreBuf, vertical, + dsi->isTurn, SCORE_SEP, XP_TRUE ); + + if ( vertical && dsi->isTurn ) { + RectangleType r = *(RectangleType*)rInner; + XP_U16 x, y; + + x = r.topLeft.x; + y = r.topLeft.y + 1; + + WinDrawLine( x, y, x + r.extent.x - 1, y); + y += r.extent.y - 3; + WinDrawLine( x, y, x + r.extent.x - 1, y ); + } +} + +static void +palm_bnw_draw_score_drawPlayer( DrawCtx* p_dctx, const XP_Rect* rInner, + const XP_Rect* rOuter, + const DrawScoreInfo* dsi ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + + doDrawPlayer( dctx, dsi, rInner ); + + if ( dsi->selected ) { + WinInvertRectangle( (RectangleType*)rInner, 0 ); + } +} /* palm_bnw_draw_score_drawPlayer */ + +#define PENDING_DIGITS 3 +static void +palm_draw_score_pendingScore( DrawCtx* p_dctx, const XP_Rect* rect, + XP_S16 score, XP_U16 XP_UNUSED(playerNum), + CellFlags flags ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + char buf[PENDING_DIGITS+1] = "000"; + RectangleType oldClip, newClip; + XP_U16 x = rect->left + 1; + IndexedColorType oclr = 0; + + XP_ASSERT( flags == CELL_NONE || flags == CELL_ISCURSOR ); + + HIGHRES_PUSH_LOC(dctx); + + if ( flags != CELL_NONE ) { + oclr = WinSetBackColor( dctx->drawingPrefs->drawColors[COLOR_CURSOR] ); + } + + WinGetClip( &oldClip ); + RctGetIntersection( &oldClip, (RectangleType*)rect, &newClip ); + if ( newClip.extent.y > 0 ) { + WinSetClip( &newClip ); + pmEraseRect( rect ); + + if ( score >= 0 ) { + XP_UCHAR tbuf[4]; + UInt16 len; + if ( score <= 999 ) { + StrPrintF( (char*)tbuf, "%d", score ); + } else { + StrCopy( (char*)tbuf, "wow" ); /* thanks, Marcus :-) */ + } + len = XP_STRLEN( (const char*)tbuf ); + XP_MEMCPY( &buf[PENDING_DIGITS-len], tbuf, len ); + } else { + StrCopy( buf, "???" ); + } + + /* There's no room for the pts string if we're in highres mode and + WinSetScalingMode isn't available. */ + if ( !dctx->doHiRes || dctx->oneDotFiveAvail ) { + const XP_UCHAR* str = (*dctx->getResStrFunc)( dctx->globals, + STR_PTS ); + + if ( dctx->oneDotFiveAvail ) { + smallBoldStringAt( (const char*)str, XP_STRLEN((const char*)str), + x, rect->top ); + } else { + WinDrawChars( (const char*)str, + XP_STRLEN((const char*)str), x, rect->top ); + } + } + + WinDrawChars( buf, PENDING_DIGITS, x, + rect->top + (rect->height/2) - 1 ); + WinSetClip( &oldClip ); + } + + if ( flags != 0 ) { + (void)WinSetBackColor( oclr ); + } + + HIGHRES_POP_LOC(dctx); +} /* palm_draw_score_pendingScore */ + +static void +palmFormatTimerText( XP_UCHAR* buf, XP_S16 secondsLeft ) +{ + XP_U16 minutes, seconds; + XP_UCHAR secBuf[6]; + + if ( secondsLeft < 0 ) { + *buf++ = '-'; + secondsLeft *= -1; + } + minutes = secondsLeft / 60; + seconds = secondsLeft % 60; + + /* StrPrintF can't do 0-padding; do it manually. Otherwise 5:03 will + come out 5:3 */ + StrPrintF( (char*)secBuf, "0%d", seconds ); + StrPrintF( (char*)buf, "%d:%s", minutes, + secBuf[2] == '\0'? secBuf:&secBuf[1] ); +} /* palmFormatTimerText */ + +static void +palm_draw_drawTimer( DrawCtx* p_dctx, const XP_Rect* rInner, + const XP_Rect* XP_UNUSED(rOuter), + XP_U16 XP_UNUSED(player), XP_S16 secondsLeft ) +{ + /* This is called both from within drawScoreboard and not, meaning that + * sometimes draw_boardBegin() and draw_boardFinished() bracket it and + * sometimes they don't. So it has to do its own HIGHRES_PUSH_LOC stuff. + */ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + XP_UCHAR buf[10]; + XP_Rect localR = *rInner; + RectangleType saveClip; + XP_U16 len, width, y; + + HIGHRES_PUSH_LOC(dctx); + + palmFormatTimerText( buf, secondsLeft ); + len = XP_STRLEN( (const char*)buf ); + + width = FntCharsWidth( (const char*)buf, len ); + + pmEraseRect( &localR ); + + if ( width < localR.width ) { + localR.left += localR.width - width; + localR.width = width; + } + + y = localR.top - 2; + if ( dctx->doHiRes ) { + y -= 1; /* tweak it up one high-res pixel */ + } + + WinGetClip( &saveClip ); + WinSetClip( (RectangleType*)&localR ); + + WinDrawChars( (const char*)buf, len, localR.left, y ); + WinSetClip( &saveClip ); + + HIGHRES_POP_LOC(dctx); +} /* palm_draw_drawTimer */ + +#define MINI_LINE_HT 12 +#define MINI_V_PADDING 6 +#define MINI_H_PADDING 8 + +static const XP_UCHAR* +palm_draw_getMiniWText( DrawCtx* p_dctx, XWMiniTextType textHint ) +{ + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + const XP_UCHAR* str; + XP_U16 strID = 0; /* make compiler happy */ + + switch( textHint ) { + case BONUS_DOUBLE_LETTER: + strID = STR_DOUBLE_LETTER; break; + case BONUS_DOUBLE_WORD: + strID = STR_DOUBLE_WORD; break; + case BONUS_TRIPLE_LETTER: + strID = STR_TRIPLE_LETTER; break; + case BONUS_TRIPLE_WORD: + strID = STR_TRIPLE_WORD; break; + case INTRADE_MW_TEXT: + strID = STR_TRADING_REMINDER; break; + default: + XP_ASSERT( XP_FALSE ); + } + + str = (*dctx->getResStrFunc)( dctx->globals, strID ); + + return str; +} /* palm_draw_getMiniWText */ + +static void +splitString( const XP_UCHAR* str, XP_U16* nBufsP, XP_UCHAR** bufs ) +{ + XP_U16 nBufs = 0; + + for ( ; ; ) { + XP_UCHAR* nextStr = StrStr( str, XP_CR ); + XP_U16 len = nextStr==NULL? XP_STRLEN(str): nextStr - str; + + XP_ASSERT( nextStr != str ); + + XP_MEMCPY( bufs[nBufs], str, len ); + bufs[nBufs][len] = '\0'; + ++nBufs; + + if ( nextStr == NULL ) { + break; + } + str = nextStr + XP_STRLEN(XP_CR); /* skip '\n' */ + } + *nBufsP = nBufs; +} /* splitString */ + +#define VALUE_HINT_RECT_HEIGHT 12 +#define VALUE_HINT_RECT_HEIGHT_HR 24 +static XP_U16 +getMiniLineHt( PalmDrawCtx* dctx ) +{ + if ( dctx->doHiRes ) { + return VALUE_HINT_RECT_HEIGHT_HR; + } else { + return VALUE_HINT_RECT_HEIGHT; + } +} /* getMiniLineHt */ + +static void +palm_draw_measureMiniWText( DrawCtx* p_dctx, const XP_UCHAR* str, + XP_U16* widthP, XP_U16* heightP ) +{ + XP_U16 maxWidth, height; + XP_UCHAR buf1[48]; + XP_UCHAR buf2[48]; + XP_UCHAR* bufs[2] = { buf1, buf2 }; + XP_U16 nBufs, i; + + HIGHRES_PUSH_LOC( (PalmDrawCtx*)p_dctx ); + FntSetFont( stdFont ); + + splitString( str, &nBufs, bufs ); + + for ( height = 0, maxWidth = 0, i = 0; i < nBufs; ++i ) { + XP_U16 oneWidth = 8 + FntCharsWidth( (const char*)bufs[i], + XP_STRLEN(bufs[i]) ); + + maxWidth = XP_MAX( maxWidth, oneWidth ); + height += getMiniLineHt((PalmDrawCtx*)p_dctx); + } + + *widthP = maxWidth; + *heightP = height + 4; + + HIGHRES_POP_LOC( (PalmDrawCtx*)p_dctx ); +} /* palm_draw_measureMiniWText */ + +static void +palm_draw_drawMiniWindow( DrawCtx* p_dctx, const XP_UCHAR* text, + const XP_Rect* rect, void** XP_UNUSED(closureP) ) +{ + XP_Rect localR; + XP_UCHAR buf1[48]; + XP_UCHAR buf2[48]; + XP_UCHAR* bufs[2] = { buf1, buf2 }; + XP_U16 nBufs, i, offset; + PalmDrawCtx* dctx = (PalmDrawCtx*)p_dctx; + + HIGHRES_PUSH_LOC(dctx); + + localR = *rect; + insetRect( &localR, 1 ); + WinEraseRectangle( (RectangleType*)&localR, 0 ); + localR.left++; + localR.top++; + localR.width -= 3; + localR.height -= 3; + WinDrawRectangleFrame( popupFrame, (RectangleType*)&localR ); + + splitString( text, &nBufs, bufs ); + + offset = 0; + for ( i = 0; i < nBufs; ++i ) { + XP_UCHAR* txt = bufs[i]; + XP_U16 len = XP_STRLEN( txt ); + XP_U16 width = FntCharsWidth( txt, len ); + + WinDrawChars( (const char*)txt, len, + localR.left + ((localR.width-width)/2), + localR.top + 1 + offset ); + offset += getMiniLineHt( dctx ); + } + + HIGHRES_POP_LOC( dctx ); +} /* palm_draw_drawMiniWindow */ + +static void +draw_doNothing( DrawCtx* XP_UNUSED(dctx), ... ) +{ +} /* draw_doNothing */ + +DrawCtx* +palm_drawctxt_make( MPFORMAL GraphicsAbility able, + PalmAppGlobals* globals, + GetResStringFunc getRSF, DrawingPrefs* drawprefs ) +{ + PalmDrawCtx* dctx; + XP_U16 i; + XP_U16 cWinWidth, cWinHeight; + + dctx = XP_MALLOC( mpool, sizeof(PalmDrawCtx) ); + XP_MEMSET( dctx, 0, sizeof(PalmDrawCtx) ); + + MPASSIGN(dctx->mpool, mpool); + + dctx->able = able; + dctx->doHiRes = globals->hasHiRes && globals->width == 320; + dctx->globals = globals; + dctx->getResStrFunc = getRSF; + dctx->drawingPrefs = drawprefs; + + dctx->vtable = XP_MALLOC( mpool, + sizeof(*(((PalmDrawCtx*)dctx)->vtable)) ); + for ( i = 0; i < sizeof(*dctx->vtable)/4; ++i ) { + ((void**)(dctx->vtable))[i] = draw_doNothing; + } + + /* To keep the number of entry points this file has to 1, all + functions but the initter called from here must go through a + vtable call. so....*/ + dctx->drawBitmapFunc = drawBitmapAt; + + SET_VTABLE_ENTRY( dctx->vtable, draw_destroyCtxt, palm ); + SET_VTABLE_ENTRY( dctx->vtable, draw_dictChanged, palm ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_invertCell, palm ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTile, palm ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTileBack, palm ); +#ifdef POINTER_SUPPORT + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTileMidDrag, palm ); +#endif + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTrayDivider, palm ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_drawBoardArrow, palm ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_scoreBegin, palm ); + SET_VTABLE_ENTRY( dctx->vtable, draw_vertScrollBoard, palm ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_measureRemText, palm ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawRemText, palm ); + SET_VTABLE_ENTRY( dctx->vtable, draw_measureScoreText, palm ); + SET_VTABLE_ENTRY( dctx->vtable, draw_score_pendingScore, palm ); + SET_VTABLE_ENTRY( dctx->vtable, draw_objFinished, palm ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTimer, palm ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_getMiniWText, palm ); + SET_VTABLE_ENTRY( dctx->vtable, draw_measureMiniWText, palm ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawMiniWindow, palm ); + + if ( able == COLOR ) { +#ifdef COLOR_SUPPORT + SET_VTABLE_ENTRY( dctx->vtable, draw_boardBegin, palm_clr ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawCell, palm_clr ); + SET_VTABLE_ENTRY( dctx->vtable, draw_score_drawPlayer, palm_clr ); + SET_VTABLE_ENTRY( dctx->vtable, draw_trayBegin, palm_clr ); + SET_VTABLE_ENTRY( dctx->vtable, draw_clearRect, palm_clr ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawMiniWindow, palm_clr ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_drawBoardArrow, palm_clr ); +#else + XP_ASSERT(0); +#endif + } else { + SET_VTABLE_ENTRY( dctx->vtable, draw_boardBegin, palm_common ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawCell, palm_common ); + SET_VTABLE_ENTRY( dctx->vtable, draw_score_drawPlayer, palm_bnw ); + SET_VTABLE_ENTRY( dctx->vtable, draw_trayBegin, palm_bnw ); + SET_VTABLE_ENTRY( dctx->vtable, draw_clearRect, palm_bnw ); + } + + cWinWidth = CHARRECT_WIDTH; + cWinHeight = CHARRECT_HEIGHT; + if ( dctx->doHiRes ) { + cWinWidth *= 2; + cWinHeight *= 2; + } + + if ( able == COLOR ) { + } else { + short patBits[] = { 0x8844, 0x2211, 0x8844, 0x2211, + 0xaa55, 0xaa55, 0xaa55, 0xaa55, + 0xCC66, 0x3399, 0xCC66, 0x3399, + 0xCCCC, 0x3333, 0xCCCC, 0x3333 }; + XP_MEMCPY( &dctx->u.bnw.valuePatterns[0], patBits, sizeof(patBits) ); + } + + dctx->fntHeight = FntBaseLine(); + dctx->oneDotFiveAvail = globals->oneDotFiveAvail; + + return (DrawCtx*)dctx; +} /* palm_drawctxt_make */ + diff --git a/xwords4/palm/palmip.c b/xwords4/palm/palmip.c new file mode 100644 index 000000000..cb6bd86c4 --- /dev/null +++ b/xwords4/palm/palmip.c @@ -0,0 +1,380 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2001-2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef XWFEATURE_RELAY + +#include + +#include "palmip.h" +#include "memstream.h" + +#define NETLIB_TIMEOUT (5 * sysTicksPerSecond) +#define NAMELOOKUP_TIMEOUT (60*sysTicksPerSecond) + +void +palm_ip_setup( PalmAppGlobals* globals ) +{ + globals->nlStuff.socket = -1; + globals->nlStuff.netLibRef = 0; /* probably unnecessary */ +} + +static void +close_socket( PalmAppGlobals* globals ) +{ + if ( globals->nlStuff.socket != -1 ) { + Err ignore; + NetLibSocketClose( globals->nlStuff.netLibRef, + globals->nlStuff.socket, 0, &ignore); + globals->nlStuff.socket = -1; + } +} /* close_socket */ + +void +palm_ip_close( PalmAppGlobals* globals ) +{ + if ( globals->nlStuff.netLibRef != 0 ) { + + close_socket( globals ); + + NetLibClose(globals->nlStuff.netLibRef, 0); + + globals->nlStuff.netLibRef = 0; + } +} /* palm_ip_close */ + +static XP_Bool +openNetLibIfNot( PalmAppGlobals* globals ) +{ + Err err; + XP_Bool done = globals->nlStuff.netLibRef != 0; + + if ( !done ) { + UInt16 libRef; + err = SysLibFind( "Net.lib", &libRef); + if ( err == errNone ) { + UInt16 ifErrs; + err = NetLibOpen( libRef, &ifErrs ); + if ( err == errNone ) { + globals->nlStuff.netLibRef = libRef; + done = XP_TRUE; + XP_LOGF( "successful netlib open" ); + } else { + XP_LOGF( "NetLibOpen failed: err=%d; ifErrs=%d", + err, ifErrs ); + } + } + } + + return done; +} /* openNetLibIfNot */ + +static XP_Bool +connectSocket( PalmAppGlobals* globals, const CommsAddrRec* addr ) +{ + XP_Bool success; + Err err; + NetSocketAddrINType socketAddr; + + socketAddr.family = netSocketAddrINET; + socketAddr.port = XP_HTONS( addr->u.ip_relay.port ); + socketAddr.addr = XP_HTONL( addr->u.ip_relay.ipAddr ); + + success = ( 0 == NetLibSocketConnect( globals->nlStuff.netLibRef, + globals->nlStuff.socket, + (NetSocketAddrType*)&socketAddr, + sizeof(socketAddr), NETLIB_TIMEOUT, + &err ) ); + if ( !success ) { + XP_LOGF( "NetLibSocketConnect => %d", err ); + } + return success; +} /* connectSocket */ + +static XP_Bool +openSocketIfNot( PalmAppGlobals* globals, const CommsAddrRec* addr ) +{ + XP_Bool open = globals->nlStuff.socket != -1; + + if ( !open ) { + Err err; + NetSocketRef socket; + + XP_ASSERT( globals->nlStuff.netLibRef != 0 ); + + socket = NetLibSocketOpen( globals->nlStuff.netLibRef, + netSocketAddrINET, + netSocketTypeStream, + 0, /* protocol (ignored) */ + NETLIB_TIMEOUT, &err ); + if ( err == errNone ) { + NetSocketLingerType lt; + + XP_LOGF( "Opened socket %d", socket ); + + /* Just for grins, turn linger off; suggested by + * http://tomfrauen.blogspot.com/2005/01/some-palm-os-network-programming.html + */ + lt.onOff = true; + lt.time = 0; + NetLibSocketOptionSet( globals->nlStuff.netLibRef, socket, + netSocketOptLevelSocket, + netSocketOptSockLinger, <, sizeof(lt), + NETLIB_TIMEOUT, &err ); + + globals->nlStuff.socket = socket; + open = connectSocket( globals, addr ); + if ( !open ) { + close_socket( globals ); + } + + } else { + XP_LOGF( "Failed to open socket: %d", err ); + } + } + return open; +} /* openSocketIfNot */ + +static XP_Bool +resolveAddressIfNot( PalmAppGlobals* globals, CommsAddrRec* addr, + XP_Bool* resolvedP ) +{ + XP_Bool resolved = addr->u.ip_relay.ipAddr != 0 + && !globals->nlStuff.ipAddrInval; + *resolvedP = XP_FALSE; + + if ( !resolved ) { + NetHostInfoBufType niBuf; + NetHostInfoPtr result; + Err err; + + result = NetLibGetHostByName( globals->nlStuff.netLibRef, + addr->u.ip_relay.hostName, + &niBuf, NAMELOOKUP_TIMEOUT, &err ); + if ( result == NULL ) { + XP_LOGF( "NetLibGetHostByName => %d", err ); + } else { + + XP_ASSERT( result->addrLen == sizeof(addr->u.ip_relay.ipAddr) ); + /* Addresses are in host byte order. So just copy. */ + XP_MEMCPY( &addr->u.ip_relay.ipAddr, result->addrListP[0], + sizeof( addr->u.ip_relay.ipAddr ) ); + XP_LOGF( "got address 0x%lx for %s", addr->u.ip_relay.ipAddr, + addr->u.ip_relay.hostName ); + + *resolvedP = resolved = XP_TRUE; + globals->nlStuff.ipAddrInval = XP_FALSE; + + comms_setAddr( globals->game.comms, addr ); + } + } + return resolved; +} /* resolveAddressIfNot */ + +void +ip_addr_change( PalmAppGlobals* globals, const CommsAddrRec* oldAddr, + const CommsAddrRec* newAddr ) +{ + /* If host name changing, close any open socket and set up to inval + the cached ip address. */ + if ( 0 != XP_STRNCMP( oldAddr->u.ip_relay.hostName, + newAddr->u.ip_relay.hostName, + sizeof( oldAddr->u.ip_relay.hostName ) ) ) { + + close_socket( globals ); + globals->nlStuff.ipAddrInval = XP_TRUE; + } + +} /* ip_addr_change */ + +/* Deal with NetLibSend's willingness to send less than the full buffer */ +static XP_Bool +sendLoop( PalmAppGlobals* globals, const XP_U8* buf, XP_U16 len ) +{ + XP_U16 totalSent = 0; + + do { + XP_S16 thisSent; + Err err; + thisSent = NetLibSend( globals->nlStuff.netLibRef, + globals->nlStuff.socket, + (void*)(buf + totalSent), + len - totalSent, 0, /* flags */ + NULL, 0, NETLIB_TIMEOUT, &err ); + + if ( thisSent == 0 ) { + globals->nlStuff.socket = -1; /* mark socket closed */ + return XP_FALSE; + } else if ( thisSent < 0 ) { + XP_LOGF( "NetLibSend => %d", err ); + return XP_FALSE; + } else { + totalSent += thisSent; + if ( totalSent < len ) { + XP_LOGF( "looping in sendLoop: %d of %d sent so far", + totalSent, len ); + } + } + } while ( totalSent < len ); + + XP_LOGF( "sendLoop sent %d bytes", len ); + return XP_TRUE; +} /* sendLoop */ + +XP_S16 +palm_ip_send( const XP_U8* buf, XP_U16 len, const CommsAddrRec* addrp, + PalmAppGlobals* globals ) +{ + CommsAddrRec localRec; + CommsAddrRec* addr = &localRec; + XP_S16 nSent = 0; + XP_Bool resolved = XP_FALSE; + + XP_LOGF( "palm_ip_send: len=%d", len ); + XP_ASSERT( len < MAX_MSG_LEN ); + + if ( !!addrp ) { + XP_MEMCPY( addr, addrp, sizeof(*addr) ); + } else { + comms_getAddr( globals->game.comms, addr ); + } + + if ( openNetLibIfNot( globals ) ) { + if ( resolveAddressIfNot( globals, addr, &resolved ) ) { + if ( resolved ) { + comms_setAddr( globals->game.comms, addr ); + } + + if ( openSocketIfNot( globals, addr ) ) { + XP_U16 netlen; + + /* Send the length */ + netlen = XP_HTONS( len ); + if ( sendLoop( globals, (XP_U8*)&netlen, sizeof(netlen) ) + && sendLoop( globals, buf, len ) ) { + nSent = len; + } + } + } + } + return nSent; +} /* palm_ip_send */ + +/* Deal with NetLibReceive's willingness to return will fewer bytes than + requested. */ +static XP_Bool +recvLoop( PalmAppGlobals* globals, XP_U8* buf, XP_U16 lenSought ) +{ + XP_U32 timeout = TimGetSeconds() + 5; + XP_U16 totalRead = 0; + NetSocketAddrINType fromAddr; + void* fromAddrP; + UInt16 fromLen; + + if ( globals->romVersion >= 50 ) { + fromAddrP = NULL; + fromLen = 0; + } else { + fromAddrP = (void*)&fromAddr; + fromLen = sizeof( fromAddr ); + } + + /* Be sure there's a way to timeout quickly here!!! */ + while ( totalRead < lenSought && TimGetSeconds() < timeout ) { + Err err; + Int16 nRead = NetLibReceive( globals->nlStuff.netLibRef, + globals->nlStuff.socket, + buf, lenSought, 0, /* flags */ + fromAddrP, &fromLen, + NETLIB_TIMEOUT, &err ); + + if ( (nRead < 0) && (err != netErrTimeout) ) { + XP_LOGF( "NetLibReceive => %d", err ); + return XP_FALSE; + } else if ( nRead == 0 ) { + XP_LOGF( "NetLibReceive; socket close" ); + globals->nlStuff.socket = -1; + return XP_FALSE; + } else { + totalRead += nRead; + } + } + + XP_LOGF( "recvLoop got %d bytes", totalRead ); + return totalRead == lenSought; +} /* recvLoop */ + +static XWStreamCtxt* +packetToStream( PalmAppGlobals* globals ) +{ + XP_U8 buf[MAX_MSG_LEN]; + XWStreamCtxt* result = NULL; + XP_U16 netlen; + + if ( recvLoop( globals, (XP_U8*)&netlen, sizeof(netlen) ) ) { + netlen = XP_NTOHS( netlen ); + XP_LOGF( "netlen = %d", netlen ); + if ( recvLoop( globals, buf, netlen ) ) { + + result = mem_stream_make( MEMPOOL globals->vtMgr, + globals, 0, NULL); + stream_open( result ); + stream_putBytes( result, buf, netlen ); + } + } + + return result; +} /* packetToStream */ + +void +checkHandleNetEvents( PalmAppGlobals* globals ) +{ + if ( ipSocketIsOpen( globals ) ) { + NetFDSetType readFDs; + NetFDSetType writeFDs; + NetFDSetType ignoreFDs; + XP_S16 nSockets; + UInt16 width; + Err err; + + netFDZero( &readFDs ); + netFDZero( &writeFDs ); + netFDZero( &ignoreFDs ); + + netFDSet( globals->nlStuff.socket, &readFDs ); + netFDSet( sysFileDescStdIn, &readFDs ); + width = XP_MAX( globals->nlStuff.socket, sysFileDescStdIn ); + + XP_ASSERT( globals->nlStuff.netLibRef != 0 ); + nSockets = NetLibSelect( globals->nlStuff.netLibRef, + width + 1, + &readFDs, &writeFDs, &ignoreFDs, + globals->runningOnPOSE? 0 : NETLIB_TIMEOUT, + &err ); + + if ( nSockets > 0 && netFDIsSet(globals->nlStuff.socket, &readFDs) ) { + + XWStreamCtxt* instream = packetToStream( globals ); + if ( !!instream ) { + checkAndDeliver( globals, NULL, instream, COMMS_CONN_RELAY ); + } + } + } +} /* checkHandleNetEvents */ + +#endif diff --git a/xwords4/palm/palmip.h b/xwords4/palm/palmip.h new file mode 100644 index 000000000..1276bd845 --- /dev/null +++ b/xwords4/palm/palmip.h @@ -0,0 +1,33 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 - 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _PALMIP_H_ +#define _PALMIP_H_ + +#include "palmmain.h" + +void palm_ip_setup( PalmAppGlobals* globals ); +void palm_ip_close( PalmAppGlobals* globals ); +XP_S16 palm_ip_send( const XP_U8* buf, XP_U16 len, const CommsAddrRec* addr, + PalmAppGlobals* globals ); +void ip_addr_change( PalmAppGlobals* globals, const CommsAddrRec* oldAddr, + const CommsAddrRec* newAddr ); +void checkHandleNetEvents( PalmAppGlobals* globals ); + +#endif diff --git a/xwords4/palm/palmir.c b/xwords4/palm/palmir.c new file mode 100644 index 000000000..e82e6c581 --- /dev/null +++ b/xwords4/palm/palmir.c @@ -0,0 +1,101 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef XWFEATURE_IR + +#include + +#include "palmir.h" + +#include "callback.h" +#include "xwords4defines.h" +#include "comms.h" +#include "memstream.h" +#include "palmutil.h" +#include "LocalizedStrIncludes.h" + +/* We're passed an address as we've previously defined it and a buffer + * containing a message to send. Prepend any palm/ir specific headers to the + * message, save the buffer somewhere, and fire up the state machine that + * will eventually get it sent to the address. + * + * Note that the caller will queue the message for possible resend, but + * won't automatically schedule that resend whatever results we return. + * + * NOTE also that simply stuffing the buf ptr into the packet won't work + * if there's any ir-specific packet header I need to prepend to what's + * outgoing. + */ +XP_S16 +palm_ir_send( const XP_U8* buf, XP_U16 len, PalmAppGlobals* globals ) +{ + UInt32 sent = 0; + Err err; + ExgSocketType exgSocket; + XP_MEMSET( &exgSocket, 0, sizeof(exgSocket) ); + exgSocket.description = "Crosswords data"; + exgSocket.length = len; + exgSocket.target = APPID; + + if ( globals->romVersion >= 40 ) { + exgSocket.name = exgBeamPrefix; + } + + err = ExgPut( &exgSocket ); + while ( !err && sent < len ) { + sent += ExgSend( &exgSocket, buf+sent, len-sent, &err ); + XP_ASSERT( sent < 0x7FFF ); + } + err = ExgDisconnect( &exgSocket, err ); + + return err==0? sent : 0; +} /* palm_ir_send */ + +void +palm_ir_receiveMove( PalmAppGlobals* globals, ExgSocketPtr socket ) +{ + UInt32 nBytesReceived = -1; + Err err; + + err = ExgAccept( socket ); + if ( err == 0 ) { + XWStreamCtxt* instream; + + instream = mem_stream_make( MEMPOOL globals->vtMgr, globals, + CHANNEL_NONE, NULL ); + stream_open( instream ); + + for ( ; ; ) { + UInt8 buf[128]; + nBytesReceived = ExgReceive( socket, buf, sizeof(buf), &err ); + if ( nBytesReceived == 0 || err != 0 ) { + break; + } + + stream_putBytes( instream, buf, nBytesReceived ); + } + (void)ExgDisconnect( socket, err ); + + if ( nBytesReceived == 0 ) { /* successful loop exit */ + checkAndDeliver( globals, NULL, instream, COMMS_CONN_IR ); + } + } +} /* palm_ir_receiveMove */ + +#endif /* XWFEATURE_IR */ diff --git a/xwords4/palm/palmir.h b/xwords4/palm/palmir.h new file mode 100644 index 000000000..2a8302eab --- /dev/null +++ b/xwords4/palm/palmir.h @@ -0,0 +1,96 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 1999 - 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _PALMIR_H_ +#define _PALMIR_H_ + +#include "comms.h" +#include "palmmain.h" + +/* IR strategy. We'll attempt to keep the IR link up, but won't bring it up + * in the first place, or fix it once it goes down, until there's something + * to send. + * + * So when there's a packet to send, if the link is up + * (state==IR_STATE_CANDODATA) we just send it. Otherwise we do what's + * needed to get to that state. What state we're actually in (in the + * difficult case where we brought the link up before but the players have + * had their machines separated and the link's died) will, I hope, be + * tracable by the messages passed to each device's callback. + * + * This isn't going to make a game between three or four devices very easy + * to manage, but perhaps that will have to wait until whatever's wrong with + * shutting down and restarting links gets fixed. + */ +enum { /* values for IR_STATE */ + IR_STATE_NONE = 0, /* nothing's up or been done */ + IR_STATE_DISCOVERY_COMPLETE = 1,/* nothing to do til there's a message to + send */ + /* Discovery */ + IR_STATE_NOTHING_TO_DO = 2, /* exists just for testing against */ + IR_STATE_NO_OTHER_FOUND = 3, /* first discovery attempt failed */ + IR_STATE_DO_DISCOVERY = 4, + IR_STATE_REDO_DISCOVERY = 5, + IR_STATE_DISCOVERY_SENT = 6, + IR_STATE_DOLAP = 7, + + /* IR Lap */ + IR_STATE_LAP_SENT = 8, + IR_STATE_LAP_ESTAB = 9,//was IR_STATE_DOLMP, + + /* LMP */ +/* IR_STATE_CONNECTREQ_SENT, */ + IR_STATE_LMPREQ_SENT = 10, /* was IR_STATE_CONNECTREQ_SENT */ + IR_STATE_LMP_ESTAB = 11, //was IR_STATE_SEND_READY, + + /* Send */ + IR_STATE_SEND_DONE = 12, + IR_STATE_CAN_DISCONNECT = 13, + + /* receive (doesn't require even discovery) */ + IR_STATE_CONN_RECD = 14, /* triggered by LEVENT_LAP_CON_IND */ + IR_STATE_LAP_RCV = 15, //was IR_STATE_DOLMP_RCV, + IR_STATE_LMPRCV_REQ_SENT = 16, + IR_STATE_CONN_INCOMMING = 17, /* triggered by LEVENT_LM_CON_IND */ + IR_STATE_MESSAGE_RECD = 18 +}; + +#define ir_work_exists(g) \ + ((g)->ir_state > IR_STATE_NOTHING_TO_DO || (getSendQueueHead(g)!=NULL)) + +MyIrPacket* getSendQueueHead( PalmAppGlobals* globals ); + +void ir_callback_out( IrConnect* con, IrCallBackParms* parms ); + +Boolean ir_do_work( PalmAppGlobals* globals ); +void ir_show_status( PalmAppGlobals* globals ); +void ir_cleanup( PalmAppGlobals* globals ); + +void receiveMove( ExgSocketPtr cmdPBP ); +XP_Bool loadReceivedMove( PalmAppGlobals* globals, MemHandle moveData ); + +void palm_ir_receiveMove( PalmAppGlobals* globals, ExgSocketPtr socket ); + +XP_S16 palm_ir_send( const XP_U8* buf, XP_U16 len, PalmAppGlobals* globals ); + +#ifdef XWFEATURE_STANDALONE_ONLY +# define palm_ir_send (TransportSend)NULL +#endif + +#endif diff --git a/xwords4/palm/palmmain.c b/xwords4/palm/palmmain.c new file mode 100644 index 000000000..b6d77dd78 --- /dev/null +++ b/xwords4/palm/palmmain.c @@ -0,0 +1,4174 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; compile-command: "make ARCH=ARM_ONLY MEMDEBUG=TRUE"; -*- */ +/* + * Copyright 1999 - 2007 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef FEATURE_SILK +# include +#endif +#ifdef XWFEATURE_BLUETOOTH +# include +#endif +#include "comtypes.h" +#include "comms.h" + +#include "xwords4defines.h" +#include "palmmain.h" +#include "newgame.h" +#include "palmdbg.h" +#include "dbgutil.h" +#include "dictui.h" +#include "dictlist.h" +#include "palmutil.h" +#include "palmdict.h" +#include "palmsavg.h" +#include "memstream.h" +#include "strutils.h" +#include "palmir.h" +#include "palmip.h" +#include "palmbt.h" +#include "xwcolors.h" +#include "prefsdlg.h" +#include "connsdlg.h" +#include "gameutil.h" +#include "dictui.h" +#include "LocalizedStrIncludes.h" + +#ifdef XWFEATURE_FIVEWAY +# include +/* # include */ +#endif + +#include "callback.h" +#include "pace_man.h" /* for crash() macro */ + +#ifdef SUPPORT_SONY_JOGDIAL +#include "SonyChars.h" +#endif + +#define PALM_TIMER_DELAY 25 + +/*-------------------------------- defines and consts-----------------------*/ +/* #define COLORCHANGE_THRESHOLD 300 */ + +/*-------------------------------- prototypes ------------------------------*/ +static XP_Bool startApplication( PalmAppGlobals** globalsP ); +static void eventLoop( PalmAppGlobals* globals ); +static void stopApplication( PalmAppGlobals* globals ); +static Boolean applicationHandleEvent( EventPtr event ); +static Boolean mainViewHandleEvent( EventPtr event ); + +static UInt16 romVersion( void ); +static Boolean handleHintRequest( PalmAppGlobals* globals ); +static XP_Bool timeForTimer( PalmAppGlobals* globals, XWTimerReason* why, + XP_U32* when ); +static XP_S16 palm_send( const XP_U8* buf, XP_U16 len, + const CommsAddrRec* addr, void* closure ); +#ifdef COMMS_HEARTBEAT +static void palm_reset( void* closure ); +#endif +static void palm_send_on_close( XWStreamCtxt* stream, void* closure ); + +/* callbacks */ +static VTableMgr* palm_util_getVTManager( XW_UtilCtxt* uc ); +static void palm_util_userError( XW_UtilCtxt* uc, UtilErrID id ); +static XP_Bool palm_util_userQuery( XW_UtilCtxt* uc, UtilQueryID id, + XWStreamCtxt* stream ); +static XWBonusType palm_util_getSquareBonus( XW_UtilCtxt* uc, + const ModelCtxt* model, + XP_U16 col, XP_U16 row ); +static XP_S16 palm_util_userPickTile( XW_UtilCtxt* uc, const PickInfo* pi, + XP_U16 playerNum, const XP_UCHAR4* texts, + XP_U16 nTiles ); +static XP_Bool palm_util_askPassword( XW_UtilCtxt* uc, const XP_UCHAR* name, + XP_UCHAR* buf, XP_U16* len ); +static void palm_util_trayHiddenChange( XW_UtilCtxt* uc, + XW_TrayVisState newState, + XP_U16 nVisibleRows ); +static void palm_util_yOffsetChange( XW_UtilCtxt* uc, XP_U16 oldOffset, + XP_U16 newOffset ); +static void palm_util_notifyGameOver( XW_UtilCtxt* uc ); +static XP_Bool palm_util_hiliteCell( XW_UtilCtxt* uc, XP_U16 col, + XP_U16 row ); +static XP_Bool palm_util_engineProgressCallback( XW_UtilCtxt* uc ); +static void palm_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why, XP_U16 when, + XWTimerProc proc, void* closure ); +static XP_Bool palm_util_altKeyDown( XW_UtilCtxt* uc ); +static XP_U32 palm_util_getCurSeconds( XW_UtilCtxt* uc ); +static void palm_util_requestTime( XW_UtilCtxt* uc ); +static DictionaryCtxt* palm_util_makeEmptyDict( XW_UtilCtxt* uc ); + +#ifndef XWFEATURE_STANDALONE_ONLY +static XWStreamCtxt* palm_util_makeStreamFromAddr( XW_UtilCtxt* uc, + XP_PlayerAddr channelNo ); +#endif +static const XP_UCHAR* palm_util_getUserString( XW_UtilCtxt* uc, + XP_U16 stringCode ); +static XP_Bool palm_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi, + XP_U16 turn, XP_Bool turnLost ); +static void palm_util_remSelected(XW_UtilCtxt* uc); + +#if defined XWFEATURE_BLUETOOTH || defined XWFEATURE_RELAY +static void palm_util_addrChange( XW_UtilCtxt* uc, const CommsAddrRec* oldAddr, + const CommsAddrRec* newAddr ); +#endif +#ifdef XWFEATURE_BLUETOOTH +static void btEvtHandler( PalmAppGlobals* globals, BtCbEvtInfo* evt ); +#endif + +#ifdef XWFEATURE_SEARCHLIMIT +static XP_Bool palm_util_getTraySearchLimits( XW_UtilCtxt* uc, XP_U16* min, + XP_U16* max ); +#endif +static void userErrorFromStrId( PalmAppGlobals* globals, XP_U16 strID ); +static XP_Bool askFromStream( PalmAppGlobals* globals, XWStreamCtxt* stream, + XP_S16 titleID, Boolean closeAndDestroy ); +static void displayFinalScores( PalmAppGlobals* globals ); +static void updateScrollbar( PalmAppGlobals* globals, Int16 newValue ); +static void askStartNewGame( PalmAppGlobals* globals ); +static void palmSetCtrlsForTray( PalmAppGlobals* globals ); +static void drawFormButtons( PalmAppGlobals* globals ); +static MemHandle findXWPrefsRsrc( PalmAppGlobals* globals, UInt32 resType, + UInt16 resID ); +#ifdef SHOW_PROGRESS +static void palm_util_engineStarting( XW_UtilCtxt* uc, XP_U16 nBlanks ); +static void palm_util_engineStopping( XW_UtilCtxt* uc ); +#endif + +static void initAndStartBoard( PalmAppGlobals* globals, XP_Bool newGame ); +#ifdef XWFEATURE_FIVEWAY +static XP_Bool isBoardObject( XP_U16 id ); +#endif + +/*-------------------------------- Globals ---------------------------------*/ +/* NONE!!! */ + +/***************************************************************************** + * + ****************************************************************************/ +#define XW_MOVE_EXG_TYPE "XwMv" +UInt32 +PM2(PilotMain)( UInt16 cmd, MemPtr cmdPBP, UInt16 launchFlags) +{ + PalmAppGlobals* globals; + if ( cmd == sysAppLaunchCmdNormalLaunch ) { + if ( (launchFlags & sysAppLaunchFlagNewGlobals) != 0) { +#ifdef XW_TARGET_PNO + /* SVN_REV isn't a string in ARM. Fix that */ + XP_LOGF( "%s: arch=ARM", __func__ ); +#else + XP_LOGF( "%s: arch=68K, rev=%s", __func__, SVN_REV ); +#endif +#ifdef MEM_DEBUG + { + char date[MAX_GAMENAME_LENGTH]; + makeDefaultGameName( date ); + XP_LOGF( "date: %s", date ); + } +#endif + if ( startApplication( &globals ) ) { + XP_ASSERT( (launchFlags & sysAppLaunchFlagNewGlobals) != 0 ); + // Initialize the application's global variables and database. + eventLoop( globals ); + } + } + stopApplication( globals ); + +#ifdef XWFEATURE_IR + } else if ( cmd == sysAppLaunchCmdExgAskUser ) { + if ( (launchFlags & sysAppLaunchFlagSubCall) != 0 ) { + ((ExgAskParamPtr)cmdPBP)->result = exgAskOk; + } + } else if ( cmd == sysAppLaunchCmdSyncNotify ) { + if ( romVersion() >= 30 ) { + ExgRegisterData( APPID, exgRegTypeID, XW_MOVE_EXG_TYPE ); + } + } else if ( cmd == sysAppLaunchCmdExgReceiveData ) { + if ( (launchFlags & sysAppLaunchFlagSubCall) != 0 ) { + globals = getFormRefcon(); + palm_ir_receiveMove( globals, (ExgSocketPtr)cmdPBP ); + } +#endif + } + return 0; +} /* PilotMain */ + +/***************************************************************************** + * + ****************************************************************************/ +static UInt16 +romVersion( void ) +{ + UInt32 dwOSVer; + UInt16 result; + Err err; + + err = FtrGet(sysFtrCreator, sysFtrNumROMVersion, &dwOSVer ); + XP_ASSERT( errNone == err ); + /* should turn 3 and 5 into 35 */ + result = (sysGetROMVerMajor(dwOSVer)*10) + sysGetROMVerMinor(dwOSVer); + + return result; +} /* romVersion */ + +#ifdef COLOR_SUPPORT +/***************************************************************************** + * + ****************************************************************************/ +static UInt32 +cur_screen_depth( void ) +{ + UInt32 curDepth; + + XP_ASSERT( romVersion() >= 30 ); /* */ + + WinScreenMode( winScreenModeGet, 0, 0, &curDepth, 0 ); + return curDepth; +} /* cur_screen_depth */ +#endif + +static void +getSizes( PalmAppGlobals* globals ) +{ + XP_U16 width, height; + width = 160; + height = 160; + + if ( globals->hasHiRes ) { + XP_U32 tmp; + + if ( WinScreenGetAttribute( winScreenWidth, &tmp ) == errNone ) { + width = tmp; + } + if ( WinScreenGetAttribute( winScreenHeight, &tmp ) == errNone ) { + height = tmp; + } + } + + if ( width == 320 ) { + FormPtr form = FrmGetActiveForm(); + WinGetDisplayExtent( &width, &height ); + + if ( !!form ) { + RectangleType r; + r.topLeft.x = 0; + r.topLeft.y = 0; + r.extent.x = width; + r.extent.y = height; + + WinSetBounds( FrmGetWindowHandle(FrmGetActiveForm()), &r ); + } + + width *= 2; + height *= 2; + globals->useHiRes = width >= 320 && height >= 320; + } + + globals->width = width; + globals->height = height; +} /* getSizes */ + +/* The resources place the tray-related buttons for the high-res case. If + * the device is going to want them in the higher low-res position, move them + * here. And resize 'em too. + */ +static void +locateTrayButtons( PalmAppGlobals* globals ) +{ + if ( !globals->useHiRes ) { + /* we need to put the buttons into the old position and set their + sizes for the larger tray. */ + XP_U16 buttonInfoTriplets[] = { XW_MAIN_HIDE_BUTTON_ID, + TRAY_BUTTONS_Y_LR, + + XW_MAIN_JUGGLE_BUTTON_ID, + TRAY_BUTTONS_Y_LR, + + XW_MAIN_TRADE_BUTTON_ID, + TRAY_BUTTONS_Y_LR + + TRAY_BUTTON_HEIGHT_LR, + + XW_MAIN_DONE_BUTTON_ID, + TRAY_BUTTONS_Y_LR + + TRAY_BUTTON_HEIGHT_LR + }; + XP_U16* ptr; + XP_U16 i; + + for ( i = 0, ptr = buttonInfoTriplets; i < 4; ++i, ptr += 2 ) { + RectangleType rect; + getObjectBounds( ptr[0], &rect ); + rect.topLeft.y = ptr[1]; + rect.extent.y = TRAY_BUTTON_HEIGHT_LR; + setObjectBounds( ptr[0], &rect ); + } + } +} /* locateTrayButtons */ + +static XP_Bool +positionBoard( PalmAppGlobals* globals ) +{ + XP_U16 bWidth = globals->width; + XP_Bool erase = XP_FALSE; + XP_Bool isLefty = globals->isLefty; + XP_U16 nCols, leftEdge; + XP_U16 scale = PALM_BOARD_SCALE; + XP_U16 scaleH, scaleV; + XP_U16 boardHeight, trayTop, trayScaleV; + XP_U16 boardTop, scoreTop, scoreLeft, scoreWidth, scoreHeight; + XP_U16 timerWidth, timerLeft; + XP_U16 freeSpaceH; + XP_Bool showGrid = globals->gState.showGrid; + XP_U16 doubler = globals->useHiRes? 2 : 1; +#ifdef SHOW_PROGRESS + RectangleType bounds; +#endif + + XP_ASSERT( !!globals->game.model ); + nCols = model_numCols( globals->game.model ); + XP_ASSERT( nCols <= PALM_MAX_ROWS ); + + /* With the screen having variable width and height, we do away with + * constants and calculate everything on the fly. Horizontally, the + * screen consists of the board and scrollbar/buttons. Vertically, it's + * the scoreboard, the board, and the tray. If the board is square the + * tray must overlap the board -- but not if the smallest font we can fit + * in a cell allows the squares to squeeze down! + * + * Be careful with squeezing cells! Custom chars won't allow it. So + * cell size needs to stay constant... For now we only avoid overlap + * cute when the screen is taller than it is wide. + */ + + /* since we only want the lines between cells one pixel wide, we can + increase scale more than 2x when doubling. */ + if ( !showGrid ) { + --scale; + } + scale = scale * doubler; + scaleV = scaleH = scale; + if ( globals->useHiRes ) { + scaleV -= 2; + } + + freeSpaceH = ((PALM_MAX_COLS-nCols)/2) * scaleH; + if ( isLefty ) { + leftEdge = bWidth - (nCols * scaleH) - freeSpaceH - 1; + } else { + leftEdge = PALM_BOARD_LEFT_RH + freeSpaceH; + } + + /* position the timer. There are really four cases: width depends on + whether the grid's visible, and left edge depends on isLefty _and_ + width in the non-lefty case. */ + + if ( showGrid ) { + timerWidth = FntCharsWidth( "-00:00", 6 ); /* the ideal */ + } else { + timerWidth = PALM_GRIDLESS_SCORE_WIDTH; + } + timerWidth *= doubler; + + if ( isLefty && !showGrid ) { + timerLeft = 0; + } else { + timerLeft = bWidth - timerWidth; + } + board_setTimerLoc( globals->game.board, timerLeft, PALM_TIMER_TOP, + timerWidth, PALM_TIMER_HEIGHT * doubler ); + + if ( showGrid ) { + boardTop = PALM_BOARD_TOP; + scoreLeft = PALM_SCORE_LEFT; + scoreTop = PALM_SCORE_TOP; + scoreWidth = (bWidth/doubler) - PALM_SCORE_LEFT - (timerWidth/doubler); + scoreHeight = PALM_SCORE_HEIGHT; + } else { + boardTop = PALM_GRIDLESS_BOARD_TOP; + scoreLeft = isLefty? 0: PALM_GRIDLESS_SCORE_LEFT; + scoreTop = PALM_GRIDLESS_SCORE_TOP; + scoreWidth = PALM_GRIDLESS_SCORE_WIDTH; + scoreHeight = PALM_TRAY_TOP - PALM_GRIDLESS_SCORE_TOP - 2; + + if ( !isLefty ) { + leftEdge += doubler; /* for the frame */ + } + } + + boardTop *= doubler; + scoreLeft *= doubler; + scoreTop *= doubler; + scoreWidth *= doubler; + scoreHeight *= doubler; + + board_setPos( globals->game.board, leftEdge, + boardTop, isLefty ); + board_setScale( globals->game.board, scaleH, scaleV ); + + board_setScoreboardLoc( globals->game.board, scoreLeft, scoreTop, + scoreWidth, scoreHeight, showGrid ); + + board_setShowColors( globals->game.board, globals->gState.showColors ); + board_setYOffset( globals->game.board, 0 ); + + /* figure location for the tray. If possible, make it smaller than the + ideal to avoid using a scrollbar. Also, note at this point whether a + scrollbar will be required. */ + globals->needsScrollbar = false; /* default */ + boardHeight = scaleV * nCols; + + if ( globals->useHiRes ) { + trayTop = ((160 - TRAY_HEIGHT_HR) * doubler) - 1; + globals->needsScrollbar = false; + } else { + trayTop = 160 - TRAY_HEIGHT_LR; + globals->needsScrollbar = showGrid && (nCols == PALM_MAX_COLS); + } + + trayScaleV = + globals->useHiRes? (TRAY_HEIGHT_HR*doubler) + 1: + TRAY_HEIGHT_LR; + board_setTrayLoc( globals->game.board, + (isLefty? PALM_TRAY_LEFT_LH:PALM_TRAY_LEFT_RH) * doubler, + trayTop, + PALM_TRAY_WIDTH * doubler, trayScaleV, + PALM_DIVIDER_WIDTH ); + + board_prefsChanged( globals->game.board, &globals->gState.cp ); + +#ifdef SHOW_PROGRESS + if ( showGrid ) { + getObjectBounds( XW_MAIN_SCROLLBAR_ID, &bounds ); + + bounds.topLeft.x += doubler; + bounds.extent.x -= (doubler << 1); + } else { + bounds.topLeft.y = (PALM_TIMER_HEIGHT + 2) * doubler; + bounds.topLeft.x = (globals->isLefty? FLIP_BUTTON_WIDTH+3: + PALM_GRIDLESS_SCORE_LEFT+2) * doubler; + + bounds.extent.x = (RECOMMENDED_SBAR_WIDTH + 2) * doubler; + bounds.extent.y = (PALM_GRIDLESS_SCORE_TOP - bounds.topLeft.y - 2) + * doubler; + } + globals->progress.boundsRect = bounds; +#endif + + updateScrollbar( globals, globals->scrollValue ); /* changing visibility? */ + palmSetCtrlsForTray( globals ); + drawFormButtons( globals ); + + return erase; +} /* positionBoard */ + +static XWStreamCtxt* +makeSimpleStream( PalmAppGlobals* globals, MemStreamCloseCallback cb ) +{ + return mem_stream_make( MPPARM(globals->mpool) + globals->vtMgr, + globals, + CHANNEL_NONE, cb ); +} /* makeSimpleStream */ + +static XWStreamCtxt* +gameRecordToStream( PalmAppGlobals* globals, XP_U16 index ) +{ + XWStreamCtxt* recStream = NULL; + LocalID id; + MemHandle handle; + Err err; + + id = DMFINDDATABASE( globals, CARD_0, XW_GAMES_DBNAME ); + if ( id != 0 ) { + UInt16 numRecs; + DmOpenRef dbP; + + dbP = DMOPENDATABASE( globals, CARD_0, id, dmModeReadOnly ); + numRecs = DmNumRecords( dbP ); + + if ( index < numRecs ) { + handle = DmGetRecord( dbP, index ); + + recStream = makeSimpleStream( globals, NULL ); + stream_open( recStream ); + stream_putBytes( recStream, MemHandleLock(handle), + MemHandleSize(handle) ); + MemHandleUnlock(handle); + err = DmReleaseRecord( dbP, index, false ); + XP_ASSERT( err == 0 ); + } + DMCLOSEDATABASE( dbP ); + } + return recStream; +} /* gameRecordToStream */ + +static void +loadGamePrefs( /*PalmAppGlobals* globals, */XWStreamCtxt* stream ) +{ + /* Keep in sync with games saved in prev version, which foolishly saved + hintsNotAllowed separate from the current game's value for the same + thing. When the version changes get rid of this bit. PENDING */ + (void)stream_getBits( stream, 1 ); +} /* loadGamePrefs */ + +static void +saveGamePrefs( /*PalmAppGlobals* globals, */XWStreamCtxt* stream ) +{ + stream_putBits( stream, 1, 0 ); +} /* saveGamePrefs */ + +static void +keySafeCustomAlert( PalmAppGlobals* globals, const XP_UCHAR* buf ) +{ + /* Another gross hack to get around the OS sending a spurious keyDown + event when a dialog is invoked while still processing a keyUp event. + We just pull all events off the queue until the keyDown is found. In + practice that's always the first event, but let's leave logging on for + a while in case this causes problems. */ + while ( globals->handlingKeyEvent ) { + EventType event; + EvtGetEvent( &event, 0 ); + XP_LOGF( "%s: consumed %s", __func__, eType_2str(event.eType) ); + if ( event.eType == keyDownEvent || event.eType == nilEvent ) { + break; + } + } + (void)FrmCustomAlert( XW_ERROR_ALERT_ID, (const char*)buf, " ", " " ); +} + +static void +reportMissingDict( PalmAppGlobals* globals, XP_UCHAR* name ) +{ + /* FrmCustomAlert crashes on some OS versions when there's no form under + it to "return" to. */ + if ( FrmGetActiveForm() != NULL ) { + XP_UCHAR buf[48]; + const XP_UCHAR* str = getResString( globals, STRS_CANNOT_FIND_DICT ); + StrPrintF( buf, str, name ); + keySafeCustomAlert( globals, buf ); + } +} /* reportMissingDict */ + +static XP_Bool +loadCurrentGame( PalmAppGlobals* globals, XP_U16 gIndex, + XWGame* game, CurGameInfo* ginfo ) +{ + XP_Bool hasDict; + XWStreamCtxt* recStream; + XP_Bool success = XP_FALSE; + DictionaryCtxt* dict; + + recStream = gameRecordToStream( globals, gIndex ); + + /* now read everything out of the stream */ + if ( !!recStream ) { + char ignore[MAX_GAMENAME_LENGTH]; + + /* skip the name */ + stream_getBytes( recStream, ignore, MAX_GAMENAME_LENGTH ); + + loadGamePrefs( /*globals, */recStream ); + + hasDict = stream_getU8( recStream ); + if ( hasDict ) { + XP_UCHAR name[33]; + stringFromStreamHere( recStream, name, sizeof(name) ); + dict = palm_dictionary_make( MPPARM(globals->mpool) globals, + name, globals->dictList ); + success = dict != NULL; + + if ( !success ) { + reportMissingDict( globals, name ); + beep(); + } + } else { + dict = NULL; + success = XP_TRUE; + } + + if ( success ) { + success = game_makeFromStream( MEMPOOL recStream, game, ginfo, + dict, &globals->util, + globals->draw, &globals->gState.cp, + palm_send, IF_CH(palm_reset) globals ); + } + + stream_destroy( recStream ); + } + + return success; +} /* loadCurrentGame */ + +static void +initUtilFuncs( PalmAppGlobals* globals ) +{ + UtilVtable* vtable = globals->util.vtable = + XP_MALLOC( globals->mpool, sizeof( UtilVtable ) ); + globals->util.closure = (void*)globals; + globals->util.gameInfo = &globals->gameInfo; + + MPASSIGN( globals->util.mpool, globals->mpool ); + + vtable->m_util_getVTManager = palm_util_getVTManager; + vtable->m_util_userError = palm_util_userError; + vtable->m_util_getSquareBonus = palm_util_getSquareBonus; + vtable->m_util_userQuery = palm_util_userQuery; + vtable->m_util_userPickTile = palm_util_userPickTile; + vtable->m_util_askPassword = palm_util_askPassword; + vtable->m_util_trayHiddenChange = palm_util_trayHiddenChange; + vtable->m_util_yOffsetChange = palm_util_yOffsetChange; + vtable->m_util_notifyGameOver = palm_util_notifyGameOver; + vtable->m_util_hiliteCell = palm_util_hiliteCell; + vtable->m_util_engineProgressCallback = palm_util_engineProgressCallback; + vtable->m_util_setTimer = palm_util_setTimer; + vtable->m_util_requestTime = palm_util_requestTime; + vtable->m_util_altKeyDown = palm_util_altKeyDown; + vtable->m_util_getCurSeconds = palm_util_getCurSeconds; + vtable->m_util_makeEmptyDict = palm_util_makeEmptyDict; +#ifndef XWFEATURE_STANDALONE_ONLY + vtable->m_util_makeStreamFromAddr = palm_util_makeStreamFromAddr; +#endif + vtable->m_util_getUserString = palm_util_getUserString; + vtable->m_util_warnIllegalWord = palm_util_warnIllegalWord; + vtable->m_util_remSelected = palm_util_remSelected; +#if defined XWFEATURE_BLUETOOTH || defined XWFEATURE_RELAY + vtable->m_util_addrChange = palm_util_addrChange; +#endif +#ifdef XWFEATURE_SEARCHLIMIT + vtable->m_util_getTraySearchLimits = palm_util_getTraySearchLimits; +#endif +#ifdef SHOW_PROGRESS + vtable->m_util_engineStarting = palm_util_engineStarting; + vtable->m_util_engineStopping = palm_util_engineStopping; +#endif +} /* initUtilFuncs */ + +#ifdef COLOR_SUPPORT +static void +loadColorsFromRsrc( DrawingPrefs* prefs, MemHandle colorH ) +{ + RGBColorType color; + UInt8* colorP; + short index = 0; + UInt32 count; + + count = MemHandleSize( colorH ); + XP_ASSERT( count < 0xFFFF ); + XP_ASSERT( (((XP_U16)count) % 3) == 0 ); + colorP = MemHandleLock( colorH ); + + do { + color.r = *colorP++; + color.g = *colorP++; + color.b = *colorP++; + prefs->drawColors[index++] = WinRGBToIndex( &color ); + } while ( (count -= 3) != 0 ); + +#ifdef XWFEATURE_FIVEWAY + prefs->drawColors[COLOR_CURSOR] + = UIColorGetTableEntryIndex( UIObjectSelectedFill ); +#endif + MemHandleUnlock( colorH ); +} /* loadColorsFromRsrc */ +#endif + +static void +palmInitPrefs( PalmAppGlobals* globals ) +{ + globals->gState.showGrid = true; + globals->gState.versionNum = CUR_PREFS_VERS; + globals->gState.cp.showBoardArrow = XP_TRUE; + globals->gState.cp.showRobotScores = XP_TRUE; + +#ifdef SHOW_PROGRESS + globals->gState.showProgress = true; +#endif + +} /* palmInitPrefs */ + +static void +openXWPrefsDB( PalmAppGlobals* globals ) +{ + Err err; + + err = DmCreateDatabase( CARD_0, XW_PREFS_DBNAME, + APPID, XWORDS_PREFS_TYPE, true ); + XP_ASSERT( err == errNone || err == dmErrAlreadyExists ); + globals->boardDBID = DmFindDatabase( CARD_0, XW_PREFS_DBNAME ); + globals->boardDBP = DmOpenDatabase( CARD_0, globals->boardDBID, + dmModeWrite ); +} /* openXWPrefsDB */ + +static XP_Bool +setupBonusPtrs( PalmAppGlobals* globals ) +{ + XP_U16 i; + for ( i = 0; i < NUM_BOARD_SIZES; ++i ) { + MemHandle hand = findXWPrefsRsrc( globals, BOARD_RES_TYPE, + BOARD_RES_ID + i ); + if ( !hand ) { + return XP_FALSE; + } + + XP_ASSERT( MemHandleLockCount(hand) == 0 ); + globals->bonusResPtr[i] = MemHandleLock( hand ); + } + return XP_TRUE; +} /* setupBonusPtrs */ + +static void +unlockBonusPtrs( PalmAppGlobals* globals ) +{ + XP_U16 i; + for ( i = 0; i < NUM_BOARD_SIZES; ++i ) { + MemPtrUnlock( (MemPtr)globals->bonusResPtr[i] ); + } +} /* unlockBonusPtrs */ + +static void +openGamesDB( PalmAppGlobals* globals ) +{ + Err err; + + err = DmCreateDatabase( CARD_0, XW_GAMES_DBNAME, + APPID, XWORDS_GAMES_TYPE, false ); + globals->gamesDBID = DmFindDatabase( CARD_0, XW_GAMES_DBNAME ); + globals->gamesDBP = DmOpenDatabase( CARD_0, globals->gamesDBID, + dmModeReadWrite ); + XP_ASSERT( !!globals->gamesDBP ); +} /* openGamesDB */ + +static MemHandle +findXWPrefsRsrc( PalmAppGlobals* globals, UInt32 resType, UInt16 resID ) +{ + Int16 index; + MemHandle handle = NULL; + Boolean beenThere = XP_FALSE; + + for ( ; ; ) { + XP_ASSERT( !!globals->boardDBP ); + index = DmFindResource( globals->boardDBP, resType, resID, NULL ); + + if ( index == -1 ) { /* not found */ + MemHandle builtinH; + MemHandle newH; + UInt32 size; + + if ( !beenThere ) { + builtinH = DmGetResource( resType, resID ); + XP_ASSERT( !!builtinH ); + size = MemHandleSize( builtinH ); + newH = DmNewResource( globals->boardDBP, resType, + resID, size ); + XP_ASSERT( !!newH ); + DmWrite( MemHandleLock( newH ), 0, MemHandleLock(builtinH), + size ); + MemHandleUnlock( newH ); + MemHandleUnlock( builtinH ); + DmReleaseResource( newH ); + DmReleaseResource( builtinH ); + + beenThere = XP_TRUE; + continue; + } + break; + } + + handle = DmGetResourceIndex( globals->boardDBP, index ); + break; + } + + return handle; +} /* findXWPrefsRsrc */ + +static XP_Bool +initResources( PalmAppGlobals* globals ) +{ + /* strings */ + MemHandle hand; + + XP_ASSERT( !globals->stringsResPtr ); + + hand = DmGetResource( STRL_RES_TYPE, STRL_RES_ID ); + XP_ASSERT( !!hand ); + XP_ASSERT( MemHandleLockCount(hand) == 0 ); + globals->stringsResPtr = (XP_UCHAR*)MemHandleLock( hand ); + + /* bonus square and color values. These live in a separate database, + which we create if it doesn't already exist. */ + openXWPrefsDB( globals ); + if ( !setupBonusPtrs( globals ) ) { + return XP_FALSE; + } + + openGamesDB( globals ); + + if ( globals->able == COLOR ) { + hand = findXWPrefsRsrc( globals, COLORS_RES_TYPE, COLORS_RES_ID ); + if ( !hand ) { + return XP_FALSE; + } + loadColorsFromRsrc( &globals->drawingPrefs, hand ); + DmReleaseResource( hand ); + } + + return XP_TRUE; +} /* initResources */ + +static void +freeAndUnlockPtr( MemPtr ptr ) +{ + MemHandle hand; + XP_ASSERT( !!ptr ); + hand = MemPtrRecoverHandle(ptr ); + XP_ASSERT( !!hand ); + MemHandleUnlock( hand ); + DmReleaseResource( hand ); +} /* freeAndUnlockPtr */ + +static void +uninitResources( PalmAppGlobals* globals ) +{ + XP_U16 i; + + /* strings */ + freeAndUnlockPtr( globals->stringsResPtr ); + globals->stringsResPtr = NULL; + + /* bonus square values */ + for ( i = 0; i < NUM_BOARD_SIZES; ++i ) { + freeAndUnlockPtr( globals->bonusResPtr[i] ); + } + + XP_ASSERT( !!globals->boardDBP ); + DmCloseDatabase( globals->boardDBP ); + +} /* uninitResources */ + +const XP_UCHAR* +getResString( PalmAppGlobals* globals, XP_U16 strID ) +{ + XP_ASSERT( !!globals->stringsResPtr ); + XP_ASSERT( strID < MemPtrSize( globals->stringsResPtr ) ); + XP_ASSERT( (strID == 0) || (globals->stringsResPtr[strID-1] == '\0') ); + XP_ASSERT( strID < STR_LAST_STRING ); + return &globals->stringsResPtr[strID]; +} /* getResString */ + +static Err +volChangeEventProc( SysNotifyParamType* XP_UNUSED_SILK(notifyParamsP) ) +{ +#if 0 + if ( notifyParamsP->notifyType == sysNotifyVolumeUnmountedEvent ) { + + DictListHandleUnmount( globals->dictList ); + + } else if ( notifyParamsP->notifyType == sysNotifyVolumeMountedEvent ) { + + DictListHandleMount( &globals->dictList ); + + } else { + XP_ASSERT(0); + return errNone; + } +#endif + +#ifdef FEATURE_SILK + if ( notifyParamsP->notifyType == sysNotifyDisplayChangeEvent ) { + postEmptyEvent( doResizeWinEvent ); + return errNone; + } +#endif + /* for now, just blow outta here! Force the app to rebuild + datastructures when it's relaunched. This is a hack but I like + it. :-) */ +#ifndef REALLY_HANDLE_MEDIA + postEmptyEvent( appStopEvent ); +#endif + + return errNone; +} /* volChangeEventProc */ + +static void +doCallbackReg( PalmAppGlobals* globals, XP_Bool reg ) +{ + /* The mounted/unmounted events aren't there unless we're PalmOS version + 4.0 or greater. No need to use FtrGet to check for Notification Mgr + here, as it's useless without these. */ + if ( globals->romVersion >= 40 ) { + XP_U16 i; + UInt32 notifyTypes[] = { sysNotifyVolumeUnmountedEvent + , sysNotifyVolumeMountedEvent +#ifdef FEATURE_SILK + , sysNotifyDisplayChangeEvent +#endif + }; + + + for ( i = 0; i < VSIZE(notifyTypes); ++i ) { + UInt32 notifyType = notifyTypes[i]; + + if ( reg ) { + SysNotifyRegister( 0, 0, notifyType, volChangeEventProc, + sysNotifyNormalPriority, globals ); + } else { + SysNotifyUnregister( 0, 0, notifyType, + sysNotifyNormalPriority); + } + } + } +} /* doCallbackReg */ + +/* temp workarounds for some sony include file trouble */ +# ifdef FEATURE_SILK +extern Err SilkLibEnableResizeFoo(UInt16 refNum) + SILK_LIB_TRAP(sysLibTrapCustom+1); +extern Err VskSetStateFoo(UInt16 refNum, UInt16 stateType, UInt16 state) + SILK_LIB_TRAP(sysLibTrapCustom+3+3); +# endif + +static XP_Bool +isOnZodiac( void ) +{ + // from http://tamspalm.tamoggemon.com/2006/03/02/determining-if-your-app-runs-on-a-zodiac/ + const XP_U32 twCreatorID = 'Tpwv'; + Err err; + UInt32 manufacturer; + XP_Bool result; + err = FtrGet( sysFileCSystem, sysFtrNumOEMCompanyID, &manufacturer ); + result = (err == errNone) && (manufacturer == twCreatorID); + LOG_RETURNF( "%d", (int)result ); + return result; +} + +static void +initHighResGlobals( PalmAppGlobals* globals ) +{ + Err err; + XP_U32 vers; + + err = FtrGet( sysFtrCreator, sysFtrNumWinVersion, &vers ); + XP_ASSERT( err == errNone ); + globals->hasHiRes = (err == errNone) && (vers >= 4) && !globals->isZodiac; + XP_LOGF( "hasHiRes = %d", (XP_U16)globals->hasHiRes ); + globals->oneDotFiveAvail = globals->hasHiRes + && (err == errNone) && (vers >= 5); + +#ifdef XWFEATURE_FIVEWAY +# ifndef hsFtrIDNavigationSupported +# define hsFtrIDNavigationSupported 14 +# endif + /* sysFtrNumUIHardwareFlags unavailable on PalmOS 4 */ + err = FtrGet( sysFtrCreator, sysFtrNumUIHardwareFlags, &vers ); + globals->generatesKeyUp = ( (err == errNone) && + ((vers & sysFtrNumUIHardwareHasKbd) != 0) ) + || globals->isZodiac; + globals->hasTreoFiveWay = (err == errNone) + && ((vers & sysFtrNumUIHardwareHas5Way) != 0) && !globals->isZodiac; + + err = FtrGet( hsFtrCreator, hsFtrIDNavigationSupported, &vers ); + if ( errNone == err ) { + XP_ASSERT( vers == 1 || vers == 2 ); + globals->isTreo600 = (err == errNone) && (vers == 1); + } +#endif + +#ifdef FEATURE_SILK + if ( globals->hasHiRes ) { + XP_U16 ref; + + err = SysLibFind(sonySysLibNameSilk, &ref ); + if ( err == sysErrLibNotFound ) { + err = SysLibLoad( 'libr', sonySysFileCSilkLib, &ref ); + } + + if ( err == errNone ) { + XP_U32 tmp; + globals->sonyLibRef = ref; + + err = FtrGet( sonySysFtrCreator, sonySysFtrNumVskVersion, &tmp ); + if ( err == errNone ) { + globals->doVSK = XP_TRUE; + if ( VskOpen( ref ) == errNone ) { + VskSetStateFoo( ref, vskStateEnable, 1 ); + } + } else { + if ( SilkLibOpen( ref ) == errNone ) { + SilkLibEnableResizeFoo( ref ); + } + } + } + } +#endif +} /* initHighResGlobals */ + +static void +uninitHighResGlobals( PalmAppGlobals* XP_UNUSED_SILK(globals) ) +{ +#ifdef FEATURE_SILK + if ( globals->hasHiRes && globals->sonyLibRef != 0 ) { + if ( globals->doVSK ) { + VskClose( globals->sonyLibRef ); + } else { + SilkLibClose( globals->sonyLibRef ); + } + } +#endif +} /* uninitHighResGlobals */ + +static XP_Bool +canConvertPrefs( XWords4PreferenceType* prefs, UInt16 prefSize, XP_S16 vers ) +{ + XP_Bool success = XP_FALSE; + + if ( vers == VERSION_NUM_405 ) { + if ( prefSize < sizeof(XWords4PreferenceType) ) { + XP_U8* newRgn = ((XP_U8*)prefs) + prefSize; + XP_MEMSET( newRgn, 0, sizeof(XWords4PreferenceType) - prefSize ); + } + success = XP_TRUE; + } + + return success; +} /* canConvertPrefs */ + +/***************************************************************************** + * + ****************************************************************************/ +static XP_Bool +startApplication( PalmAppGlobals** globalsP ) +{ + UInt16 prefSize; + Boolean prefsFound; + XWords4PreferenceType prefs; + PalmAppGlobals* globals; + Boolean leftyFlag; + Int16 vers; + UInt32 ignore; + Err err; + MPSLOT; + +#if defined FOR_GREMLINS + SysRandom( 1 ); +#else + SysRandom( TimGetTicks() ); /* initialize */ +#endif + +#ifdef MEM_DEBUG + mpool = mpool_make(); +#endif + + globals = (PalmAppGlobals*)XP_MALLOC( mpool, sizeof( PalmAppGlobals ) ); + *globalsP = globals; + setFormRefcon( globals ); + XP_MEMSET( globals, 0, sizeof(PalmAppGlobals) ); + MPASSIGN( globals->mpool, mpool ); + + globals->isZodiac = isOnZodiac(); + + initHighResGlobals( globals ); + getSizes( globals ); + + globals->runningOnPOSE = FtrGet( 'pose', 0, &ignore) != ftrErrNoSuchFeature; + +#if defined XWFEATURE_BLUETOOTH + err = FtrGet( btLibFeatureCreator, btLibFeatureVersion, &ignore ); + /* could expand the test to skip version 1 and the Treo650 :-) */ + globals->hasBTLib = ftrErrNoSuchFeature != err; +# ifdef MEM_DEBUG + if ( errNone == err ) { + /* Sprint Treo650 is returning 0036 */ + /* Treo 700 on VWZ: sysFileCBtLib version: 00000003 */ + /* Treo 650 on Sprint: sysFileCBtLib version: 00000001 */ + XP_LOGF( "sysFileCBtLib version: %lx", ignore ); + } else { + XP_LOGF( "no sysFileCBtLib via FtrGet: OS too old?" ); + } + /* Make the UI elements easier to test */ + if ( globals->runningOnPOSE ) { + globals->hasBTLib = XP_TRUE; + } +# endif +#endif + + globals->vtMgr = make_vtablemgr( MPPARM_NOCOMMA(globals->mpool) ); + + globals->romVersion = romVersion(); + + globals->isFirstLaunch = true; + + leftyFlag = 0; + if ( !PrefGetAppPreferencesV10('Lfty', 1, &leftyFlag, + sizeof(leftyFlag) )) { + leftyFlag = 0; + } + globals->isLefty = leftyFlag != 0; + +#ifdef COLOR_SUPPORT + if ( (globals->romVersion >= 35) && (cur_screen_depth() >= 8) ) { + globals->able = COLOR; + } else { + globals->able = ONEBIT; + } +#else + globals->able = ONEBIT; +#endif + + if ( !initResources( globals ) ) { + return XP_FALSE; + } + +#ifdef XWFEATURE_RELAY + palm_ip_setup( globals ); +#endif + + doCallbackReg( globals, XP_TRUE ); + + initUtilFuncs( globals ); + + offerConvertOldDicts( globals ); + + globals->dictList = DictListMake( MPPARM_NOCOMMA(globals->mpool) ); + if ( DictListCount( globals->dictList ) == 0 ) { + userErrorFromStrId( globals, STR_NO_DICT_INSTALLED ); + return XP_FALSE; + } + + prefSize = sizeof( prefs ); + vers = PrefGetAppPreferences( AppType, PrefID, &prefs, &prefSize, true); + if ( vers == VERSION_NUM ) { + prefsFound = XP_TRUE; + } else if ( vers != noPreferenceFound ) { + prefsFound = canConvertPrefs( &prefs, prefSize, vers ); + } else { + prefsFound = XP_FALSE; + } + + if ( prefsFound ) { + prefs.versionNum = XP_NTOHS( prefs.versionNum ); + prefs.curGameIndex = XP_NTOHS( prefs.curGameIndex ); + prefs.focusItem = XP_NTOHS( prefs.focusItem ); + + MemMove( &globals->gState, &prefs, sizeof(prefs) ); + } + + globals->draw = palm_drawctxt_make( MPPARM(globals->mpool) + globals->able, + globals, + getResString, + &globals->drawingPrefs ); + + FrmGotoForm( XW_MAIN_FORM ); + + /* do this first so players who don't exist have default names */ + gi_initPlayerInfo( MEMPOOL &globals->gameInfo, + getResString( globals, STR_DEFAULT_NAME ) ); + + if ( prefsFound && loadCurrentGame( globals, globals->gState.curGameIndex, + &globals->game, &globals->gameInfo) ) { + postEmptyEvent( loadGameEvent ); + globals->isFirstLaunch = false; + } else { + DictListEntry* dlep; + XP_U32 gameID; + + /* if we're here because dict missing, don't re-init all prefs! */ + if ( !prefsFound ) { + palmInitPrefs( globals ); + } else { + /* increment count so we get a new game rather than replace + existing one. We want it still there if somebody puts the + missing dict back. */ + globals->gState.curGameIndex = countGameRecords( globals ); + } + globals->isNewGame = true; + + getNthDict( globals->dictList, 0, &dlep ); + globals->gameInfo.dictName = copyString( globals->mpool, + dlep->baseName ); + + gameID = TimGetSeconds(); + game_makeNewGame( MEMPOOL &globals->game, &globals->gameInfo, + &globals->util, globals->draw, gameID, + &globals->gState.cp, + palm_send, IF_CH(palm_reset) globals ); + FrmPopupForm( XW_NEWGAMES_FORM ); + } + + return XP_TRUE; +} /* startApplication */ + +/* save the stream's contents to a database. */ +static void +writeToDb( XWStreamCtxt* stream, void* closure ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)closure; + Err err; + + err = DmCreateDatabase( CARD_0, XW_GAMES_DBNAME, + APPID, XWORDS_GAMES_TYPE, false ); + + streamToGameRecord( globals, stream, globals->gState.curGameIndex ); +} /* writeToDb */ + +static void +saveOpenGame( PalmAppGlobals* globals ) +{ + if ( !!globals->game.server ) { + XWStreamCtxt* memStream; + DictionaryCtxt* dict; + const XP_UCHAR* dictName; + char namebuf[MAX_GAMENAME_LENGTH]; + + if ( gi_countLocalHumans( &globals->gameInfo ) > 1 ) { + board_hideTray( globals->game.board ); /* so won't be visible when + next opened */ + } + memStream = makeSimpleStream( globals, writeToDb ); + stream_open( memStream ); + + /* write the things's name. Name is first because we want to be able + to manipulate it without knowing about the other stuff. */ + nameFromRecord( globals, globals->gState.curGameIndex, namebuf ); + stream_putBytes( memStream, namebuf, MAX_GAMENAME_LENGTH ); + + saveGamePrefs( /*globals, */memStream ); + + /* the dictionary */ + dict = model_getDictionary( globals->game.model ); + dictName = !!dict? dict_getName( dict ) : NULL; + stream_putU8( memStream, !!dictName ); + if ( !!dictName ) { + stringToStream( memStream, dictName ); + } + + game_saveToStream( &globals->game, &globals->gameInfo, memStream ); + + stream_destroy( memStream ); + } +} /* saveOpenGame */ + +/***************************************************************************** + * + ****************************************************************************/ +static void +stopApplication( PalmAppGlobals* globals ) +{ + if ( globals != NULL ) { +#ifdef XWFEATURE_FIVEWAY + Int16 focusItem = getFocusOwner(); +#endif + MPSLOT; + + saveOpenGame( globals ); + + FrmCloseAllForms(); + + uninitResources( globals ); + +#ifdef XWFEATURE_BLUETOOTH + palm_bt_close( globals ); +#endif + + /* Write the state information -- once we're ready to read it in. + But skip the save if user cancelled launching the first time. */ + if ( !globals->isFirstLaunch ) { + XWords4PreferenceType prefs; + /* temporarily don't save prefs since we crash on opening + them. */ + XP_MEMCPY( &prefs, &globals->gState, sizeof(prefs) ); + prefs.versionNum = XP_HTONS( prefs.versionNum ); + prefs.curGameIndex = XP_HTONS( prefs.curGameIndex ); + +#ifdef XWFEATURE_FIVEWAY + prefs.focusItem = XP_HTONS(focusItem); +#endif + + PrefSetAppPreferences( AppType, PrefID, VERSION_NUM, + &prefs, sizeof(prefs), true ); + } + + if ( !!globals->draw ) { + draw_destroyCtxt( globals->draw ); + } + + game_dispose( &globals->game ); + gi_disposePlayerInfo( MEMPOOL &globals->gameInfo ); + +#ifdef XWFEATURE_RELAY + palm_ip_close( globals ); +#endif + + if ( !!globals->dictList ) { + DictListFree( MPPARM(globals->mpool) globals->dictList ); + } + + if ( !!globals->util.vtable ) { + XP_FREE( globals->mpool, globals->util.vtable ); + } + + if ( !!globals->prefsDlgState ) { + XP_FREE( globals->mpool, globals->prefsDlgState ); + } + + if ( !!globals->savedGamesState && !globals->isFirstLaunch ) { + freeSavedGamesData( MPPARM(globals->mpool) + globals->savedGamesState ); + XP_FREE( globals->mpool, globals->savedGamesState ); + } + + uninitHighResGlobals( globals ); + + XP_ASSERT( !!globals->gamesDBP ); + DmCloseDatabase( globals->gamesDBP ); + + if ( !!globals->vtMgr ) { + vtmgr_destroy( MPPARM(globals->mpool) globals->vtMgr ); + } + + doCallbackReg( globals, XP_FALSE ); + + MPASSIGN( mpool, globals->mpool ); + XP_FREE( globals->mpool, globals ); + mpool_destroy( mpool ); + } +} /* stopApplication */ + +static Int32 +figureWaitTicks( PalmAppGlobals* globals ) +{ + Int32 result = evtWaitForever; + XP_U32 when; + XWTimerReason why; + + if ( 0 ) { +#ifdef XWFEATURE_RELAY + } else if ( ipSocketIsOpen(globals) ) { +/* we'll do our sleeping in NetLibSelect */ + result = 0; +#endif + } else if ( globals->timeRequested || globals->hintPending ) { + result = 0; + } else if ( timeForTimer( globals, &why, &when ) ) { + result = when - TimGetTicks(); + if ( result < 0 ) { + result = 0; + } + } else { + /* leave it */ + } + /* XP_DEBUGF( "figureWaitTicks returning %d", result ); */ + +# ifdef XWFEATURE_BLUETOOTH + if ( !!globals->mainForm ) { + palm_bt_amendWaitTicks( globals, &result ); + } +# endif + + return result; +} /* figureWaitTicks */ + +static XP_Bool +closeNonMainForms( PalmAppGlobals* globals ) +{ +#if 1 + return FrmGetActiveForm() == globals->mainForm; +#else + /* This doesn't work. If there's a form in front of the main form + sending it the close event closes it, but then FrmGetActiveForm() + returns null the next time called.*/ + FormPtr prevActive; + FormPtr curActive = NULL; + + for ( ; ; ) { + EventType event; + + prevActive = curActive; + curActive = FrmGetActiveForm(); + if ( prevActive == curActive ) { + return XP_FALSE; + } + + if ( curActive == globals->mainForm ) { + return XP_TRUE; + } + event.eType = frmCloseEvent; + event.data.frmClose.formID = FrmGetFormId(curActive); + FrmDispatchEvent( &event ); + } +#endif +} /* closeNonMainForms */ + +/***************************************************************************** + * + ****************************************************************************/ +static void +eventLoop( PalmAppGlobals* globals ) +{ + EventType event; + + do { +#ifdef XWFEATURE_RELAY + if ( !!globals->game.comms + && (comms_getConType(globals->game.comms) == COMMS_CONN_RELAY) ) { + checkHandleNetEvents( globals ); + } +#endif + + /* EvtGetEvent( &event, evtWaitForever ); */ + EvtGetEvent( &event, figureWaitTicks(globals) ); + + if ( event.eType == keyDownEvent ) { + if ( 0 ) { +#ifdef FOR_GREMLINS + } else if ( event.data.keyDown.chr == findChr ) { + continue; +#endif + } else if ( (event.data.keyDown.modifiers & commandKeyMask) != 0 + && ( (event.data.keyDown.chr == autoOffChr) + || (event.data.keyDown.chr == hardPowerChr) ) + && !!globals->game.board ) { + if ( !globals->menuIsDown /* hi Marcus :-) */ + && closeNonMainForms(globals) + && gi_countLocalHumans( &globals->gameInfo ) > 1 + && board_hideTray( globals->game.board ) ) { + board_draw( globals->game.board ); + } + } + } + + /* Give the system a chance to handle the event. */ + if ( !SysHandleEvent(&event)) { + UInt16 error; + if ( !MenuHandleEvent( NULL, &event, &error)) { + if ( !applicationHandleEvent( &event )) { + FrmDispatchEvent(&event); + } + } + } + } while (event.eType != appStopEvent); +} /* eventLoop */ + +/********************************************************************** + * applicationHandleEvent + **********************************************************************/ +static Boolean +applicationHandleEvent( EventPtr event ) +{ + FormPtr frm = NULL; + Int16 formId; + Boolean result = false; + FormEventHandlerType* handler = NULL; + + if ( event->eType == frmLoadEvent ) { + /*Load the form resource specified in the event then activate the + form.*/ + formId = event->data.frmLoad.formID; + frm = FrmInitForm(formId); + FrmSetActiveForm(frm); + + switch (formId) { + case XW_MAIN_FORM: + handler = mainViewHandleEvent; + break; + case XW_NEWGAMES_FORM: + handler = newGameHandleEvent; + break; + case XW_DICTINFO_FORM: + handler = dictFormHandleEvent; + break; + case XW_PREFS_FORM: + handler = PrefsFormHandleEvent; + break; +#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH + case XW_CONNS_FORM: + handler = ConnsFormHandleEvent; + break; +#endif + case XW_SAVEDGAMES_DIALOG_ID: + handler = savedGamesHandleEvent; + break; + } + if ( !!handler ) { + XP_ASSERT( !!frm ); + result = true; + FrmSetEventHandler( frm, handler ); + } + } + + return result; +} // applicationHandleEvent + +#if 0 +static void +destroy_on_close( XWStreamCtxt* p_stream ) +{ + MemStreamCtxt* stream = (MemStreamCtxt*)p_stream; + /* PalmAppGlobals* globals = stream->globals; */ + MemHandle handle; + + XP_WARNF( "destroy_on_close called" ); + handle = stream->bufHandle; + MemHandleFree( handle ); + stream_destroy( p_stream ); +} /* destroy_on_close */ +#endif + +static void +palmFireTimer( PalmAppGlobals* globals, XWTimerReason why ) +{ + XWTimerProc proc = globals->timerProcs[why]; + void* closure = globals->timerClosures[why]; + XP_ASSERT( TimGetTicks() >= globals->timerFireAt[why] ); + globals->timerProcs[why] = NULL; + (*proc)( closure, why ); +} /* fireTimer */ + +static XP_Bool +timeForTimer( PalmAppGlobals* globals, XWTimerReason* why, XP_U32* when ) +{ + XP_U16 i; + XWTimerReason nextWhy = 0; + XP_U32 nextWhen = 0xFFFFFFFF; + XP_Bool found; + + for ( i = 1; i < NUM_PALM_TIMERS; ++i ) { + if ( (globals->timerProcs[i] != NULL) && + (globals->timerFireAt[i] < nextWhen) ) { + nextWhy = i; + nextWhen = globals->timerFireAt[i]; + } + } + + found = nextWhy != 0; + if ( found ) { + *why = nextWhy; + *when = nextWhen; + } + return found; +} /* timeForTimer */ + +#ifdef XWFEATURE_BLUETOOTH +static void +showConnState( PalmAppGlobals* globals ) +{ + CommsCtxt* comms = globals->game.comms; + Int16 resID = 0; + if ( !!comms ) { + if ( (COMMS_CONN_BT == comms_getConType( comms )) ) { + switch( globals->btUIState ) { + case BTUI_NOBT: + break; + case BTUI_NONE: + resID = BTSTATUS_NONE_RES_ID; break; + case BTUI_LISTENING: + resID = BTSTATUS_LISTENING_RES_ID; break; + case BTUI_CONNECTING: + resID = BTSTATUS_SEEKING_RES_ID; break; + case BTUI_CONNECTED: + case BTUI_SERVING: + resID = BTSTATUS_CONNECTED_RES_ID; break; + } + } /* else might want IP conn status too.... */ + } + if ( globals->lastBTStatusRes != resID ) { + RectangleType bounds; + getObjectBounds( XW_BTSTATUS_GADGET_ID, &bounds ); + if ( resID != 0 ) { + draw_drawBitmapAt( globals->draw, resID, + bounds.topLeft.x, bounds.topLeft.y ); + } else { + if ( globals->useHiRes ) { + bounds.extent.x = (1 + bounds.extent.x) >> 1; + bounds.extent.y = (1 + bounds.extent.y) >> 1; + } + WinEraseRectangle( &bounds, 0 ); + } + globals->lastBTStatusRes = resID; + } +} /* showConnState */ + +static void +btEvtHandler( PalmAppGlobals* globals, BtCbEvtInfo* evt ) +{ + switch ( evt->evt ) { + case BTCBEVT_CONFIRM: + if ( globals->suspendBT ) { + evt->u.confirm.confirmed = XP_FALSE; + } else if ( globals->gameInfo.confirmBTConnect ) { + const XP_UCHAR* fmt; + char buf[256]; /* fmt is 182+ bytes in English */ + XP_ASSERT( !!globals->game.comms && + !comms_getIsServer(globals->game.comms) ); + fmt = getResString( globals, STRS_BT_CONFIRM ); + XP_SNPRINTF( buf, sizeof(buf), fmt, evt->u.confirm.hostName ); + evt->u.confirm.confirmed = palmask( globals, buf, NULL, -1 ); + globals->suspendBT = !evt->u.confirm.confirmed; + } else { + evt->u.confirm.confirmed = XP_TRUE; + } + break; + case BTCBEVT_CONN: + if ( !!globals->game.comms ) { + comms_resendAll( globals->game.comms ); + } + break; + case BTCBEVT_DATA: + if ( COMMS_CONN_BT == comms_getConType( globals->game.comms ) ) { + XWStreamCtxt* instream; + instream = makeSimpleStream( globals, NULL ); + stream_putBytes( instream, evt->u.data.data, evt->u.data.len ); + checkAndDeliver( globals, evt->u.data.fromAddr, + instream, COMMS_CONN_BT ); + } else { + /* If we're no longer using BT (meaning somebody loaded a new game + that doesn't use it), close it down. We don't want to do it as + part of unloading the old game since it's expensive to stop/start + BT and the new game will probably use the same connection. But if + we get here, a non-bt game's been loaded and we should shut + down.*/ + postEmptyEvent( closeBtLibEvent ); + } + break; + default: + XP_ASSERT(0); + } +} /* btEvtHandler */ +#endif + +static Boolean +handleNilEvent( PalmAppGlobals* globals ) +{ + Boolean handled = true; + XP_U32 when; + XWTimerReason why; + + if ( 0 ) { +#ifdef XWFEATURE_BLUETOOTH + } else if ( (handled = (!globals->suspendBT) + && palm_bt_doWork( globals, btEvtHandler, + &globals->btUIState ) ) + ,showConnState( globals ) + ,handled ) { + /* nothing to do */ +#endif + } else if ( timeForTimer( globals, &why, &when ) + && (when <= TimGetTicks()) ) { + palmFireTimer( globals, why ); + } else if ( globals->menuIsDown ) { + /* do nothing */ + } else if ( globals->hintPending ) { + handled = handleHintRequest( globals ); + } else if ( globals->timeRequested ) { + globals->timeRequested = false; + if ( globals->msgReceivedDraw ) { + XP_ASSERT ( !!globals->game.board ); + board_draw( globals->game.board ); + globals->msgReceivedDraw = XP_FALSE; + } + handled = server_do( globals->game.server ); + } else { + handled = false; + } + + return handled; +} /* handleNilEvent */ + +static Boolean +handleFlip( PalmAppGlobals* globals ) +{ + XP_ASSERT( !!globals->game.board ); + return board_flip( globals->game.board ); +} /* handle_flip_button */ + +static Boolean +handleValueToggle( PalmAppGlobals* globals ) +{ + return board_toggle_showValues( globals->game.board ); +} /* handleValueToggle */ + +static Boolean +handleHideTray( PalmAppGlobals* globals ) +{ + Boolean draw; + if ( TRAY_REVEALED == board_getTrayVisState( globals->game.board ) ) { + draw = board_hideTray( globals->game.board ); + } else { + draw = board_showTray( globals->game.board ); + } + + return draw; +} /* handleHideTray */ + +#ifdef XWFEATURE_SEARCHLIMIT +static Boolean +popupLists( EventPtr event ) +{ + Boolean handled = false; + XP_U16 ctlID; + ListPtr list = NULL; + XP_S16 chosen; + + if ( event->eType == ctlSelectEvent ) { + ctlID = event->data.ctlSelect.controlID; + if ( ctlID == XW_HINTCONFIG_MINSELECTOR_ID ) { + list = getActiveObjectPtr( XW_HINTCONFIG_MINLIST_ID ); + } else if ( ctlID == XW_HINTCONFIG_MAXSELECTOR_ID ) { + list = getActiveObjectPtr( XW_HINTCONFIG_MAXLIST_ID ); + } + + if ( !!list ) { + chosen = LstPopupList( list ); + if ( chosen >= 0 ) { + setSelectorFromList( ctlID, list, chosen ); + } + handled = true; + } + } + + return handled; +} /* popupLists */ + +static XP_Bool +doHintConfig( XP_U16* minP, XP_U16* maxP ) +{ + FormPtr form, prevForm; + ListPtr listMin, listMax; + XP_Bool confirmed; + + prevForm = FrmGetActiveForm(); + form = FrmInitForm( XW_HINTCONFIG_FORM_ID ); + FrmSetEventHandler( form, popupLists ); + FrmSetActiveForm( form ); + + listMin = getActiveObjectPtr( XW_HINTCONFIG_MINLIST_ID ); + LstSetSelection( listMin, *minP - 1 ); + setSelectorFromList( XW_HINTCONFIG_MINSELECTOR_ID, + listMin, *minP - 1 ); + + listMax = getActiveObjectPtr( XW_HINTCONFIG_MAXLIST_ID ); + LstSetSelection( listMax, *maxP - 1 ); + setSelectorFromList( XW_HINTCONFIG_MAXSELECTOR_ID, + listMax, *maxP - 1 ); + + confirmed = FrmDoDialog( form ) == XW_HINTCONFIG_OK_ID; + if ( confirmed ) { + *minP = LstGetSelection( listMin ) + 1; + *maxP = LstGetSelection( listMax ) + 1; + } + + FrmDeleteForm( form ); + FrmSetActiveForm( prevForm ); + + return confirmed; +} /* doHintConfig */ +#endif + +static Boolean +handleHintRequest( PalmAppGlobals* globals ) +{ + Boolean notDone; + Boolean draw; + + XP_ASSERT( !!globals->game.board ); + + draw = board_requestHint( globals->game.board, +#ifdef XWFEATURE_SEARCHLIMIT + globals->askTrayLimits, +#endif + + ¬Done ); + globals->hintPending = notDone; + return draw; +} /* handleHintRequest */ + +static Boolean +handleDone( PalmAppGlobals* globals ) +{ + return board_commitTurn( globals->game.board ); +} /* handleDone */ + +static Boolean +handleJuggle( PalmAppGlobals* globals ) +{ + return board_juggleTray( globals->game.board ); +} /* handleJuggle */ + +static Boolean +handleTrade( PalmAppGlobals* globals ) +{ + return board_beginTrade( globals->game.board ); +} /* handleJuggle */ + +static Boolean +buttonIsUsable( ControlPtr button ) +{ + return CtlEnabled( button ); +} /* buttonIsUsable */ + +static void +drawBitmapButton( PalmAppGlobals* globals, UInt16 ctrlID, UInt16 resID, + XP_Bool eraseIfDisabled ) +{ + FormPtr form; + UInt16 index; + RectangleType bounds; + + form = FrmGetActiveForm(); + index = FrmGetObjectIndex( form, ctrlID ); + FrmGetObjectBounds( form, index, &bounds ); + + if ( buttonIsUsable( getActiveObjectPtr( ctrlID ) ) ) { + draw_drawBitmapAt( globals->draw, resID, bounds.topLeft.x, + bounds.topLeft.y ); + } else if ( eraseIfDisabled ) { + /* gross hack; the button represents a larger bitmap; erase the + whole thing.*/ +#ifndef EIGHT_TILES + if ( ctrlID == XW_MAIN_HIDE_BUTTON_ID ) { + bounds.extent.x += TRAY_BUTTON_WIDTH + 1; + bounds.extent.y += TRAY_BUTTON_WIDTH + 1; + } +#endif + WinEraseRectangle( &bounds, 0 ); + } +} /* drawBitmapButton */ + +static void +drawFormButtons( PalmAppGlobals* globals ) +{ +#ifdef XWFEATURE_FIVEWAY + Int16 focusItem; +#endif + XP_U16 pairs[] = { + XW_MAIN_FLIP_BUTTON_ID, FLIP_BUTTON_BMP_RES_ID, XP_TRUE, + XW_MAIN_VALUE_BUTTON_ID, VALUE_BUTTON_BMP_RES_ID, XP_TRUE, + XW_MAIN_HINT_BUTTON_ID, HINT_BUTTON_BMP_RES_ID, XP_TRUE, +#ifndef EIGHT_TILES + XW_MAIN_HIDE_BUTTON_ID, TRAY_BUTTONS_BMP_RES_ID, XP_TRUE, +#endif + XW_MAIN_SHOWTRAY_BUTTON_ID, SHOWTRAY_BUTTON_BMP_RES_ID, XP_FALSE, + 0, + }; + XP_U16* pair = (XP_U16*)pairs; + + if ( FrmGetActiveFormID() == XW_MAIN_FORM ) { + while ( !!*pair ) { + drawBitmapButton( globals, pair[0], pair[1], pair[2] ); + pair += 3; + } + } + +#ifdef XWFEATURE_FIVEWAY + if ( globals->hasTreoFiveWay ) { + focusItem = globals->gState.focusItem; + if ( focusItem > 0 ) { + if ( isFormObject( globals->mainForm, focusItem ) ) { +/* XP_WARNF( "setting focus: %s", frmObjId_2str(focusItem) ); */ + setFormFocus( globals->mainForm, focusItem ); + if ( !isBoardObject( focusItem ) + && buttonIsUsable( getActiveObjectPtr(focusItem) ) ) { + drawFocusRingOnGadget( globals, focusItem, focusItem ); + } + } + globals->gState.focusItem = -1; + } else { + drawFocusRingOnGadget( globals, XW_MAIN_DONE_BUTTON_ID, + XW_MAIN_HIDE_BUTTON_ID ); + } + } +#endif +} /* drawFormButtons */ + +static void +updateScrollbar( PalmAppGlobals* globals, Int16 newValue ) +{ + if ( FrmGetActiveFormID() == XW_MAIN_FORM ) { + ScrollBarPtr scroll = getActiveObjectPtr( XW_MAIN_SCROLLBAR_ID ); + XW_TrayVisState state = board_getTrayVisState( globals->game.board ); + XP_U16 max, min; + + max = model_numRows( globals->game.model ); + min = max; + if ( globals->needsScrollbar && (max == SBAR_MAX) + && state != TRAY_HIDDEN ) { + min -= 2; /* fragile!!! PENDING */ + } + + SclSetScrollBar( scroll, newValue + min, min, max, SBAR_PAGESIZE ); + } +} /* updateScrollbar */ + +static void +palmSetCtrlsForTray( PalmAppGlobals* globals ) +{ + XW_TrayVisState state = board_getTrayVisState( globals->game.board ); + FormPtr form = globals->mainForm; + + /* In rare circumstances, e.g. when an appStopEvent comes in while the + prefs dialog is up, this'll get called when the main form's not on + top. In that case it's probably ok to just do nothing. But if not + I'll need to queue an event of some sort so it gets done later. */ + if ( FrmGetActiveFormID() == XW_MAIN_FORM ) { + + disOrEnable( form, XW_MAIN_HINT_BUTTON_ID, + (state==TRAY_REVEALED) && + !globals->gameInfo.hintsNotAllowed ); + +#ifndef EIGHT_TILES + disOrEnable( form, XW_MAIN_DONE_BUTTON_ID, state==TRAY_REVEALED ); + disOrEnable( form, XW_MAIN_JUGGLE_BUTTON_ID, state==TRAY_REVEALED ); + disOrEnable( form, XW_MAIN_TRADE_BUTTON_ID, state==TRAY_REVEALED ); + disOrEnable( form, XW_MAIN_HIDE_BUTTON_ID, state!=TRAY_HIDDEN ); +#endif + disOrEnable( form, XW_MAIN_SHOWTRAY_BUTTON_ID, state==TRAY_HIDDEN + && globals->gameInfo.nPlayers > 0 ); + + globals->scrollValue = board_getYOffset( globals->game.board ); + updateScrollbar( globals, globals->scrollValue ); + + /* PENDING(ehouse) Can't the board just do this itself? */ + if ( state==TRAY_HIDDEN ) { + board_setYOffset( globals->game.board, 0 ); + } + } +} /* palmSetCtrlsForTray */ + +static Boolean +scrollBoard( PalmAppGlobals* globals, Int16 newValue, Boolean fromBar ) +{ + XP_Bool result = XP_FALSE; + + XP_ASSERT( !!globals->game.board ); + + result = board_setYOffset( globals->game.board, newValue ); + + if ( !fromBar ) { + updateScrollbar( globals, newValue ); + } + return result; +} /* scrollBoard */ + +/* We can't create the board back in newgame.c because the wrong form's + * frontmost at that point. So we do it here instead -- and must also call + * server_do. + */ +static void +initAndStartBoard( PalmAppGlobals* globals, XP_Bool newGame ) +{ + DictionaryCtxt* dict; + XP_UCHAR* newDictName = globals->gameInfo.dictName; + + /* This needs to happen even when !newGame because it's how the dict + slots in PlayerInfo get loaded. That really ought to happen earlier, + though. */ + XP_ASSERT( !!globals->game.model ); + dict = model_getDictionary( globals->game.model ); + + if ( !!dict ) { + const XP_UCHAR* dictName = dict_getName( dict ); + if ( !!newDictName && 0 != XP_STRCMP( (const char*)dictName, + (const char*)newDictName ) ) { + dict_destroy( dict ); + dict = NULL; + } else { + replaceStringIfDifferent( globals->mpool, + &globals->gameInfo.dictName, dictName ); + } + } + + if ( !dict ) { + XP_ASSERT( !!newDictName ); + dict = palm_dictionary_make( MPPARM(globals->mpool) globals, + newDictName, globals->dictList ); + XP_ASSERT( !!dict ); + model_setDictionary( globals->game.model, dict ); + } + + if ( newGame ) { + XP_U32 newGameID = TimGetSeconds(); + game_reset( MEMPOOL &globals->game, &globals->gameInfo, + &globals->util, newGameID, &globals->gState.cp, + palm_send, IF_CH(palm_reset) globals ); +#if defined XWFEATURE_BLUETOOTH || defined XWFEATURE_RELAY || defined XWFEATURE_IR + if ( !!globals->game.comms ) { + comms_setAddr( globals->game.comms, + &globals->newGameState.addr ); + } else if ( globals->gameInfo.serverRole != SERVER_STANDALONE ) { + XP_ASSERT(0); + } +#endif + } + + XP_ASSERT( !!globals->game.board ); + getSizes( globals ); + (void)positionBoard( globals ); + +#ifdef XWFEATURE_IR + if ( newGame && globals->gameInfo.serverRole == SERVER_ISCLIENT ) { + XWStreamCtxt* stream; + XP_ASSERT( !!globals->game.comms ); + stream = makeSimpleStream( globals, palm_send_on_close ); + server_initClientConnection( globals->game.server, stream ); + } +#endif + + if ( !!globals->game.comms ) { + comms_start( globals->game.comms ); + } + + /* Used to call server_do here, but if it's a robot's turn it'll run + without drawing the board first. This allows work to get done almost + as quickly. If the board starts flashing on launch this is why; + server_do might need to take a bool param skip-robot */ + palm_util_requestTime( &globals->util ); + + board_invalAll( globals->game.board ); + board_draw( globals->game.board ); + +#ifdef XWFEATURE_BLUETOOTH + globals->suspendBT = XP_FALSE; + showConnState( globals ); +#endif + + globals->isNewGame = false; +} /* initAndStartBoard */ + +#ifdef DEBUG +static void +toggleBoolFtr( XP_U16 ftr ) +{ + UInt32 val; + FtrGet( APPID, ftr, &val ); + val = !val; + FtrSet( APPID, ftr, val ); + XP_WARNF( "Turned %s.", val==0? "OFF" : "ON" ); +} /* toggleBoolFtr */ + +static void +askOnClose( XWStreamCtxt* stream, void* closure ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)closure; + + (void)askFromStream( globals, stream, -1, false ); +} /* askOnClose */ +#endif + +static void +updateForLefty( PalmAppGlobals* globals, FormPtr form ) +{ + XP_S16 idsAndXs[] = { + /* ButtonID, x-coord-when-lefty, */ + XW_MAIN_FLIP_BUTTON_ID, 0, + XW_MAIN_VALUE_BUTTON_ID, 0, + XW_MAIN_HINT_BUTTON_ID, 0, + XW_MAIN_SCROLLBAR_ID, 0, + XW_MAIN_SHOWTRAY_BUTTON_ID, 0, + +#ifdef FOR_GREMLINS + GREMLIN_BOARD_GADGET_IDAUTOID, 9, + GREMLIN_TRAY_GADGET_IDAUTOID, 9, +#endif + +#ifndef EIGHT_TILES + XW_MAIN_HIDE_BUTTON_ID, -1, + XW_MAIN_JUGGLE_BUTTON_ID, TRAY_BUTTON_WIDTH-1, + XW_MAIN_TRADE_BUTTON_ID, -1, + XW_MAIN_DONE_BUTTON_ID, TRAY_BUTTON_WIDTH-1, +#endif +#ifdef XWFEATURE_BLUETOOTH + XW_BTSTATUS_GADGET_ID, 0, +#endif + 0, + }; + if ( globals->isLefty ) { + UInt16 id; + UInt16* idp = (UInt16*)idsAndXs; + for ( id = *idp; !!id; id = *(idp+=2) ) { + XP_S16 x, y; + UInt16 objIndex = FrmGetObjectIndex( form, id ); + FrmGetObjectPosition( form, objIndex, &x, &y ); + + FrmSetObjectPosition( form, objIndex, idp[1], y ); + } + } +} /* updateForLefty */ + +static void +beamBoard( PalmAppGlobals* globals ) +{ + Err err; + XP_UCHAR prcName[50]; + + unlockBonusPtrs( globals ); + DmCloseDatabase( globals->boardDBP ); + + /* do we need to close the db first, and reopen after? */ + XP_SNPRINTF( prcName, sizeof(prcName), (XP_UCHAR*)"%s.prc", + XW_PREFS_DBNAME ); + err = sendDatabase( CARD_0, globals->boardDBID, + prcName, (XP_UCHAR*)"board prefs" ); + + globals->boardDBP = DmOpenDatabase( CARD_0, globals->boardDBID, + dmModeWrite ); + setupBonusPtrs( globals ); +} /* beamBoard */ + +static XP_Bool +considerMenuShow( EventPtr event ) +{ + XP_S16 y = event->screenY; + XP_Bool penInRightPlace = (y < PALM_BOARD_TOP) && (y >= 0); + + if ( penInRightPlace ) { + EventType menuEvent; + XP_MEMSET( &menuEvent, 0, sizeof(menuEvent) ); + menuEvent.eType = keyDownEvent; + menuEvent.data.keyDown.chr = menuChr; + menuEvent.data.keyDown.keyCode = 0; + menuEvent.data.keyDown.modifiers = commandKeyMask; + EvtAddEventToQueue( &menuEvent ); + } + + return penInRightPlace; +} /* considerMenuShow */ + +/* Draw immediately, because we've made a change we need reflected + immediately. */ +static void +drawChangedBoard( PalmAppGlobals* globals ) +{ + if ( !!globals->game.board && !globals->menuIsDown ) { + board_draw( globals->game.board ); + } +} /* drawChangedBoard */ + +static XP_Bool +tryLoadSavedGame( PalmAppGlobals* globals, XP_U16 newIndex ) +{ + XWGame tmpGame; + CurGameInfo tmpGInfo; + XP_Bool loaded; + + XP_MEMSET( &tmpGame, 0, sizeof(tmpGame) ); + XP_MEMSET( &tmpGInfo, 0, sizeof(tmpGInfo) ); + + loaded = loadCurrentGame( globals, newIndex, &tmpGame, &tmpGInfo ); + + /* Nuke the one we don't want */ + game_dispose( loaded? &globals->game : &tmpGame ); + gi_disposePlayerInfo( MEMPOOL (loaded? &globals->gameInfo : &tmpGInfo) ); + + if ( loaded ) { + XP_MEMCPY( &globals->game, &tmpGame, sizeof(globals->game) ); + XP_MEMCPY( &globals->gameInfo, &tmpGInfo, sizeof(globals->gameInfo) ); + globals->gState.curGameIndex = newIndex; + } + + return loaded; +} /* tryLoadSavedGame */ + +static XP_U16 +hresX( PalmAppGlobals* globals, XP_U16 screenX ) +{ + if ( globals->useHiRes ) { + screenX *= 2; + } + return screenX; +} + +static XP_U16 +hresY( PalmAppGlobals* globals, XP_U16 screenY ) +{ + if ( globals->useHiRes ) { + screenY *= 2; + } + return screenY; +} + +static void +hresRect( PalmAppGlobals* globals, RectangleType* r ) +{ + if ( globals->useHiRes ) { + r->topLeft.x *= 2; + r->topLeft.y *= 2; + r->extent.x *= 2; + r->extent.y *= 2; + } +} + +#ifdef XWFEATURE_FIVEWAY +static void +invalRectAroundButton( PalmAppGlobals* globals, XP_U16 objectID ) +{ + RectangleType rect; + getObjectBounds( objectID, &rect ); + + rect.topLeft.x -= 3; + rect.topLeft.y -= 3; + rect.extent.x += 6; + rect.extent.y += 6; + hresRect( globals, &rect ); + + board_invalRect( globals->game.board, (XP_Rect*)&rect ); +} + +static XP_Bool +isBoardObject( XP_U16 id ) +{ + return id == XW_BOARD_GADGET_ID + || id == XW_SCOREBOARD_GADGET_ID + || id == XW_TRAY_GADGET_ID; +} + +static XP_Bool +handleFocusEvent( PalmAppGlobals* globals, const EventType* event, + XP_Bool* drawP ) +{ + XP_U16 objectID = event->data.frmObjectFocusTake.objectID; + XP_Bool isBoardObj = isBoardObject( objectID ); + XP_Bool take; + BoardObjectType typ; + + XP_ASSERT( &event->data.frmObjectFocusTake.objectID + == &event->data.frmObjectFocusLost.objectID ); + take = event->eType == frmObjectFocusTakeEvent; + +/* XP_LOGF( "%s(%s,%s)", __func__, frmObjId_2str(objectID), */ +/* (take? "take":"lost") ); */ + + if ( take && !globals->initialTakeDropped && + (objectID == XW_SCOREBOARD_GADGET_ID) ) { + /* Work around OS's insistence on sending initial take event. */ + globals->initialTakeDropped = XP_TRUE; + } else { + /* Need to invalidate the neighborhood of buttons on which palm draws + the focus ring when they lose focus -- to redraw where the focus + ring may have been. No need unless we have the focus now, + however, since we'll otherwise have drawn the object correctly + (unfocussed). */ + + if ( (!take) && (!isBoardObj) && isBoardObject( getFocusOwner() ) ) { + EventType event; + event.eType = updateAfterFocusEvent; + event.data.generic.datum[0] = objectID; + EvtAddEventToQueue( &event ); + } + + /* Board needs to know about any change involving it, including + something else taking the focus it may think it has. Why? + Because takes preceed losses, yet the board must draw itself + without focus before some button draws itself with focus and snags + as part of the background the board in focussed state. */ + + typ = isBoardObj? OBJ_BOARD + (objectID - XW_BOARD_GADGET_ID) : OBJ_NONE; + *drawP = board_focusChanged( globals->game.board, typ, take ); + if ( isBoardObj && take ) { + setFormFocus( globals->mainForm, objectID ); + } + } + return isBoardObj; +} /* handleFocusEvent */ +#endif + +#ifdef DO_TUNGSTEN_FIVEWAY +/* These are supposed to be defined in some SDK headers but I can't find 'em, + * and if I could they're obscure enough that I wouldn't want the build to + * depend on 'em since they're copyrighted and I couldn't distribute. */ +# define vchrNavChange (vchrPalmMin + 3) +# define navBitUp 0x0001 +# define navBitDown 0x0002 +# define navBitLeft 0x0004 +# define navBitRight 0x0008 +# define navBitSelect 0x0010 +# define navBitsAll 0x001F + +# define navChangeUp 0x0100 +# define navChangeDown 0x0200 +# define navChangeLeft 0x0400 +# define navChangeRight 0x0800 +# define navChangeSelect 0x1000 +# define navChangeBitsAll 0x1F00 +#endif + +static XP_Bool +handleKeyEvent( PalmAppGlobals* globals, const EventType* event, + XP_Bool* handledP ) +{ + /* keyDownEvent: be very careful here. keyUpEvent is only sent on + devices with a hard keyboard. Do not assume keyUpEvent or all + non-Treos will be broken!!! */ + + XP_Bool draw = XP_FALSE; + XP_Key xpkey; + XP_Bool handled = XP_FALSE; + XP_Bool altOn = (event->data.keyUp.modifiers & shiftKeyMask) != 0; + XP_Bool treatAsUp = !globals->generatesKeyUp + || (event->eType == keyUpEvent); + XP_U16 keyCode = event->data.keyDown.keyCode; + Int16 chr; + XP_Bool (*handler)( BoardCtxt*, XP_Key, XP_Bool* ); + BoardCtxt* board = globals->game.board; + XP_S16 incr = 0; /* needed for tungsten and zodiac, but not treo since + the OS handled focus movement between objects. */ + + globals->handlingKeyEvent = XP_TRUE; + +#ifdef DO_TUNGSTEN_FIVEWAY + if ( !globals->generatesKeyUp ) { /* this is the Tungsten case */ + if ( event->data.keyDown.chr == vchrNavChange ) { + if ( (keyCode & (/* navBitUp | */navChangeUp )) != 0 ) { + keyCode = vchrRockerUp; + incr = -1; + } else if ( (keyCode & (/* navBitDown | */navChangeDown )) != 0 ) { + keyCode = vchrRockerDown; + incr = 1; + } else if ( (keyCode & (navBitLeft /* |navChangeLeft */ )) != 0 ) { + keyCode = vchrRockerLeft; + incr = -1; + } else if ( (keyCode & ( navBitRight /*|navChangeRight*/ )) != 0 ) { + keyCode = vchrRockerRight; + incr = 1; + } else if ( (keyCode & (navBitSelect /*|navChangeSelect*/)) != 0 ) { + keyCode = vchrRockerCenter; + } + } else { + keyCode = event->data.keyUp.chr; + } + } +#endif + + /* We're assuming the same layout for keyUp and keyDown event data. + Let's make sure they're the same.... */ + XP_ASSERT( OFFSET_OF(EventType, data.keyUp.modifiers) + == OFFSET_OF(EventType, data.keyDown.modifiers) ); + XP_ASSERT( OFFSET_OF(EventType, data.keyUp.keyCode) + == OFFSET_OF(EventType, data.keyDown.keyCode) ); + + if ( !globals->generatesKeyUp ) { + handler = board_handleKey; + } else if ( event->eType == keyUpEvent ) { + handler = board_handleKeyUp; + globals->lastKeyDown = XP_KEY_NONE; + } else if ( (event->data.keyDown.modifiers & autoRepeatKeyMask) != 0 ) { + handler = board_handleKeyRepeat; + } else { + handler = board_handleKeyDown; + XP_ASSERT( globals->lastKeyDown == XP_KEY_NONE ); + globals->lastKeyDown = event->data.keyDown.keyCode; + } + + /* Unlike Treo, zodiac doesn't use keyCode as documented */ + if ( globals->isZodiac ) { + keyCode = event->data.keyDown.chr; + } + + /* Treo gets at least one of these wrong in the chr field, but puts the + right value in the keyCode. So use that. On other platforms must set + it first. */ + switch ( keyCode ) { +#ifdef XWFEATURE_FIVEWAY + case vchrRockerCenter: + xpkey = XP_RETURN_KEY; + break; + case vchrRockerLeft: + xpkey = altOn ? XP_CURSOR_KEY_ALTLEFT : XP_CURSOR_KEY_LEFT; + incr = -1; + break; + case vchrRockerRight: + xpkey = altOn ? XP_CURSOR_KEY_ALTRIGHT : XP_CURSOR_KEY_RIGHT; + incr = 1; + break; + case vchrRockerUp: + xpkey = altOn ? XP_CURSOR_KEY_ALTUP : XP_CURSOR_KEY_UP; + incr = -1; + break; + case vchrRockerDown: + xpkey = altOn ? XP_CURSOR_KEY_ALTDOWN : XP_CURSOR_KEY_DOWN; + incr = 1; + break; + case chrSpace: + xpkey = XP_RAISEFOCUS_KEY; + break; +#endif + default: + /* Zodiac doesn't send keyUp events for printing chars, which somehow + includes backspace */ + if ( globals->isZodiac ) { + handler = board_handleKey; + } + + xpkey = XP_KEY_NONE; + chr = event->data.keyUp.chr; + /* I'm not interested in being dependent on a particular version + of the OS, (can't manage to link against the intl library + anyway) and so don't want to use the 3.5-only text tests. So + let's give the board two shots at each char, one lower case + and another upper. */ + if ( !!handler && (chr < 255) && (chr > ' ') ) { /* space is first + printing char */ + draw = (*handler)( board, chr, &handled ); + if ( !handled && chr >= 'a' ) { + draw = (*handler)( board, chr - ('a' - 'A'), &handled ); + } + } else { + switch ( chr ) { + case pageUpChr: + draw = treatAsUp && scrollBoard( globals, 0, false ); + break; + case pageDownChr: + draw = treatAsUp && scrollBoard( globals, 2, false ); + break; + case backspaceChr: + xpkey = XP_CURSOR_KEY_DEL; + break; + case chrSpace: + xpkey = XP_RAISEFOCUS_KEY; + break; + } + } + } + + if ( xpkey != XP_KEY_NONE ) { + XP_ASSERT( !!handler ); + draw = (*handler)( board, xpkey, &handled ); + + if ( 0 ) { +#ifdef DO_TUNGSTEN_FIVEWAY + /* If it's a tungsten or zodiac, there's no built-in focus xfer + so we do it here. Don't do it for Treo, and don't do it on + key-down for zodiac since it has keyUp too. */ + } else if ( !globals->hasTreoFiveWay && treatAsUp + && !handled && (incr != 0) ) { + /* order'll be different if scoreboard is vertical */ + BoardObjectType typs[] = { OBJ_SCORE, OBJ_BOARD, OBJ_TRAY }; + BoardObjectType nxt = board_getFocusOwner( board ); + XP_U16 indx = 0; + if ( nxt != OBJ_NONE ) { + for ( ; indx < VSIZE(typs); ++indx ){ + if ( nxt == typs[indx] ) { + indx = (indx + (VSIZE(typs) + incr)); + indx %= VSIZE(typs); + break; + } + } + } + draw = board_focusChanged( board, typs[indx], XP_TRUE ) || draw; +#endif + } else if ( draw && !handled ) { + /* If handled comes back false yet something changed (draw), + we'll be getting another event shortly. Put the draw off + until then so we don't flash the tray focussed then not. This + is a hack, but I can't think of a way to integrate it into + board.c logic without making too many palm-centric assumptions + there. */ + draw = XP_FALSE; + } + } else { + /* remove this and break focus drilldown. Why? */ + handled = draw; + } + *handledP = handled; + + globals->handlingKeyEvent = XP_FALSE; + + return draw; +} /* handleKeyEvent */ + +static void +showRemaining( PalmAppGlobals* globals ) +{ + if ( !!globals->game.board ) { + XWStreamCtxt* stream = makeSimpleStream( globals, NULL ); + board_formatRemainingTiles( globals->game.board, stream ); + (void)askFromStream( globals, stream, STR_REMAINS_TITLE, true ); + } +} + +/***************************************************************************** + * + ****************************************************************************/ +static Boolean +mainViewHandleEvent( EventPtr event ) +{ + XP_Bool handled = XP_TRUE; + XP_Bool draw = XP_FALSE; + Boolean erase; +#if defined CURSOR_MOVEMENT && defined DEBUG + CursorDirection cursorDir; + Boolean movePiece; +#endif + PalmAppGlobals* globals; + OpenSavedGameData* savedGameData; + char newName[MAX_GAMENAME_LENGTH]; + XP_U16 prevSize; + XWStreamCtxt* stream; + + CALLBACK_PROLOGUE(); + + globals = getFormRefcon(); + +/* XP_LOGF( "%s(%s)", __func__, eType_2str(event->eType) ); */ + + switch ( event->eType ) { + + case nilEvent: + draw = handled = handleNilEvent( globals ); + break; + + + case noopEvent: + /* do nothing! Exists just to force EvtGetEvent to return */ + XP_ASSERT( handled ); + break; + + case newGameCancelEvent: + /* If user cancelled the new game dialog that came up the first time + he launched (i.e. when there's no game to fall back to) then just + quit. It's easier than dealing with everything that can go wrong + in this state. */ + if ( globals->isFirstLaunch ) { + postEmptyEvent( appStopEvent ); + } + globals->isNewGame = false; + break; + + case openSavedGameEvent: + globals->postponeDraw = XP_FALSE; + prevSize = globals->gameInfo.boardSize; + savedGameData = (OpenSavedGameData*)&event->data.generic; + + if ( tryLoadSavedGame( globals, savedGameData->newGameIndex ) ) { + if ( prevSize > globals->gameInfo.boardSize ) { + WinEraseWindow(); + } + initAndStartBoard( globals, XP_FALSE ); + } + draw = true; + break; + + case newGameOkEvent: + if ( globals->newGameIsNew ) { + globals->gState.curGameIndex = countGameRecords( globals ); + } + globals->postponeDraw = false; + makeDefaultGameName( newName ); + writeNameToGameRecord( globals, globals->gState.curGameIndex, + newName, XP_STRLEN(newName) ); + globals->isFirstLaunch = false; /* so we'll save the game */ + + case loadGameEvent: + XP_ASSERT( !!globals->game.server ); + initAndStartBoard( globals, event->eType == newGameOkEvent ); + draw = true; + XP_ASSERT( !!globals->game.board ); + break; + +#ifdef XWFEATURE_BLUETOOTH + case closeBtLibEvent: + palm_bt_close( globals ); + break; +#endif + +#ifdef FEATURE_SILK + case doResizeWinEvent: + getSizes( globals ); + positionBoard( globals ); + board_invalAll( globals->game.board ); + FrmUpdateForm( 0, frmRedrawUpdateCode ); + break; +#endif + + case prefsChangedEvent: + erase = LocalPrefsToGlobal( globals ); + draw = board_prefsChanged( globals->game.board, &globals->gState.cp ); + server_prefsChanged( globals->game.server, &globals->gState.cp ); + /* watch out for short-circuiting. Both must be called */ + erase = positionBoard( globals ) || erase; + if ( erase ) { + WinEraseWindow(); + } + globals->postponeDraw = false; + FrmUpdateForm( 0, frmRedrawUpdateCode ); /* <- why is this necessary? */ + break; + +#ifdef XWFEATURE_FIVEWAY + case updateAfterFocusEvent: + invalRectAroundButton( globals, event->data.generic.datum[0] ); + draw = XP_TRUE; + break; +#endif + + case winExitEvent: + if ( event->data.winExit.exitWindow == (WinHandle)FrmGetActiveForm() ){ + globals->menuIsDown = true; + } + if ( globals->lastKeyDown != XP_KEY_NONE ) { + EventType event; + XP_Bool ignore; + + event.eType = keyUpEvent; + event.data.keyUp.chr = event.data.keyUp.keyCode + = globals->lastKeyDown; + draw = handleKeyEvent( globals, &event, &ignore ); + } + break; + + case winEnterEvent: + // From PalmOS's "Knowledge base": In the current code, the menu + // doesn't remove itself when it receives a winExitEvent so we need + // an extra check to make sure that the window being entered is the + // first form. This may be different in your implementation (ie: if + // the first form opened is not the one you are currently watching + // for) + if (event->data.winEnter.enterWindow == (WinHandle)FrmGetActiveForm() && + event->data.winEnter.enterWindow == (WinHandle)FrmGetFirstForm() ){ + globals->menuIsDown = false; + } + break; + + case frmOpenEvent: + globals->mainForm = FrmGetActiveForm(); + locateTrayButtons( globals ); + updateForLefty( globals, globals->mainForm ); + FrmDrawForm( globals->mainForm ); + break; + + case frmUpdateEvent: + FrmDrawForm( globals->mainForm ); /* on 3.5 and higher, this erases + the window before drawing, so + there's nothing to be done about + the erase after user clicks OK + in prefs dialog. */ + if ( !!globals->game.board ) { + RectangleType clip; + WinGetClip( &clip ); + + drawFormButtons( globals ); + hresRect( globals, &clip ); + board_invalRect( globals->game.board, (XP_Rect*)&clip ); + draw = !globals->postponeDraw; + } + break; + + case penDownEvent: + draw = board_handlePenDown( globals->game.board, + hresX(globals, event->screenX), + hresY(globals, event->screenY), + &handled ); + globals->penDown = handled; + break; + + case penMoveEvent: + if ( globals->penDown ) { + handled = board_handlePenMove( globals->game.board, + hresX( globals, event->screenX ), + hresY( globals, event->screenY )); + draw = handled; + } + break; + + case penUpEvent: + if ( globals->penDown ) { + draw = board_handlePenUp( globals->game.board, + hresX( globals, event->screenX), + hresY( globals, event->screenY ) ); + handled = draw; /* this is wrong!!!! */ + globals->penDown = false; + + if ( !handled ) { + handled = considerMenuShow( event ); + } + } + break; + + case menuEvent: + MenuEraseStatus(0); + switch ( event->data.menu.itemID ) { + + case XW_TILEVALUES_PULLDOWN_ID: + if ( !!globals->game.server ) { + stream = makeSimpleStream( globals, NULL ); + + server_formatDictCounts( globals->game.server, stream, + 4 ); /* 4: ncols */ + + (void)askFromStream( globals, stream, STR_VALUES_TITLE, true ); + } + break; + + case XW_TILESLEFT_PULLDOWN_ID: + showRemaining( globals ); + break; + + case XW_HISTORY_PULLDOWN_ID: + if ( !!globals->game.server ) { + XP_Bool gameOver = server_getGameIsOver(globals->game.server); + stream = makeSimpleStream( globals, NULL ); + + model_writeGameHistory( globals->game.model, stream, + globals->game.server, gameOver ); + if ( stream_getSize( stream ) > 0 ) { + (void)askFromStream( globals, stream, STR_HISTORY_TITLE, + XP_FALSE ); + } else { + beep(); + } + stream_destroy( stream ); + } + break; + + case XW_NEWGAME_PULLDOWN_ID: + askStartNewGame( globals ); + break; + + case XW_SAVEDGAMES_PULLDOWN_ID: + saveOpenGame( globals );/* so it can be accurately duped */ + /* save game changes state; reflect on screen before + popping up dialog */ + drawChangedBoard( globals ); + FrmPopupForm( XW_SAVEDGAMES_DIALOG_ID ); + break; + + case XW_FINISH_PULLDOWN_ID: + if ( server_getGameIsOver( globals->game.server ) ) { + displayFinalScores( globals ); + } else if ( palmaskFromStrId( globals, STR_CONFIRM_END_GAME, -1 ) ) { + server_endGame( globals->game.server ); + draw = true; + } + break; + +#ifndef XWFEATURE_STANDALONE_ONLY + /* Would be better to beep when no remote players.... */ + case XW_RESENDIR_PULLDOWN_ID: + if ( !!globals->game.comms ) { +#ifdef XWFEATURE_BLUETOOTH + globals->suspendBT = XP_FALSE; +#endif + (void)comms_resendAll( globals->game.comms ); + } else { + userErrorFromStrId( globals, STR_RESEND_STANDALONE ); + } + break; +#endif + case XW_BEAMDICT_PULLDOWN_ID: + globals->dictuiForBeaming = true; + FrmPopupForm( XW_DICTINFO_FORM ); + break; + + case XW_BEAMBOARD_PULLDOWN_ID: + beamBoard( globals ); + break; + +#ifdef FEATURE_DUALCHOOSE + /* This probably goes away at ship.... */ + case XW_RUN68K_PULLDOWN_ID: + case XW_RUNARM_PULLDOWN_ID: { + Err err; + LocalID dbID; + + (void)FtrUnregister( APPID, FEATURE_WANTS_68K ); + err = FtrSet( APPID, FEATURE_WANTS_68K, + event->data.menu.itemID == XW_RUN68K_PULLDOWN_ID? + WANTS_68K : WANTS_ARM ); + + dbID = DmFindDatabase( CARD_0, APPNAME ); + if ( dbID != 0 ) { + (void)SysUIAppSwitch( 0, dbID, + sysAppLaunchCmdNormalLaunch, NULL ); + } + } + break; +#endif + + case XW_PASSWORDS_PULLDOWN_ID: + globals->isNewGame = false; + FrmPopupForm( XW_NEWGAMES_FORM ); + break; + +#ifdef COLOR_EDIT + case XW_EDITCOLORS_PULLDOWN_ID: + if ( globals->able == COLOR ) { + FrmPopupForm( XW_COLORPREF_DIALOG_ID ); + } + break; +# ifdef DEBUG + case XW_DUMPCOLORS_PULLDOWN_ID: + dumpColors( globals ); + break; +# endif +#endif + + case XW_PREFS_PULLDOWN_ID: + globals->stateTypeIsGlobal = XP_TRUE; + GlobalPrefsToLocal( globals ); + FrmPopupForm( XW_PREFS_FORM ); + break; + + case XW_ABOUT_PULLDOWN_ID: + palmaskFromStrId( globals, STR_ABOUT_CONTENT, STR_ABOUT_TITLE ); + break; + + case XW_HINT_PULLDOWN_ID: + board_resetEngine( globals->game.board ); + globals->askTrayLimits = XP_FALSE; + + case XW_NEXTHINT_PULLDOWN_ID: + draw = handleHintRequest( globals ); + break; + +#ifdef XWFEATURE_SEARCHLIMIT + case XW_HINTCONFIG_PULLDOWN_ID: + board_resetEngine( globals->game.board ); + globals->askTrayLimits = XP_TRUE; + draw = handleHintRequest( globals ); + break; +#endif + + case XW_UNDOCUR_PULLDOWN_ID: + draw = board_replaceTiles( globals->game.board ); + break; + + case XW_UNDOLAST_PULLDOWN_ID: + draw = server_handleUndo( globals->game.server ); + break; + + case XW_DONE_PULLDOWN_ID: + draw = handleDone( globals ); + break; + + case XW_JUGGLE_PULLDOWN_ID: + draw = handleJuggle( globals ); + break; + + case XW_TRADEIN_PULLDOWN_ID: + draw = handleTrade( globals ); + break; + + case XW_HIDESHOWTRAY_PULLDOWN_ID: + draw = handleHideTray( globals ); + break; + +#ifdef FOR_GREMLINS + case XW_GREMLIN_DIVIDER_RIGHT: + if ( !!globals->game.board ) { + board_moveDivider( globals->game.board, XP_TRUE ); + draw = XP_TRUE; + } + break; + case XW_GREMLIN_DIVIDER_LEFT: + if ( !!globals->game.board ) { + board_moveDivider( globals->game.board, XP_FALSE ); + draw = XP_TRUE; + } + break; +#endif + +#ifdef DEBUG + case XW_LOGFILE_PULLDOWN_ID: + toggleBoolFtr( LOG_FILE_FEATURE ); + break; + case XW_LOGMEMO_PULLDOWN_ID: + toggleBoolFtr( LOG_MEMO_FEATURE ); + break; + + case XW_CLEARLOGS_PULLDOWN_ID: + PalmClearLogs(); + break; +# if 0 + case XW_RESET_PULLDOWN_ID: { + postEmptyEvent( appStopEvent ); + } + + globals->resetGame = true; + break; +# endif + + case XW_NETSTATS_PULLDOWN_ID: + if ( !!globals->game.comms ) { + stream = makeSimpleStream( globals, askOnClose ); + comms_getStats( globals->game.comms, stream ); + stream_destroy( stream ); + } + break; +#if defined XWFEATURE_BLUETOOTH && defined DEBUG + case XW_BTSTATS_PULLDOWN_ID: + stream = makeSimpleStream( globals, askOnClose ); + palm_bt_getStats( globals, stream ); + stream_destroy( stream ); + break; +#endif + +#ifdef MEM_DEBUG + case XW_MEMSTATS_PULLDOWN_ID : + if ( !!globals->mpool ) { + stream = makeSimpleStream( globals, askOnClose ); + mpool_stats( globals->mpool, stream ); + stream_destroy( stream ); + } + break; +#endif + +#endif + + default: + break; + } + break; + +#ifdef XWFEATURE_FIVEWAY + case frmObjectFocusTakeEvent: + case frmObjectFocusLostEvent: + handled = globals->hasTreoFiveWay + && handleFocusEvent( globals, event, &draw ); + break; +#endif + + case keyUpEvent: + XP_ASSERT( globals->generatesKeyUp ); + /* work around not yet being able to set generatesKeyUp accurately + using FtrGet */ + if ( !globals->generatesKeyUp ) { + globals->generatesKeyUp = XP_TRUE; + globals->keyDownReceived = XP_FALSE; /* drop the event this once */ + } else if ( globals->keyDownReceived ) { + globals->keyDownReceived = XP_FALSE; + draw = handleKeyEvent( globals, event, &handled ); + } + break; + case keyDownEvent: + if ( !globals->menuIsDown ) { + globals->keyDownReceived = XP_TRUE; + draw = handleKeyEvent( globals, event, &handled ); + } + break; + + case sclRepeatEvent: + draw = scrollBoard( globals, event->data.sclRepeat.newValue-SBAR_MIN, + true ); + handled = false; + break; + + case ctlSelectEvent: + handled = true; + switch ( event->data.ctlEnter.controlID ) { + case XW_MAIN_FLIP_BUTTON_ID: + draw = handleFlip( globals ); + break; + case XW_MAIN_VALUE_BUTTON_ID: + draw = handleValueToggle( globals ); + break; + case XW_MAIN_HINT_BUTTON_ID: + draw = handleHintRequest( globals ); + break; +#ifndef EIGHT_TILES + case XW_MAIN_DONE_BUTTON_ID: + draw = handleDone( globals ); + break; + case XW_MAIN_JUGGLE_BUTTON_ID: + draw = handleJuggle( globals ); + break; + case XW_MAIN_TRADE_BUTTON_ID: + draw = handleTrade( globals ); + break; + case XW_MAIN_HIDE_BUTTON_ID: + draw = handleHideTray( globals ); + break; +#endif + case XW_MAIN_SHOWTRAY_BUTTON_ID: + draw = board_showTray( globals->game.board ); + break; + + default: + handled = false; + break; + } /* switch event->data.ctlEnter.controlID */ + + default: + handled = false; + break; + } + + if ( draw && !!globals->game.board && !globals->menuIsDown ) { + XP_Bool drewAll = board_draw( globals->game.board ); + if ( !drewAll ) { + globals->msgReceivedDraw = XP_TRUE; + palm_util_requestTime( &globals->util ); + } + } + + CALLBACK_EPILOGUE(); + return handled; +} /* mainViewHandleEvent */ + +static void +askStartNewGame( PalmAppGlobals* globals ) +{ + if ( palmaskFromStrId( globals, STR_ASK_REPLACE_GAME, -1 )) { + /* do nothing; popping up the NEWGAMES dlg will do it -- if not + cancelled */ + globals->newGameIsNew = XP_FALSE; + } else { + saveOpenGame( globals ); + + drawChangedBoard( globals ); + + globals->newGameIsNew = XP_TRUE; + } + globals->isNewGame = true; + globals->isFirstLaunch = false; + FrmPopupForm( XW_NEWGAMES_FORM ); +} /* askStartNewGame */ + +static void +displayFinalScores( PalmAppGlobals* globals ) +{ + XWStreamCtxt* stream; + + stream = makeSimpleStream( globals, NULL ); + server_writeFinalScores( globals->game.server, stream ); + stream_putU8( stream, '\0' ); + + (void)askFromStream( globals, stream, STR_FINAL_SCORES_TITLE, true ); +} /* displayFinalScores */ + +XP_S16 +palm_memcmp( XP_U8* p1, XP_U8* p2, XP_U16 nBytes ) +{ + /* man memcmp: The memcmp() function compares the first n bytes of the + memory areas s1 and s2. It returns an integer less than, equal to, or + greater than zero if s1 is found, respectively, to be less than, to + match, or be greater than s2.*/ + XP_S16 result = 0; + while ( result == 0 && nBytes-- ) { + result = *p1++ - *p2++; + } + return result; +} /* palm_memcmp */ + +static void +askScrollbarAdjust( PalmAppGlobals* globals, FieldPtr field ) +{ + UInt16 scrollPos; + UInt16 textHeight; + UInt16 fieldHeight; + UInt16 maxValue; + ScrollBarPtr scroll; + + FldGetScrollValues( field, &scrollPos, &textHeight, &fieldHeight ); + + if ( textHeight > fieldHeight ) { + maxValue = textHeight - fieldHeight; + } else if ( scrollPos != 0 ) { + maxValue = scrollPos; + } else { + maxValue = 0; + } + + scroll = getActiveObjectPtr( XW_ASK_SCROLLBAR_ID ); + SclSetScrollBar( scroll, scrollPos, 0, maxValue, fieldHeight-1 ); + globals->prevScroll = scrollPos; +} /* askScrollbarAdjust */ + +static void +getWindowBounds( WinHandle wHand, RectangleType* r ) +{ + WinHandle prev = WinSetDrawWindow( wHand ); + WinGetDrawWindowBounds( r ); + (void)WinSetDrawWindow( prev ); +} /* getWindowBounds */ + +static void +tryGrowAskToFit( FormPtr form, FieldPtr field, const XP_UCHAR* str ) +{ + RectangleType fieldRect, dlgRect; + UInt16 scrollPos; + UInt16 textHeight; + UInt16 fieldHeight; + WinHandle wHand; + XP_S16 fldWidth, fldHeight, maxHeight, needsHeight; + XP_U16 lineHeight; + XP_U16 growthAmt, i; + RectangleType objBounds; + + FldGetScrollValues( field, &scrollPos, &textHeight, &fieldHeight ); + + getObjectBounds( XW_ASK_TXT_FIELD_ID, &fieldRect ); + fldWidth = fieldRect.extent.x; + fldHeight = fieldRect.extent.y; + + /* max is cur height plus diff between dialog's height and what it could + be */ + wHand = FrmGetWindowHandle( form ); + getWindowBounds( wHand, &dlgRect ); + maxHeight = fldHeight + (156 - dlgRect.extent.y); + + lineHeight = FntLineHeight(); + + needsHeight = FldCalcFieldHeight((const char*)str, fldWidth) * lineHeight; + + if ( needsHeight > maxHeight ) { + /* make window as large as it can be */ + needsHeight = maxHeight; + } + + /* now round down to a multiple of lineHeight */ + needsHeight = (needsHeight / lineHeight) * lineHeight; + + growthAmt = needsHeight - fldHeight; + + /* now reflect the new size by moving things around. Window first */ + dlgRect.topLeft.y -= growthAmt; + dlgRect.extent.y += growthAmt; + WinSetBounds( wHand, &dlgRect ); + + /* then the field */ + fieldRect.extent.y += growthAmt; + setObjectBounds( XW_ASK_TXT_FIELD_ID, &fieldRect ); + + /* the scrollbar */ + getObjectBounds( XW_ASK_SCROLLBAR_ID, &objBounds ); + objBounds.extent.y = fieldRect.extent.y; + setObjectBounds( XW_ASK_SCROLLBAR_ID, &objBounds ); + + /* and the buttons */ + XP_ASSERT( XW_ASK_NO_BUTTON_ID - XW_ASK_YES_BUTTON_ID == 1 ); + for ( i = XW_ASK_YES_BUTTON_ID; i <= XW_ASK_NO_BUTTON_ID; ++i ){ + getObjectBounds( i, &objBounds ); + objBounds.topLeft.y += growthAmt; + setObjectBounds( i, &objBounds ); + } +} /* tryGrowAskToFit */ + +static Boolean +handleScrollInAsk( EventPtr event ) +{ + UInt16 linesToScroll = 0; + Boolean scrollFromButton = false; + Boolean result = true; + WinDirectionType direction = 5; + PalmAppGlobals* globals = (PalmAppGlobals*)getFormRefcon(); + FieldPtr field; + UInt16 endPosition; + + XP_ASSERT ( !!globals ); + + field = getActiveObjectPtr( XW_ASK_TXT_FIELD_ID ); + + switch ( event->eType ) { + + case penUpEvent: + /* When user drags pen through text and causes a scroll the scrollbar + will get out of sync. So we listen to the event but don't claim to + have handled it. */ + askScrollbarAdjust( globals, field ); + result = false; + break; + + case keyDownEvent: + if ( globals->ignoreFirstKeyDown ) { + globals->ignoreFirstKeyDown = XP_FALSE; + XP_ASSERT( result ); + } else if ( FrmGetWindowHandle( FrmGetActiveForm() ) + == WinGetDrawWindow() ) { + /* don't scroll a menu if open! */ + switch ( event->data.keyDown.chr ) { + case pageUpChr: + case vchrRockerUp: + direction = winUp; + break; + case pageDownChr: + case vchrRockerDown: + direction = winDown; + break; + default: + result = false; + } + linesToScroll = 3; + scrollFromButton = true; + } + break; + + case sclRepeatEvent: { + XP_S16 newVal = event->data.sclRepeat.newValue; + XP_S16 tmp = newVal - globals->prevScroll; + linesToScroll = XP_ABS( tmp ); + XP_ASSERT( linesToScroll != 0 ); + direction = newVal > globals->prevScroll? winDown: winUp; + globals->prevScroll = newVal; + scrollFromButton = false; + } + break; + + case menuEvent: + MenuEraseStatus(0); + result = true; + switch ( event->data.menu.itemID ) { + case ASK_COPY_PULLDOWN_ID: + FldCopy( field ); + break; + case ASK_SELECTALL_PULLDOWN_ID: + endPosition = FldGetTextLength( field ); + FldSetSelection( field, 0, endPosition ); + break; + } + break; + + default: + result = false; + } + + if ( result && FldScrollable( field, direction ) ) { + FldScrollField( field, linesToScroll, direction ); + if ( scrollFromButton ) { + askScrollbarAdjust( globals, field ); + } else { + result = false; /* for some reason this is necessary to make + scrolbar work right. */ + } + } + + return result; +} /* handleScrollInAsk */ + +/* Swap the two elements, preserving their outside borders */ +static void +moveLeftOf( UInt16 rightID, UInt16 leftID ) +{ + UInt16 leftIndex, rightIndex; + UInt16 middleMargin; + RectangleType leftBounds, rightBounds; + FormPtr form = FrmGetActiveForm(); + + leftIndex = FrmGetObjectIndex( form, leftID ); + rightIndex = FrmGetObjectIndex( form, rightID ); + + FrmGetObjectBounds( form, rightIndex, &rightBounds ); + FrmGetObjectBounds( form, leftIndex, &leftBounds ); + + XP_ASSERT( rightBounds.topLeft.y == leftBounds.topLeft.y ); + + middleMargin = rightBounds.topLeft.x - + (leftBounds.topLeft.x+leftBounds.extent.x); + + FrmSetObjectPosition( form, rightIndex, leftBounds.topLeft.x, + leftBounds.topLeft.y ); + + FrmSetObjectPosition( + form, leftIndex, + leftBounds.topLeft.x + rightBounds.extent.x + middleMargin, + leftBounds.topLeft.y ); + +} /* moveLeftOf */ + +XP_Bool +palmaskFromStrId( PalmAppGlobals* globals, XP_U16 strId, XP_S16 titleID ) +{ + const XP_UCHAR* message; + const XP_UCHAR* yes; + message = getResString( globals, strId ); + XP_ASSERT( !!message ); + yes = titleID < 0? NULL: getResString( globals, STR_OK ); + return palmask( globals, message, yes, titleID ); +} /* palmaskFromStrId */ + +XP_Bool +palmask( PalmAppGlobals* globals, const XP_UCHAR* str, + const XP_UCHAR* yesButton, XP_S16 titleID ) +{ + FormPtr form, prevForm; + FieldPtr field; + const XP_UCHAR* title; + UInt16 buttonHit; + XP_U16 buttons[] = { XW_ASK_YES_BUTTON_ID, XW_ASK_NO_BUTTON_ID }; + XP_U16 nButtons; + + if ( !!globals->game.board ) { + board_pushTimerSave( globals->game.board ); + } + + title = titleID >= 0? getResString( globals, titleID ): NULL; + + prevForm = FrmGetActiveForm(); + form = FrmInitForm( XW_ASK_FORM_ID ); + + FrmSetActiveForm( form ); + + if ( !!yesButton ) { + CtlSetLabel( getActiveObjectPtr(XW_ASK_YES_BUTTON_ID), + (const char*)yesButton ); + fitButtonToString( XW_ASK_YES_BUTTON_ID ); + } + + /* Hack: take advantage of fact that for now only non-queries should not + have a cancel button. */ + if ( title == NULL ) { + nButtons = 2; + } else { + FrmSetTitle( form, (char*)title ); + disOrEnable( form, XW_ASK_NO_BUTTON_ID, false ); + nButtons = 1; + } + /* always center: some localized buttons are bigger */ + centerControls( form, buttons, nButtons ); + + /* If we're running OS5 (oneDotFiveAvail), then eat the first keyDown. + If an earlier OS (Treo600) then we won't see that spurious event. */ + if ( globals->handlingKeyEvent && globals->oneDotFiveAvail ) { + globals->ignoreFirstKeyDown = XP_TRUE; + } + + FrmSetEventHandler( form, handleScrollInAsk ); + + globals->prevScroll = 0; + + if ( globals->isLefty ) { + moveLeftOf( XW_ASK_SCROLLBAR_ID, XW_ASK_TXT_FIELD_ID ); + } + + field = getActiveObjectPtr( XW_ASK_TXT_FIELD_ID ); + FldSetTextPtr( field, (char*)str ); + + FldRecalculateField( field, true ); + + if ( globals->romVersion >= 35 ) { + /* I'm not sure how to do this pre 3.5 */ + tryGrowAskToFit( form, field, str ); + } + + askScrollbarAdjust( globals, field ); + + FrmDrawForm( form ); + + buttonHit = FrmDoDialog( form ); + + FrmDeleteForm( form ); + FrmSetActiveForm( prevForm ); + + if ( !!globals->game.board ) { + board_popTimerSave( globals->game.board ); + } + + return buttonHit == XW_ASK_YES_BUTTON_ID; +} /* palmask */ + +static XP_Bool +askFromStream( PalmAppGlobals* globals, XWStreamCtxt* stream, XP_S16 titleID, + Boolean closeAndDestroy ) +{ + XP_U16 nBytes = stream_getSize( stream ); + XP_Bool result; + XP_UCHAR* buffer; + + XP_ASSERT( nBytes < maxFieldTextLen ); + + buffer = XP_MALLOC( globals->mpool, nBytes + 1 ); + stream_getBytes( stream, buffer, nBytes ); + /* nuke trailing chars to they don't extend length of field */ + while ( buffer[nBytes-1] == '\n' ) { + --nBytes; + } + buffer[nBytes] = '\0'; /* just to be safe */ + + result = palmask( globals, buffer, + getResString( globals, STR_OK ), titleID ); + + XP_FREE( globals->mpool, buffer ); + + if ( closeAndDestroy ) { + stream_destroy( stream ); + } + + return result; +} /* askFromStream */ + +XP_Bool +askPassword( PalmAppGlobals* globals, const XP_UCHAR* name, XP_Bool isNew, + XP_UCHAR* retbuf, XP_U16* len ) +{ + XP_Bool result = XP_FALSE; + FormPtr prevForm, form; + FieldPtr field; + UInt16 showMe; + + prevForm = FrmGetActiveForm(); + form = FrmInitForm( XW_PASSWORD_DIALOG_ID ); + FrmSetActiveForm( form ); + + if ( isNew ) { + showMe = XW_PASSWORD_NEWNAME_LABEL; + } else { + showMe = XW_PASSWORD_NAME_LABEL; + } + FrmShowObject( form, FrmGetObjectIndex( form, showMe ) ); + + FrmDrawForm( form ); + + if ( !!name ) { + field = getActiveObjectPtr( XW_PASSWORD_NAME_FIELD ); + FldSetTextPtr( field, (char*)name ); + FldDrawField( field ); + } + + if ( !globals->hasTreoFiveWay ) { + FrmSetFocus( form, FrmGetObjectIndex( form, XW_PASSWORD_PASS_FIELD ) ); + } + + if ( FrmDoDialog( form ) == XW_PASSWORD_OK_BUTTON ) { + char* enteredPass; + XP_U16 enteredLen; + field = getActiveObjectPtr( XW_PASSWORD_PASS_FIELD ); + enteredPass = FldGetTextPtr( field ); + enteredLen = enteredPass? StrLen(enteredPass) : 0; + if ( enteredLen < *len ) { + result = XP_TRUE; + if ( enteredLen > 0 ) { + XP_MEMCPY( retbuf, enteredPass, enteredLen ); + } + retbuf[enteredLen] = '\0'; + *len = enteredLen; + } + } + + FrmDeleteForm( form ); + FrmSetActiveForm( prevForm ); + + return result; +} /* askPassword */ + +static Boolean +handleKeysInBlank( EventPtr event ) +{ + Boolean handled = false; + + if ( event->eType == keyDownEvent ) { + char ch = event->data.keyDown.chr; + + if ( ch >= 'a' && ch <= 'z' ) { + ch += 'A' - 'a'; + } + if ( ch >= 'A' && ch <= 'Z' ) { + ListPtr lettersList = getActiveObjectPtr( XW_BLANK_LIST_ID ); + XP_U16 nItems; + XP_U16 i; + + XP_ASSERT( !!lettersList ); + nItems = LstGetNumberOfItems( lettersList ); + + for ( i = 0; i < nItems; ++i ) { + XP_UCHAR* itext = LstGetSelectionText( lettersList, i ); + + if ( !!itext && (itext[0] == ch) ) { + LstSetSelection( lettersList, i ); + LstMakeItemVisible( lettersList, i ); + handled = true; + break; + } + } + } else if ( ch == '\n' ) { + EventType eventToPost; + + eventToPost.eType = ctlSelectEvent; + eventToPost.data.ctlSelect.controlID = XW_BLANK_OK_BUTTON_ID; + eventToPost.data.ctlSelect.pControl = + getActiveObjectPtr( XW_BLANK_OK_BUTTON_ID ); + EvtAddEventToQueue( &eventToPost ); + } + } + + return handled; +} /* handleKeysInBlank */ + +static XP_S16 +askBlankValue( PalmAppGlobals* globals, XP_U16 playerNum, const PickInfo* pi, + XP_U16 nTiles, const XP_UCHAR4* texts ) +{ + FormPtr form, prevForm; + ListPtr lettersList; + ListData ld; + XP_U16 i; + XP_S16 chosen; + XP_UCHAR labelBuf[96]; + XP_UCHAR* name; + const XP_UCHAR* labelFmt; + FieldPtr fld; + XP_U16 tapped; +#ifdef FEATURE_TRAY_EDIT + XP_Bool forBlank = pi->why == PICK_FOR_BLANK; +#endif + + initListData( MEMPOOL &ld, nTiles ); + + for ( i = 0; i < nTiles; ++i ) { + addListTextItem( MEMPOOL &ld, (XP_UCHAR*)texts[i] ); + } + + prevForm = FrmGetActiveForm(); + form = FrmInitForm( XW_BLANK_DIALOG_ID ); + FrmSetActiveForm( form ); + +#ifdef FEATURE_TRAY_EDIT + disOrEnable( form, XW_BLANK_PICK_BUTTON_ID, !forBlank ); + disOrEnable( form, XW_BLANK_BACKUP_BUTTON_ID, + !forBlank && pi->thisPick > 0 ); +#endif + + lettersList = getActiveObjectPtr( XW_BLANK_LIST_ID ); + setListChoices( &ld, lettersList, NULL ); + + LstSetSelection( lettersList, 0 ); + + name = globals->gameInfo.players[playerNum].name; + labelFmt = getResString( globals, +#ifdef FEATURE_TRAY_EDIT + !forBlank? STRS_PICK_TILE: +#endif + STR_PICK_BLANK ); + XP_SNPRINTF( labelBuf, sizeof(labelBuf), labelFmt, name ); + +#ifdef FEATURE_TRAY_EDIT + if ( !forBlank ) { + const char* cur = getResString( globals, STR_PICK_TILE_CUR ); + XP_U16 lenSoFar; + XP_U16 i; + + lenSoFar = XP_STRLEN(labelBuf); + lenSoFar += XP_SNPRINTF( labelBuf + lenSoFar, + sizeof(labelBuf) - lenSoFar, + " (%d/%d)\n%s", pi->thisPick+1, pi->nTotal, + cur ); + + for ( i = 0; i < pi->nCurTiles; ++i ) { + lenSoFar += XP_SNPRINTF( labelBuf+lenSoFar, + sizeof(labelBuf)-lenSoFar, "%s%s", + i==0?": ":", ", pi->curTiles[i] ); + } + } +#endif + + fld = getActiveObjectPtr( XW_BLANK_LABEL_FIELD_ID ); + FldSetTextPtr( fld, labelBuf ); + FldRecalculateField( fld, false ); + + FrmDrawForm( form ); + + FrmSetEventHandler( form, handleKeysInBlank ); + tapped = FrmDoDialog( form ); + + if ( 0 ) { +#ifdef FEATURE_TRAY_EDIT + } else if ( tapped == XW_BLANK_PICK_BUTTON_ID ) { + chosen = PICKER_PICKALL; + } else if ( tapped == XW_BLANK_BACKUP_BUTTON_ID ) { + chosen = PICKER_BACKUP; +#endif + } else { + chosen = LstGetSelection( lettersList ); + } + + FrmDeleteForm( form ); + FrmSetActiveForm( prevForm ); + + freeListData( MEMPOOL &ld ); + + return chosen; +} /* askBlankValue */ + +/***************************************************************************** + * Callbacks + ****************************************************************************/ +static VTableMgr* +palm_util_getVTManager( XW_UtilCtxt* uc ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + return globals->vtMgr; +} /* palm_util_getVTManager */ + +static void +palm_util_userError( XW_UtilCtxt* uc, UtilErrID id ) +{ + PalmAppGlobals* globals; + XP_U16 strID = STR_LAST_STRING; + + switch( id ) { + case ERR_TILES_NOT_IN_LINE: + strID = STR_ALL_IN_LINE_ERR; + break; + case ERR_NO_EMPTIES_IN_TURN: + strID = STR_NO_EMPTIES_ERR; + break; + + case ERR_TWO_TILES_FIRST_MOVE: + strID = STR_FIRST_MOVE_ERR; + break; + case ERR_TILES_MUST_CONTACT: + strID = STR_MUST_CONTACT_ERR; + break; + case ERR_NOT_YOUR_TURN: + strID = STR_NOT_YOUR_TURN; + break; + case ERR_NO_PEEK_ROBOT_TILES: + strID = STR_NO_PEEK_ROBOT_TILES; + break; + +#ifndef XWFEATURE_STANDALONE_ONLY + case ERR_NO_PEEK_REMOTE_TILES: + strID = STR_NO_PEEK_REMOTE_TILES; + break; + case ERR_SERVER_DICT_WINS: + strID = STR_SERVER_DICT_WINS; + break; + case ERR_REG_UNEXPECTED_USER: + strID = STR_REG_UNEXPECTED_USER; + break; + case ERR_REG_SERVER_SANS_REMOTE: + strID = STR_REG_NEED_REMOTE; + break; + case STR_NEED_BT_HOST_ADDR: + strID = STR_REG_BT_NEED_HOST; + break; +#endif + + case ERR_CANT_TRADE_MID_MOVE: + strID = STR_CANT_TRADE_MIDTURN; + break; + + case ERR_TOO_FEW_TILES_LEFT_TO_TRADE: + strID = STR_TOO_FEW_TILES; + break; + + case ERR_CANT_UNDO_TILEASSIGN: + strID = STR_CANT_UNDO_TILEASSIGN; + break; + + case ERR_CANT_HINT_WHILE_DISABLED: + strID = STR_CANT_HINT_WHILE_DISABLED; + break; + +#ifdef XWFEATURE_RELAY + case ERR_RELAY_BASE + XWRELAY_ERROR_TIMEOUT: + strID = STR_RELAY_TIMEOUT; + break; + case ERR_RELAY_BASE + XWRELAY_ERROR_HEART_YOU: + strID = STR_RELAY_GENERIC; + break; + case ERR_RELAY_BASE + XWRELAY_ERROR_HEART_OTHER: + case ERR_RELAY_BASE + XWRELAY_ERROR_LOST_OTHER: + strID = STR_RELAY_LOST_OTHER; + break; +#endif + + default: + XP_DEBUGF( "errcode=%d", id ); + break; + } + + XP_LOGF( "%s(%d)", __func__, strID ); + + XP_ASSERT( strID < STR_LAST_STRING ); + globals = (PalmAppGlobals*)uc->closure; + userErrorFromStrId( globals, strID ); +} /* palm_util_userError */ + +static void +userErrorFromStrId( PalmAppGlobals* globals, XP_U16 strID ) +{ + const XP_UCHAR* message = getResString( globals, strID ); + keySafeCustomAlert( globals, message ); +} /* userErrorFromStrId */ + +static XP_Bool +palm_util_userQuery( XW_UtilCtxt* uc, UtilQueryID id, XWStreamCtxt* stream ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + XP_U16 strID = STR_LAST_STRING; /* error if not changed */ + + switch( id ) { + case QUERY_COMMIT_TURN: + return askFromStream( globals, stream, -1, false ); + break; + case QUERY_COMMIT_TRADE: + strID = STR_CONFIRM_TRADE; + break; + case QUERY_ROBOT_MOVE: + case QUERY_ROBOT_TRADE: + return askFromStream( globals, stream, STR_ROBOT_TITLE, false ); + break; + default: + XP_ASSERT(0); + break; + } + + return (XP_Bool)palmaskFromStrId( globals, strID, -1 ); +} /* palm_util_userQuery */ + +static XWBonusType +palm_util_getSquareBonus( XW_UtilCtxt* uc, const ModelCtxt* model, + XP_U16 col, XP_U16 row ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + XP_U16 nCols, nRows; + XP_U16 midCol, index, resIndex; + XP_UCHAR* bonusResPtr; + XP_U8 value; + + XP_ASSERT( !!model ); + + nCols = model_numCols( model ); + nRows = model_numRows( model ); + midCol = nCols / 2; + resIndex = (PALM_MAX_COLS - nCols) / 2; + bonusResPtr = globals->bonusResPtr[resIndex]; + + if ( col > midCol ) col = nCols - 1 - col; + if ( row > midCol ) row = nRows - 1 - row; + index = (row*(midCol+1)) + col; + + XP_ASSERT( index/2 < MemPtrSize(bonusResPtr) ); + + value = bonusResPtr[index/2]; + if ( index%2 == 0 ) { + value >>= 4; + } + + return value & 0x0F; +} /* palm_util_getSquareBonus */ + +static XP_S16 +palm_util_userPickTile( XW_UtilCtxt* uc, const PickInfo* pi, + XP_U16 playerNum, const XP_UCHAR4* texts, + XP_U16 nTiles ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + return askBlankValue( globals, playerNum, pi, nTiles, texts ); +} /* palm_util_userPickTile */ + +static XP_Bool +palm_util_askPassword( XW_UtilCtxt* uc, const XP_UCHAR* name, + XP_UCHAR* buf, XP_U16* len ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + return askPassword( globals, name, false, buf, len ); +} /* palm_util_askPassword */ + +static void +palm_util_trayHiddenChange( XW_UtilCtxt* uc, + XW_TrayVisState XP_UNUSED(newState), + XP_U16 XP_UNUSED(nVisibleRows) ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + palmSetCtrlsForTray( globals ); + + drawFormButtons( globals ); +} /* palm_util_trayHiddenChange */ + +static void +palm_util_yOffsetChange( XW_UtilCtxt* uc, XP_U16 XP_UNUSED_DBG(oldOffset), + XP_U16 newOffset ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + XP_ASSERT( oldOffset != newOffset ); + updateScrollbar( globals, newOffset ); +} /* palm_util_yOffsetChange */ + +static void +palm_util_notifyGameOver( XW_UtilCtxt* uc ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + board_draw( globals->game.board ); /* refresh scoreboard so it agrees + with dialog */ + displayFinalScores( globals ); +} /* palm_util_notifyGameOver */ + +static XP_Bool +palm_util_hiliteCell( XW_UtilCtxt* uc, XP_U16 col, XP_U16 row ) +{ + /* EvtSysEventAvail, not EvtEventAvail, because the former ignores nil + events, and it appears that when there's an IR connection up the + system floods us with nil events.*/ + XP_Bool eventPending = EvtSysEventAvail( true ); +#ifdef SHOW_PROGRESS + if ( !eventPending ) { + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + if ( globals->progress.curLine >= 0 ) { + board_hiliteCellAt( globals->game.board, col, row ); + } + } +#endif + + return !eventPending; +} /* palm_util_hiliteCell */ + +static XP_Bool +palm_util_engineProgressCallback( XW_UtilCtxt* uc ) +{ +#ifdef SHOW_PROGRESS + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + if ( globals->gState.showProgress && globals->progress.curLine >= 0 ) { + RectangleType rect = globals->progress.boundsRect; + short line; + Boolean draw; + + globals->progress.curLine %= rect.extent.y * 2; + draw = globals->progress.curLine < rect.extent.y; + + line = globals->progress.curLine % (rect.extent.y) + 1; + line = rect.topLeft.y + rect.extent.y - line; + if ( draw ) { + WinDrawLine( rect.topLeft.x, line, + rect.topLeft.x + rect.extent.x - 1, line); + } else { + WinEraseLine( rect.topLeft.x, line, + rect.topLeft.x + rect.extent.x - 1, + line ); + } + ++globals->progress.curLine; + } +#endif + return !EvtSysEventAvail( true ); +} /* palm_util_engineProgressCallback */ + +static void +palm_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why, + XP_U16 secsFromNow, + XWTimerProc proc, void* closure ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + XP_U32 now = TimGetTicks(); + + if ( why == TIMER_PENDOWN ) { + now += PALM_TIMER_DELAY; + } else if ( why == TIMER_TIMERTICK ) { + now += SysTicksPerSecond(); +#if defined RELAY_HEARTBEAT || defined COMMS_HEARTBEAT + } else if ( why == TIMER_HEARTBEAT ) { + now += (secsFromNow * SysTicksPerSecond()); +#endif +#ifdef XWFEATURE_BLUETOOTH + } else if ( why == TIMER_ACL_BACKOFF ) { + now += (secsFromNow * SysTicksPerSecond()); +#endif + } else { + XP_ASSERT( 0 ); + } + + XP_ASSERT( why < VSIZE(globals->timerProcs) ); + globals->timerProcs[why] = proc; + globals->timerClosures[why] = closure; + globals->timerFireAt[why] = now; + + /* Post an event to force us back out of EvtGetEvent. Required if this + * is called from inside some BT callback. */ + postEmptyEvent( noopEvent ); +} /* palm_util_setTimer */ + +static XP_Bool +palm_util_altKeyDown( XW_UtilCtxt* XP_UNUSED(uc) ) +{ + XP_LOGF( "%s unimplemented", __func__ ); + return XP_FALSE; +} + +static void +palm_util_requestTime( XW_UtilCtxt* uc ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + globals->timeRequested = true; +} /* palm_util_requestTime */ + +static XP_U32 +palm_util_getCurSeconds( XW_UtilCtxt* XP_UNUSED(uc) ) +{ + return TimGetSeconds(); +} /* palm_util_getCurSeconds */ + +static DictionaryCtxt* +palm_util_makeEmptyDict( XW_UtilCtxt* uc ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + DictionaryCtxt* result = palm_dictionary_make( MPPARM(uc->mpool) + globals, NULL, NULL ); + XP_ASSERT( !!result ); + return result; +} /* palm_util_makeEmptyDict */ + +#ifndef XWFEATURE_STANDALONE_ONLY +static XWStreamCtxt* +palm_util_makeStreamFromAddr( XW_UtilCtxt* uc, XP_PlayerAddr channelNo ) +{ + XWStreamCtxt* stream; + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + + XP_ASSERT( !!globals->game.comms ); /* shouldn't be making stream in case + where can't send -- or should I + just be passing a null on-close + function? */ + XP_LOGF( "making stream for channel %d", channelNo ); + stream = makeSimpleStream( globals, palm_send_on_close ); + stream_setAddress( stream, channelNo ); + return stream; +} /* palm_util_makeStreamFromAddr */ +#endif + +static void +palm_send_on_close( XWStreamCtxt* stream, void* closure ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)closure; + + XP_ASSERT( !!globals->game.comms ); + comms_send( globals->game.comms, stream ); +} /* palm_send_on_close */ + +#ifdef XWFEATURE_BLUETOOTH +static void +handleUserBTCancel( PalmAppGlobals* globals ) +{ + XP_ASSERT( !globals->userCancelledBT ); + globals->userCancelledBT = XP_TRUE; + userErrorFromStrId( globals, STR_BT_NOINIT ); +} +#endif + +static XP_S16 +palm_send( const XP_U8* buf, XP_U16 len, + const CommsAddrRec* addr, void* closure ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)closure; + XP_S16 result = 0; + + XP_ASSERT( !!globals->game.comms ); + + switch( comms_getConType( globals->game.comms ) ) { +#ifdef XWFEATURE_IR + case COMMS_CONN_IR: + result = palm_ir_send( buf, len, globals ); + break; +#endif +#ifdef XWFEATURE_RELAY + case COMMS_CONN_RELAY: + result = palm_ip_send( buf, len, addr, globals ); + break; +#endif +#ifdef XWFEATURE_BLUETOOTH + case COMMS_CONN_BT: + if ( !!globals->mainForm && !globals->userCancelledBT ) { + XP_Bool userCancelled; + result = palm_bt_send( buf, len, addr, globals, &userCancelled ); + if ( userCancelled ) { + handleUserBTCancel( globals ); + } + } + break; +#endif + default: + XP_ASSERT(0); + } + return result; +} /* palm_send */ + +#ifdef COMMS_HEARTBEAT +static void +palm_reset( void* closure ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)closure; + XP_ASSERT( !!globals->game.comms ); + + switch( comms_getConType( globals->game.comms ) ) { +#ifdef XWFEATURE_BLUETOOTH + case COMMS_CONN_BT: + palm_bt_reset( globals ); + break; +#endif + default: + XP_ASSERT(0); + break; + } +} +#endif + +void +checkAndDeliver( PalmAppGlobals* globals, const CommsAddrRec* addr, + XWStreamCtxt* instream, CommsConnType conType ) +{ + /* For now we'll just drop incoming packets on transports not the same as + the current game's. We *could* however alert the user, or even + volunteer to switch e.g. from BT to IR as two passengers board a + plane. That'd require significant changes. */ + CommsCtxt* comms = globals->game.comms; + if ( !!comms && (conType == comms_getConType( comms )) ) { + if ( comms_checkIncomingStream( comms, instream, addr ) ) { + (void)server_receiveMessage( globals->game.server, instream ); + globals->msgReceivedDraw = true; + } + palm_util_requestTime( &globals->util ); + } + stream_destroy( instream ); +} /* checkAndDeliver */ + +static const XP_UCHAR* +palm_util_getUserString( XW_UtilCtxt* uc, XP_U16 stringCode ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + const XP_UCHAR* str = getResString( globals, stringCode ); + return str; +} /* palm_util_getUserString */ + +static void +formatBadWords( BadWordInfo* bwi, char buf[] ) +{ + XP_U16 i; + + for ( i = 0, buf[0] = '\0'; ; ) { + char wordBuf[18]; + StrPrintF( wordBuf, "\"%s\"", bwi->words[i] ); + StrCat( buf, wordBuf ); + if ( ++i == bwi->nWords ) { + break; + } + StrCat( buf, ", " ); + } +} /* formatBadWords */ + +static XP_Bool +palm_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi, + XP_U16 XP_UNUSED(turn), + XP_Bool turnLost ) +{ + XP_Bool result = XP_TRUE; + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + + const XP_UCHAR* resStr = getResString( globals, + turnLost? STR_PHONY_REJECTED : STR_ILLEGAL_WORD ); + if ( turnLost ) { + keySafeCustomAlert( globals, resStr ); + } else { + char wordsBuf[150]; + XP_UCHAR buf[200]; + formatBadWords( bwi, wordsBuf ); + StrPrintF( (char*)buf, (const char*)resStr, wordsBuf ); + result = palmask( globals, buf, NULL, -1 ); + } + + return result; +} /* palm_util_warnIllegalWord */ + +static void +palm_util_remSelected(XW_UtilCtxt* uc) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + showRemaining( globals ); +} + +#if defined XWFEATURE_BLUETOOTH || defined XWFEATURE_RELAY +static void +palm_util_addrChange( XW_UtilCtxt* uc, + const CommsAddrRec* XP_UNUSED_RELAY(oldAddr), + const CommsAddrRec* newAddr ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + +# ifdef XWFEATURE_BLUETOOTH + XP_Bool isBT = COMMS_CONN_BT == newAddr->conType; + if ( !isBT ) { + XP_ASSERT( !!globals->mainForm ); + palm_bt_close( globals ); + showConnState( globals ); + } +# endif + + if ( 0 ) { +# ifdef XWFEATURE_RELAY + } else if ( COMMS_CONN_RELAY == newAddr->conType ) { + ip_addr_change( globals, oldAddr, newAddr ); +# endif +# ifdef XWFEATURE_BLUETOOTH + } else if ( isBT && !globals->userCancelledBT ) { + XP_Bool userCancelled; + XP_ASSERT( !!globals->mainForm ); + if ( !palm_bt_init( globals, &userCancelled ) ) { + if ( userCancelled ) { + handleUserBTCancel( globals ); + } + } +# endif + } +} /* palm_util_addrChange */ +#endif /* #if defined XWFEATURE_BLUETOOTH || defined XWFEATURE_RELAY */ + +#ifdef XWFEATURE_SEARCHLIMIT +static XP_Bool +palm_util_getTraySearchLimits( XW_UtilCtxt* XP_UNUSED(uc), + XP_U16* min, XP_U16* max ) +{ + return doHintConfig( min, max ); +} /* palm_util_getTraySearchLimits */ +#endif + +#ifdef SHOW_PROGRESS +static void +palm_util_engineStarting( XW_UtilCtxt* uc, XP_U16 nBlanks ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + + if ( globals->gState.showProgress +#ifdef XW_TARGET_PNO + && ( nBlanks > 0 ) +#endif + ) { + RectangleType* bounds = &globals->progress.boundsRect; + + WinEraseRectangle( bounds, 0 ); + WinDrawRectangleFrame( rectangleFrame, bounds ); + + globals->progress.curLine = 0; + } else { + globals->progress.curLine = -1; + } +} /* palm_util_engineStarting */ + +static void +palm_util_engineStopping( XW_UtilCtxt* uc ) +{ + PalmAppGlobals* globals = (PalmAppGlobals*)uc->closure; + if ( globals->gState.showProgress && globals->progress.curLine >= 0 ) { + + WinEraseRectangle( &globals->progress.boundsRect, 0 ); + WinEraseRectangleFrame( rectangleFrame, + &globals->progress.boundsRect ); + + if ( globals->needsScrollbar ) { + SclDrawScrollBar( getActiveObjectPtr( XW_MAIN_SCROLLBAR_ID ) ); + } + } +} /* palm_util_engineStopping */ +#endif diff --git a/xwords4/palm/palmmain.h b/xwords4/palm/palmmain.h new file mode 100644 index 000000000..7c810ffe4 --- /dev/null +++ b/xwords4/palm/palmmain.h @@ -0,0 +1,439 @@ +/* -*-mode: C; fill-column: 76; c-basic-offset: 4; -*- */ +/* + * Copyright 1999 - 2007 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef _PALMMAIN_H_ +#define _PALMMAIN_H_ + +#define AppType APPID +#define PrefID 0 +#define VERSION_NUM_405 1 +#define VERSION_NUM 2 /* 1 to 2 moving to ARM */ + +#include +#include +#include +#include +#include +#include +#ifdef XWFEATURE_RELAY +# include +#endif + +#include "game.h" +#include "util.h" +#include "mempool.h" +#include "nwgamest.h" + +/* #include "prefsdlg.h" */ +#include "xwcolors.h" + +#include "xwords4defines.h" + +#ifdef MEM_DEBUG +# define MEMPOOL globals->mpool, +#else +# define MEMPOOL +#endif + + +enum { ONEBIT, /* GREYSCALE, */COLOR }; +typedef unsigned char GraphicsAbility; /* don't let above be 4 bytes */ +typedef struct PalmAppGlobals PalmAppGlobals; + +typedef const XP_UCHAR* (*GetResStringFunc)( PalmAppGlobals* globals, + XP_U16 strID ); + +typedef struct { + XP_S16 topOffset; /* how many pixels from the top of the + drawing area is the first pixel set in + the glyph */ + XP_U16 height; /* How many rows tall is the image? */ +} PalmFontHtInfo; + +typedef struct PalmDrawCtx { + DrawCtxVTable* vtable; + PalmAppGlobals* globals; + + void (*drawBitmapFunc)( DrawCtx* dc, Int16 resID, Int16 x, Int16 y ); + GetResStringFunc getResStrFunc; + + DrawingPrefs* drawingPrefs; + + RectangleType oldScoreClip; + RectangleType oldTrayClip; + + XP_S16 trayOwner; + XP_U16 fntHeight; + + GraphicsAbility able; + + UInt16 oldCoord; + XP_Bool doHiRes; + XP_Bool oneDotFiveAvail; + XP_Bool topFocus; + + XP_LangCode fontLangCode; + PalmFontHtInfo* fontHtInfo; + + union { + struct { + XP_U8 reserved; /* make CW compiler happy */ + } clr; + struct { + CustomPatternType valuePatterns[4]; + } bnw; + } u; + MPSLOT +} PalmDrawCtx; + +#define draw_drawBitmapAt(dc,id,x,y) \ + (*((((PalmDrawCtx*)dc))->drawBitmapFunc))((dc),(id),(x),(y)) + +typedef struct ListData { + unsigned char** strings; + unsigned char* storage; + XP_U16 nItems; + XP_U16 storageLen; + XP_U16 nextIndex; + XP_S16 selIndex; +#ifdef DEBUG + XP_Bool choicesSet; /* ARM hack: don't use strings after PACE + swaps.... */ +#endif +} ListData; + +typedef struct XWords4PreferenceType { + Int16 versionNum; + + Int16 curGameIndex; /* which game is currently open */ + + /* these are true global preferences */ + Boolean showProgress; + Boolean showGrid; + Boolean showColors; + Boolean reserved; /* was oneTimeShown */ + Boolean reserved1[4]; /* pad out to 12 for ARM */ + + /* New for 0x0405 */ + CommonPrefs cp; + + Int16 focusItem; +} XWords4PreferenceType; + +typedef struct MyIrConnect { + IrConnect irCon; + PalmAppGlobals* globals; +} MyIrConnect; + +typedef XP_U8 IR_STATE; /* enums are in palmir.h */ + +#define IR_BUF_SIZE 256 + +typedef struct MyIrPacket MyIrPacket; + +typedef struct ProgressCtxt { + RectangleType boundsRect; + XP_S16 curLine; +} ProgressCtxt; + +/* I *hate* having to define these globally... */ +typedef struct SavedGamesState { + struct PalmAppGlobals* globals; + FormPtr form; + ListPtr gamesList; + FieldPtr nameField; + char** stringPtrs; + Int16 nStrings; + Int16 displayGameIndex; +} SavedGamesState; + +typedef struct PrefsDlgState { + ListPtr playerBdSizeList; + ListPtr phoniesList; + + CommonPrefs cp; + + XP_U16 gameSeconds; + XP_Bool stateTypeIsGlobal; + + XP_U8 phoniesAction; + XP_U8 curBdSize; + XP_Bool showColors; + XP_Bool smartRobot; + XP_Bool showProgress; + XP_Bool showGrid; + XP_Bool hintsNotAllowed; + XP_Bool timerEnabled; + XP_Bool allowPickTiles; + XP_Bool allowHintRect; +#ifdef XWFEATURE_BLUETOOTH + XP_Bool confirmBTConnect; +#endif +} PrefsDlgState; + +typedef struct DictState { + ListPtr dictList; + ListData sLd; + XP_U16 nDicts; +} DictState; + +typedef struct PalmNewGameState { + FormPtr form; + ListPtr playerNumList; + NewGameCtx* ngc; + XP_U16 nXPorts; + XP_UCHAR passwds[MAX_PASSWORD_LENGTH+1][MAX_NUM_PLAYERS]; + XP_UCHAR* dictName; + XP_UCHAR shortDictName[32]; /* as long as a dict name can be */ + + XP_Bool forwardChange; + DeviceRole curServerHilite; +#ifndef XWFEATURE_STANDALONE_ONLY + CommsAddrRec addr; +#endif +} PalmNewGameState; + +typedef struct PalmDictList PalmDictList; + +#ifdef XWFEATURE_RELAY +typedef struct NetLibStuff { + UInt16 netLibRef; + NetSocketRef socket; + XP_Bool ipAddrInval; +} NetLibStuff; +#define ipSocketIsOpen(g) ((g)->nlStuff.socket != -1) +#endif + +#define MAX_DLG_PARAMS 2 + +#ifdef XWFEATURE_BLUETOOTH +typedef enum { + BTUI_NOBT + , BTUI_NONE + , BTUI_LISTENING + , BTUI_CONNECTING + , BTUI_CONNECTED /* slave */ + , BTUI_SERVING /* master */ +} BtUIState; +#endif + +#ifdef XWFEATURE_BLUETOOTH +# define TIMER_ACL_BACKOFF NUM_TIMERS_PLUS_ONE +# define NUM_PALM_TIMERS (NUM_TIMERS_PLUS_ONE + 1) +#else +# define NUM_PALM_TIMERS NUM_TIMERS_PLUS_ONE +#endif + +struct PalmAppGlobals { + FormPtr mainForm; + PrefsDlgState* prefsDlgState; + SavedGamesState* savedGamesState; + XWGame game; + DrawCtx* draw; + XW_UtilCtxt util; + + XP_U32 dlgParams[MAX_DLG_PARAMS]; + + VTableMgr* vtMgr; + + XWords4PreferenceType gState; + + DrawingPrefs drawingPrefs; + + PalmDictList* dictList; + + DmOpenRef boardDBP; + LocalID boardDBID; + + DmOpenRef gamesDBP; + LocalID gamesDBID; + +#ifdef XWFEATURE_RELAY + UInt16 exgLibraryRef; /* what library did user choose for sending? */ +#endif + + XP_UCHAR* stringsResPtr; + XP_U8* bonusResPtr[NUM_BOARD_SIZES]; + Boolean penDown; + Boolean isNewGame; + Boolean stateTypeIsGlobal; + Boolean timeRequested; + Boolean hintPending; + Boolean isLefty; + Boolean dictuiForBeaming; + Boolean postponeDraw; + Boolean needsScrollbar; + Boolean msgReceivedDraw; + Boolean isFirstLaunch; + Boolean menuIsDown; + XP_Bool newGameIsNew; + XP_Bool runningOnPOSE; /* Needed for NetLibSelect */ +#ifdef XWFEATURE_FIVEWAY + XP_Bool isTreo600; +#endif +#ifdef XWFEATURE_BLUETOOTH + XP_Bool userCancelledBT; + XP_Bool hasBTLib; +#endif + + GraphicsAbility able; + XP_U16 prevScroll; /* for scrolling in 'ask' dialog */ + UInt16 romVersion; + + XP_U8 scrollValue; /* 0..2: scrolled position of board */ + +#ifdef SHOW_PROGRESS + ProgressCtxt progress; +#endif + + XP_U16 width, height; + XP_U16 sonyLibRef; + XP_Bool doVSK; + XP_Bool hasHiRes; + XP_Bool oneDotFiveAvail; + XP_Bool useHiRes; + XP_Bool hasTreoFiveWay; + XP_Bool generatesKeyUp; + XP_Bool isZodiac; + XP_Bool keyDownReceived; + XP_Bool initialTakeDropped; /* work around apparent OS bug */ + /* PalmOS seems pretty broken w.r.t. key events. If I put up a modal + dialog while in the process of handling a keyUp, that form gets a + keyDown (and not with the repeat bit set either.) Hack around it. */ + XP_Bool handlingKeyEvent; + XP_Bool ignoreFirstKeyDown; + + XP_U16 lastKeyDown; + +#ifdef XWFEATURE_SEARCHLIMIT + XP_Bool askTrayLimits; +#endif + + CurGameInfo gameInfo; /* for the currently open, or new, game */ + + /* dialog/forms state */ + PalmNewGameState newGameState; + + DictState dictState; + + struct ConnsDlgState* connState; + + XWTimerProc timerProcs[NUM_PALM_TIMERS]; + void* timerClosures[NUM_PALM_TIMERS]; + XP_U32 timerFireAt[NUM_PALM_TIMERS]; + +#ifdef XWFEATURE_RELAY + NetLibStuff nlStuff; + XP_U32 heartTimerFireAt; +#endif + +#ifdef XWFEATURE_BLUETOOTH + struct PalmBTStuff* btStuff; + XP_U16 lastBTStatusRes; + BtUIState btUIState; /* For showing user what's up */ + XP_Bool suspendBT; +#endif + +#ifdef DEBUG + UInt8 save_rLsap; + IR_STATE ir_state_prev; + XP_U16 yCount; +/* Boolean resetGame; */ +#endif + MPSLOT +}; /* PalmAppGlobals */ + +/* custom events */ +enum { noopEvent = firstUserEvent /* 0x6000 */ + ,dictSelectedEvent + ,newGameOkEvent + ,newGameCancelEvent + ,loadGameEvent + ,prefsChangedEvent + ,openSavedGameEvent +#ifdef XWFEATURE_FIVEWAY + ,updateAfterFocusEvent +#endif +#if defined XWFEATURE_BLUETOOTH + ,closeBtLibEvent +#endif +#ifdef FEATURE_SILK + ,doResizeWinEvent +#endif +}; + +enum { + PNOLET_STORE_FEATURE = 1 /* where FtrPtr to pnolet code lives */ + , GLOBALS_FEATURE /* for passing globals to form handlers */ + , LOG_FILE_FEATURE /* these three for debugging */ + , LOG_MEMO_FEATURE + , LOG_SCREEN_FEATURE +#ifdef FEATURE_DUALCHOOSE + , FEATURE_WANTS_68K /* support for (pre-ship) ability to choose + armlet or 68K */ +#endif +#ifdef XWFEATURE_COMBINEDAWG + , DAWG_STORE_FEATURE +#endif + , PACE_BT_CBK_FEATURE +}; +enum { WANTS_68K, WANTS_ARM }; + + +/* If we're calling the old PilotMain (in palmmain.c) from from the one in + enter68k.c it needs a different name. But if this is the 68K-only app + then that is the entry point. */ +#ifdef FEATURE_PNOAND68K +# define PM2(pm) pm2_ ## pm +UInt32 PM2(PilotMain)(UInt16 cmd, MemPtr cmdPBP, UInt16 launchFlags); +#else +# define PM2(pm) pm +#endif + +DrawCtx* palm_drawctxt_make( MPFORMAL GraphicsAbility able, + PalmAppGlobals* globals, + GetResStringFunc getRSF, + DrawingPrefs* drawprefs ); +void palm_drawctxt_destroy( DrawCtx* dctx ); + +void palm_warnf( char* format, ... ); + +XP_Bool askPassword( PalmAppGlobals* globals, const XP_UCHAR* name, + XP_Bool isNew, XP_UCHAR* retbuf, XP_U16* len ); +XP_Bool palmaskFromStrId( PalmAppGlobals* globals, XP_U16 strId, + XP_S16 titleID ); +void freeSavedGamesData( MPFORMAL SavedGamesState* state ); + +void writeNameToGameRecord( PalmAppGlobals* globals, XP_S16 index, + char* newName, XP_U16 len ); + +const XP_UCHAR* getResString( PalmAppGlobals* globals, XP_U16 strID ); +XP_Bool palmask( PalmAppGlobals* globals, const XP_UCHAR* str, + const XP_UCHAR* altButton, XP_S16 titleID ); +void checkAndDeliver( PalmAppGlobals* globals, const CommsAddrRec* addr, + XWStreamCtxt* instream, CommsConnType conType ); + +#ifdef XW_TARGET_PNO +# define READ_UNALIGNED16(n) read_unaligned16((unsigned char*)(n)) +#else +# define READ_UNALIGNED16(n) *(n) +#endif + +#define IS_T600(g) (g)->isTreo600 + +#endif /* _PALMMAIN_H_ */ diff --git a/xwords4/palm/palmsavg.c b/xwords4/palm/palmsavg.c new file mode 100644 index 000000000..9dc1dd58c --- /dev/null +++ b/xwords4/palm/palmsavg.c @@ -0,0 +1,261 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/**************************************************************************** + * * + * Copyright 1999, 2001 by Eric House (xwords@eehouse.org). All rights reserved. * + * * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + ****************************************************************************/ + +#include +#include +#include + +#include "callback.h" +#include "strutils.h" +#include "palmsavg.h" +#include "palmmain.h" +#include "palmutil.h" +#include "gameutil.h" + +#include "xwords4defines.h" +#include "LocalizedStrIncludes.h" + +/* Prototypes */ +static void populateGameList( SavedGamesState* state ); +static void setFieldToSelText( SavedGamesState* state ); +static void listDrawFunc(Int16 index, RectanglePtr bounds, char** itemsText); + +/***************************************************************************** + * Handler for dictionary info form. + ****************************************************************************/ +#define USE_POPULATE 1 +Boolean +savedGamesHandleEvent( EventPtr event ) +{ + Boolean result; + PalmAppGlobals* globals; + SavedGamesState* state; + XP_S16 newGameIndex; + Int16* curGameIndexP; + char* newName; + + CALLBACK_PROLOGUE(); + + result = false; + globals = getFormRefcon(); + state = globals->savedGamesState; + + curGameIndexP = &globals->gState.curGameIndex; + + switch ( event->eType ) { + case frmOpenEvent: + + if ( !state ) { + state = globals->savedGamesState = XP_MALLOC( globals->mpool, + sizeof(*state) ); + } + XP_MEMSET( state, 0, sizeof(*state) ); + + state->globals = globals; + state->form = FrmGetActiveForm(); + + /* dictionary list setup */ + state->gamesList = getActiveObjectPtr( XW_SAVEDGAMES_LIST_ID ); + state->nameField = getActiveObjectPtr( XW_SAVEDGAMES_NAME_FIELD ); + state->displayGameIndex = globals->gState.curGameIndex; + XP_ASSERT( state->displayGameIndex < countGameRecords(globals) ); + + /* must preceed drawing calls so they have a valid window */ + FrmDrawForm( state->form ); + + /* LstSetDrawFunction must follow FrmDrawForm since populateGameList + must be called before listDrawFunc has valid globals; can't let + listDrawFunc get called from FrmDrawForm until set up correctly. */ + LstSetDrawFunction( state->gamesList, listDrawFunc ); + populateGameList( state ); + setFieldToSelText( state ); + break; + + case frmUpdateEvent: + FrmDrawForm( state->form ); + /* don't update field here! The keyboard may have been the form whose + disappearance triggered the frmUpdateEvent, and resetting the field + would undo the user's edits. Also causes a crash in Keyboard.c in + the ROMs for reasons I don't understand. */ + break; + + case lstSelectEvent: + state->displayGameIndex = LstGetSelection( state->gamesList ); + setFieldToSelText( state ); + result = true; + break; + + case ctlSelectEvent: + result = true; + switch ( event->data.ctlEnter.controlID ) { + + case XW_SAVEDGAMES_USE_BUTTON: /* write the new name to the selected + record */ + newName = FldGetTextPtr( state->nameField ); + if ( !!newName && (*newName != '\0') ) { + XP_U16 len = FldGetTextLength( state->nameField ); + writeNameToGameRecord( globals, state->displayGameIndex, + newName, len ); + + populateGameList( state ); + setFieldToSelText( state ); + } + break; + + case XW_SAVEDGAMES_DUPE_BUTTON: /* copy the selected record */ + newGameIndex = duplicateGameRecord( globals, + state->displayGameIndex ); + state->displayGameIndex = newGameIndex; + if ( *curGameIndexP >= newGameIndex ) { + ++*curGameIndexP; + } + populateGameList( state ); + setFieldToSelText( state ); + break; + + case XW_SAVEDGAMES_DELETE_BUTTON: /* delete the selected record. + Refuse if it's open. */ + if ( state->displayGameIndex == *curGameIndexP ) { + beep(); + } else if ( palmaskFromStrId( globals, STR_CONFIRM_DEL_GAME, -1) ) { + XP_S16 index = state->displayGameIndex; + deleteGameRecord( globals, index ); + if ( *curGameIndexP > index ) { + --*curGameIndexP; + } + if ( index == countGameRecords(globals) ) { + --index; + } + state->displayGameIndex = index; + populateGameList( state ); + } + break; + + case XW_SAVEDGAMES_OPEN_BUTTON: /* open the selected db if not already + open. */ + if ( *curGameIndexP != state->displayGameIndex ) { + EventType eventToPost = { .eType = openSavedGameEvent }; + ((OpenSavedGameData*)&eventToPost.data.generic)->newGameIndex + = state->displayGameIndex; + EvtAddEventToQueue( &eventToPost ); + globals->postponeDraw = true; + } + + case XW_SAVEDGAMES_DONE_BUTTON: + /* Update the app's idea of which record to save the current game + into -- in case any with lower IDs have been deleted. */ + FrmReturnToForm( 0 ); + + freeSavedGamesData( MPPARM(globals->mpool) state ); + + break; + } + + default: + break; + } /* switch */ + + CALLBACK_EPILOGUE(); + return result; +} /* savedGamesHandleEvent */ + +static void +setFieldToSelText( SavedGamesState* state ) +{ + FieldPtr field = state->nameField; + char name[MAX_GAMENAME_LENGTH]; + nameFromRecord( state->globals, state->displayGameIndex, name ); + XP_ASSERT( XP_STRLEN(name) < MAX_GAMENAME_LENGTH ); + XP_ASSERT( XP_STRLEN(name) > 0 ); + + FldSetSelection( field, 0, FldGetTextLength(field) ); + + FldInsert( field, name, XP_STRLEN(name) ); + FldSetSelection( field, 0, FldGetTextLength(field) ); +#ifdef XWFEATURE_FIVEWAY + setFormFocus( state->form, XW_SAVEDGAMES_NAME_FIELD ); +#endif + FldDrawField( field ); +} /* setFieldToSelText */ + +void +freeSavedGamesData( MPFORMAL SavedGamesState* state ) +{ + if ( !!state->stringPtrs ) { + XP_FREE( mpool, state->stringPtrs ); + state->stringPtrs = NULL; + } +} /* freeSavedGamesData */ + +static void +populateGameList( SavedGamesState* state ) +{ + XP_U16 nRecords; + PalmAppGlobals* globals = state->globals; + ListPtr gamesList = state->gamesList; + + nRecords = countGameRecords( globals ); + if ( state->nStrings != nRecords ) { + char** stringPtrs; + XP_U16 i; + + LstEraseList( gamesList ); + + freeSavedGamesData( MPPARM(globals->mpool) state ); + + state->nStrings = nRecords; + state->stringPtrs = stringPtrs = + XP_MALLOC( globals->mpool, + (nRecords+1) * sizeof(state->stringPtrs[0] ) ); + + stringPtrs[0] = (char*)globals; + for ( i = 1; i <= nRecords; ++i ) { + stringPtrs[i] = ""; + } + + LstSetListChoices( gamesList, &state->stringPtrs[1], nRecords ); + LstSetHeight( gamesList, XP_MIN(nRecords, 9) ); + } + + LstSetSelection( gamesList, state->displayGameIndex ); + XP_ASSERT( state->displayGameIndex < nRecords ); + LstMakeItemVisible( gamesList, state->displayGameIndex ); + LstDrawList( gamesList ); +} /* populateGameList */ + +static void +listDrawFunc( Int16 index, RectanglePtr bounds, char** itemsText ) +{ + char buf[MAX_GAMENAME_LENGTH]; + XP_U16 len; + PalmAppGlobals* globals; + + CALLBACK_PROLOGUE(); + + globals = (PalmAppGlobals*)itemsText[-1]; + + nameFromRecord( globals, index, buf ); + len = XP_STRLEN( buf ); + XP_ASSERT( len > 0 ); + WinDrawChars( buf, len, bounds->topLeft.x, bounds->topLeft.y ); + + CALLBACK_EPILOGUE(); +} /* listDrawFunc */ diff --git a/xwords4/palm/palmsavg.h b/xwords4/palm/palmsavg.h new file mode 100644 index 000000000..06a5db645 --- /dev/null +++ b/xwords4/palm/palmsavg.h @@ -0,0 +1,31 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/**************************************************************************** + * * + * Copyright 1999 - 2001 by Eric House (xwords@eehouse.org). All rights reserved. * + * * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + ****************************************************************************/ + +#ifndef _PALMSAVG_H_ +#define _PALMSAVG_H_ + +typedef struct OpenSavedGameData { + Int16 newGameIndex; +} OpenSavedGameData; + +Boolean savedGamesHandleEvent( EventPtr event ); + +#endif diff --git a/xwords4/palm/palmutil.c b/xwords4/palm/palmutil.c new file mode 100644 index 000000000..706518dfe --- /dev/null +++ b/xwords4/palm/palmutil.c @@ -0,0 +1,854 @@ +// -*-mode: C; fill-column: 80; c-basic-offset: 4; -*- +/**************************************************************************** + * * + * Copyright 1998-2001 by Eric House (xwords@eehouse.org). All rights reserved. * + * * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + ****************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#ifdef XWFEATURE_FIVEWAY +# include +#endif + +#include "strutils.h" +#include "palmutil.h" +#include "xwords4defines.h" +#include "palmmain.h" +#include "palmdbg.h" +#include "comtypes.h" + +#define MEMO "MemoDB" +#define LOGFILE "XWLogfile" + +/***************************************************************************** + * This is meant to be replaced by an actual beep.... + ****************************************************************************/ +#if defined FEATURE_BEEP || defined XW_FEATURE_UTILS +void beep( void ) { + SndPlaySystemSound( sndError ); +} /* beep */ +#endif + +/***************************************************************************** + * + ****************************************************************************/ +MemPtr +getActiveObjectPtr( UInt16 objectID ) +{ + FormPtr form = FrmGetActiveForm(); + Int16 index = FrmGetObjectIndex( form, objectID ); + XP_ASSERT( index >= 0 ); + XP_ASSERT( FrmGetObjectPtr( form, index )!= NULL ); + return FrmGetObjectPtr( form, index ); +} /* getActiveObjectPtr */ + +/***************************************************************************** + * + ****************************************************************************/ +void +getObjectBounds( UInt16 objectID, RectangleType* rectP ) +{ + FormPtr form = FrmGetActiveForm(); + Int16 index = FrmGetObjectIndex( form, objectID ); + XP_ASSERT( index >= 0 ); + FrmGetObjectBounds( form, index, rectP ); +} /* getObjectBounds */ + +#if defined XW_FEATURE_UTILS +/***************************************************************************** + * + ****************************************************************************/ +void +setObjectBounds( UInt16 objectID, RectangleType* rectP ) +{ + FormPtr form = FrmGetActiveForm(); + Int16 index = FrmGetObjectIndex( form, objectID ); + XP_ASSERT( index >= 0 ); + FrmSetObjectBounds( form, index, rectP ); +} /* getObjectBounds */ + +/***************************************************************************** + * + ****************************************************************************/ +void +setBooleanCtrl( UInt16 objectID, Boolean isSet ) +{ + CtlSetValue( getActiveObjectPtr( objectID ), isSet ); +} /* setBooleanCtrl */ + +void +setFieldEditable( UInt16 objectID, Boolean editable ) +{ + FieldPtr fld = getActiveObjectPtr( objectID ); + FieldAttrType attrs; + + FldGetAttributes( fld, &attrs ); + if ( attrs.editable != editable ) { + attrs.editable = editable; + FldSetAttributes( fld, &attrs ); + } +} /* setFieldEditable */ + +void +postEmptyEvent( eventsEnum typ ) +{ + EventType eventToPost; + eventToPost.eType = typ; + EvtAddEventToQueue( &eventToPost ); +} /* postEmptyEvent */ + +void +disOrEnable( FormPtr form, UInt16 id, Boolean enable ) +{ + UInt16 index = FrmGetObjectIndex( form, id ); + FormObjectKind typ; + + if ( enable ) { + FrmShowObject( form, index ); + } else { + FrmHideObject( form, index ); + } + + typ = FrmGetObjectType( form, index ); + if ( typ == frmControlObj ) { + ControlPtr ctl = getActiveObjectPtr( id ); + CtlSetEnabled( ctl, enable ); + } +} /* disOrEnable */ + +void +disOrEnableTri( FormPtr form, UInt16 id, XP_TriEnable enable ) +{ + UInt16 index; + XP_ASSERT( enable != TRI_ENAB_NONE ); + + index = FrmGetObjectIndex( form, id ); + + if ( enable == TRI_ENAB_HIDDEN ) { + FrmHideObject( form, index ); + } else { + FormObjectKind typ = FrmGetObjectType( form, index ); + XP_Bool active = enable == TRI_ENAB_ENABLED; + + FrmShowObject( form, index ); + + switch( typ ) { + case frmFieldObj: + setFieldEditable( id, active ); + break; + case frmControlObj: + CtlSetEnabled( getActiveObjectPtr( id ), enable ); + break; + case frmLabelObj: /* what to do? */ + break; + default: + XP_WARNF( "%s: %d not handled", __func__, (XP_U16)typ ); + XP_ASSERT(0); + } + } +} /* disOrEnableTri */ + +void +disOrEnableSet( FormPtr form, const UInt16* ids, Boolean enable ) +{ + for ( ; ; ) { + XP_U16 id = *ids++; + if ( !id ) { + break; + } + disOrEnable( form, id, enable ); + } +} /* disOrEnableSet */ + +/* Space a row of controls evenly across their container. This will center a + * single control or space several regardless of their size. Assumption, + * enforced by an assertion, is that they fit without overlapping. Another, not + * enforced, is that all have the same y coordinates. This was originally + * written for localized dialogs with different-length button text. + */ +void +centerControls( FormPtr form, const UInt16* id, XP_U16 nIds ) +{ + XP_U16 i, width, prev; + RectangleType bounds; + + + /* Two passes. First, figure total width outside controls. */ + FrmGetFormBounds( form, &bounds ); + width = bounds.extent.x; + for ( i = 0; i < nIds; ++i ) { + UInt16 index = FrmGetObjectIndex( form, id[i] ); + FrmGetObjectBounds( form, index, &bounds ); + XP_ASSERT( width >= bounds.extent.x ); + width -= bounds.extent.x; + } + + /* Then layout */ + width /= nIds + 1; /* space between controls */ + prev = width; + for ( i = 0; i < nIds; ++i ) { + UInt16 index = FrmGetObjectIndex( form, id[i] ); + FrmGetObjectBounds( form, index, &bounds ); + bounds.topLeft.x = prev; + FrmSetObjectBounds( form, index, &bounds ); + prev += bounds.extent.x + width; + } +} /* centerControls */ + +/***************************************************************************** + * + ****************************************************************************/ +Boolean +getBooleanCtrl( UInt16 objectID ) +{ + return CtlGetValue( getActiveObjectPtr( objectID ) ); +} /* getBooleanCtrl */ + +void +setFieldStr( XP_U16 id, const XP_UCHAR* buf ) +{ + FieldPtr field = getActiveObjectPtr( id ); + UInt16 len = FldGetTextLength( field ); + + if ( !buf ) { + buf = ""; + } + + FldSetSelection( field, 0, len ); + FldInsert( field, buf, XP_STRLEN(buf) ); +} /* setFieldStr */ + +#ifdef XWFEATURE_RELAY +void +getFieldStr( XP_U16 id, XP_UCHAR* buf, XP_U16 max ) +{ + FieldPtr field = getActiveObjectPtr( id ); + XP_UCHAR* str = FldGetTextPtr( field ); + XP_U16 len = FldGetTextLength( field ); + if ( len >= max ) { + len = max - 1; + } + XP_MEMCPY( buf, str, len ); + buf[len] = '\0'; +} /* strFromField */ +#endif + +/***************************************************************************** + * Set up to build the string and ptr-to-string lists needed for the + * LstSetListChoices system call. + ****************************************************************************/ +void +initListData( MPFORMAL ListData* ld, XP_U16 nItems ) +{ + /* include room for the closure */ + ld->strings = XP_MALLOC( mpool, ((nItems+1) * sizeof(*ld->strings)) ); + + ld->storage = XP_MALLOC( mpool, 1 ); + ld->storage[0] = '\0'; + ld->storageLen = 1; + + ld->nItems = nItems; + ld->nextIndex = 0; + ld->selIndex = -1; +#ifdef DEBUG + ld->choicesSet = XP_FALSE; +#endif +} /* initListData */ + +void +addListTextItem( MPFORMAL ListData* ld, const XP_UCHAR* txt ) +{ + XP_U16 curLen = ld->storageLen; + XP_U16 strLen = XP_STRLEN( txt ) + 1; /* null byte */ + XP_S32 diff; + unsigned char* storage; + XP_S16 i; + + storage = XP_REALLOC( mpool, ld->storage, curLen + strLen ); + XP_MEMCPY( storage + curLen, txt, strLen ); + + /* Now update all the existing ptrs since storage may have moved. + Remember to skip item 0. */ + diff = storage - ld->storage; + for ( i = ld->nextIndex; i > 0; --i ) { + ld->strings[i] += diff; + } + + ld->storage = storage; + ld->strings[++ld->nextIndex] = storage + curLen; + ld->storageLen += strLen; +} /* addListTextItem */ + +/***************************************************************************** + * Turn the list of offsets into ptrs, free the offsets list, and call + * LstSetListChoices + ****************************************************************************/ +void +setListChoices( ListData* ld, ListPtr list, void* closure ) +{ + ld->strings[0] = closure; + LstSetListChoices( list, (Char**)&ld->strings[1], ld->nextIndex ); +#ifdef DEBUG + ld->choicesSet = XP_TRUE; +#endif + if ( ld->selIndex >= 0 ) { + LstSetSelection( list, ld->selIndex ); + } +} /* setListChoices */ + +/***************************************************************************** + * Given a string, figure out which item it matches and save off that item's + * index so that at show time it can be used to set the selection. Note that + * anything that changes the order will invalidate this, but also that there's + * no harm in calling it again. + ****************************************************************************/ +void +setListSelection( ListData* ld, const char* selName ) +{ + ld->selIndex = 0; + XP_ASSERT( !ld->choicesSet ); + + if ( !!selName ) { + XP_U16 i; + for ( i = 0; i < ld->nextIndex; ++i ) { + if ( StrCompare( ld->strings[i+1], selName ) == 0 ) { + ld->selIndex = i; + break; + } + } + } +} /* setListSelection */ + +/***************************************************************************** + * Meant to be called after all items are added to the list, this function + * sorts them in place. For now I'll build in a call to StrCompare; later + * it could be a callback passed in. + ****************************************************************************/ +void +sortList( ListData* ld ) +{ + XP_S16 i, j, smallest; + unsigned char** strings = ld->strings; + char* tmp; + + XP_ASSERT( !ld->choicesSet ); /* if ARM, strings are reversed. Use list + API at this point. */ + + for ( i = 1; i <= ld->nextIndex; ++i ) { /* skip 0th (closure) slot */ + for ( smallest = i, j = i+1; j <= ld->nextIndex; ++j ) { + if ( StrCompare( strings[smallest], strings[j] ) > 0 ) { + smallest = j; + } + } + + if ( smallest == i ) { /* we got to the end without finding anything */ + break; + } + + tmp = strings[i]; + strings[i] = strings[smallest]; + strings[smallest] = tmp; + } +} /* sortList */ + +/***************************************************************************** + * Dispose the memory. Docs don't say whether LstSetListChoices does this for + * me so I assume not. It'll crash if I'm wrong. :-) + ****************************************************************************/ +void +freeListData( MPFORMAL ListData* ld ) +{ + XP_FREE( mpool, ld->storage ); + XP_FREE( mpool, ld->strings ); +} /* freeListData */ + +/***************************************************************************** + * + ****************************************************************************/ +void +setSelectorFromList( UInt16 triggerID, ListPtr list, XP_S16 listSelIndex ) +{ + XP_ASSERT( list != NULL ); + XP_ASSERT( getActiveObjectPtr( triggerID ) != NULL ); + XP_ASSERT( !!LstGetSelectionText( list, listSelIndex ) ); + CtlSetLabel( getActiveObjectPtr( triggerID ), + LstGetSelectionText( list, listSelIndex ) ); +} /* setTriggerFromList */ + +XP_Bool +penInGadget( const EventType* event, UInt16* whichGadget ) +{ + UInt16 x = event->screenX; + UInt16 y = event->screenY; + FormPtr form = FrmGetActiveForm(); + UInt16 nObjects, i; + XP_Bool result = XP_FALSE; + + for ( i = 0, nObjects = FrmGetNumberOfObjects(form); i < nObjects; ++i ) { + if ( frmGadgetObj == FrmGetObjectType( form, i ) ) { + UInt16 objId = FrmGetObjectId( form, i ); + if ( objId != REFCON_GADGET_ID ) { + RectangleType rect; + FrmGetObjectBounds( form, i, &rect ); + if ( RctPtInRectangle( x, y, &rect ) ) { + *whichGadget = objId; + result = XP_TRUE; + break; + } + } + } + } + + return result; +} /* penInGadget */ + +void +drawOneGadget( UInt16 id, const char* text, Boolean hilite ) +{ + RectangleType divRect; + XP_U16 len = XP_STRLEN(text); + XP_U16 width = FntCharsWidth( text, len ); + XP_U16 left; + + getObjectBounds( id, &divRect ); + WinDrawRectangleFrame( rectangleFrame, &divRect ); + WinEraseRectangle( &divRect, 0 ); + left = divRect.topLeft.x; + left += (divRect.extent.x - width) / 2; + WinDrawChars( text, len, left, divRect.topLeft.y ); + if ( hilite ) { + WinInvertRectangle( &divRect, 0 ); + } +} /* drawOneGadget */ + +#ifdef XWFEATURE_FIVEWAY +XP_S16 +getFocusOwner( void ) +{ + FormPtr form = FrmGetActiveForm(); + XP_S16 ownerID = -1; + XP_S16 focus = FrmGetFocus( form ); + if ( focus >= 0 ) { + ownerID = FrmGetObjectId( form, focus ); + } + return ownerID; +} /* getFocusOwner */ + +void +setFormFocus( FormPtr form, XP_U16 objectID ) +{ + Int16 index = FrmGetObjectIndex( form, objectID ); + XP_ASSERT( index >= 0 ); + FrmSetFocus( form, index ); +} /* setFormFocus */ + +XP_Bool +isFormObject( FormPtr form, XP_U16 objectID ) +{ + Int16 index = FrmGetObjectIndex( form, objectID ); + return index >= 0; +} + +#ifndef XW_TARGET_PNO +/* Warning: gross hack. HsNavDrawFocusRing doesn't work on newer Palms, + e.g. Tungsten T. It's been replaced by FrmNavDrawFocusRing. But that + requires the sdk-5r4 headers which require significant changes I don't want + to make right before shipping. So here's a hack: define FrmNavDrawFocusRing + based on info in the r4 header info without actually including any headers. +*/ +Err FrmNavDrawFocusRing( FormType* fp, UInt16 oid, Int16 ei, + RectangleType* birp, + HsNavFocusRingStyleEnum rs, Boolean fr ) + _SYSTEM_API(_CALL_WITH_UNPOPPED_16BIT_SELECTOR)(_SYSTEM_TABLE, + sysTrapNavSelector, + 0x07 ); +#endif + +void +drawFocusRingOnGadget( PalmAppGlobals* globals, XP_U16 idLow, XP_U16 idHigh ) +{ + FormPtr form; + XP_S16 index; + XP_U16 focusID; + + form = FrmGetActiveForm(); + index = FrmGetFocus( form ); + if ( index >= 0 ) { + focusID = FrmGetObjectId( form, index ); + + if ( (focusID >= idLow) && (focusID <= idHigh) ) { + Err err; + RectangleType rect; + + getObjectBounds( focusID, &rect ); + + /* growing the rect didn't work to fix glitches in ring drawing. */ + if ( IS_T600(globals) ) { + err = HsNavDrawFocusRing( form, focusID, 0, &rect, + hsNavFocusRingStyleObjectTypeDefault, + false ); + } else { + err = FrmNavDrawFocusRing( form, focusID, 0, &rect, + hsNavFocusRingStyleObjectTypeDefault, + false ); + } + XP_ASSERT( err == errNone ); + } + } +} /* drawFocusRingOnGadget */ + +XP_Bool +considerGadgetFocus( PalmAppGlobals* globals, const EventType* event, XP_U16 idLow, XP_U16 idHigh ) +{ + XP_Bool handled; + XP_U16 objectID; + + XP_ASSERT( event->eType == frmObjectFocusLostEvent + || event->eType == frmObjectFocusTakeEvent ); + XP_ASSERT( event->data.frmObjectFocusTake.formID == FrmGetActiveFormID() ); + XP_ASSERT( &event->data.frmObjectFocusTake.objectID + == &event->data.frmObjectFocusLost.objectID ); + + objectID = event->data.frmObjectFocusTake.objectID; + handled = (objectID >= idLow) && (objectID <= idHigh); + if ( handled ) { + if ( event->eType == frmObjectFocusTakeEvent ) { + FormPtr form = FrmGetActiveForm(); + setFormFocus( form, objectID ); + drawFocusRingOnGadget( globals, idLow, idHigh ); + } + } + + return handled; +} /* considerGadgetFocus */ + +XP_Bool +tryRockerKey( XP_U16 key, XP_U16 selGadget, XP_U16 idLow, XP_U16 idHigh ) +{ + XP_Bool result = XP_FALSE; + + if ( vchrRockerCenter == key ) { + if ( selGadget >= idLow && selGadget <= idHigh ) { + result = XP_TRUE; + } + } + return result; +} /* tryRockerKey */ +#endif + +void +drawGadgetsFromList( ListPtr list, XP_U16 idLow, XP_U16 idHigh, + XP_U16 hiliteItem ) +{ + XP_U16 i; + XP_ASSERT( idLow <= idHigh ); + + for ( i = 0; idLow <= idHigh; ++i, ++idLow ) { + const char* text = LstGetSelectionText( list, i ); + Boolean hilite = idLow == hiliteItem; + drawOneGadget( idLow, text, hilite ); + } +} /* drawGadgetsFromList */ + +void +setFormRefcon( void* refcon ) +{ +#ifdef DEBUG + Err err = +#endif + FtrSet( APPID, GLOBALS_FEATURE, (UInt32)refcon ); + XP_ASSERT( err == errNone ); +} /* setFormRefcon */ + +void* +getFormRefcon() +{ + UInt32 ptr; +#ifdef DEBUG + Err err = +#endif + FtrGet( APPID, GLOBALS_FEATURE, &ptr ); + XP_ASSERT( err == errNone ); + XP_ASSERT( ptr != 0L ); + return (void*)ptr; +} /* getFormRefcon */ + +void +fitButtonToString( XP_U16 id ) +{ + ControlPtr button = getActiveObjectPtr( id ); + const char* label = CtlGetLabel( button ); + XP_U16 width = FntCharsWidth( label, XP_STRLEN(label) ); + RectangleType rect; + width += 14; /* 7 pixels at either end */ + + getObjectBounds( id, &rect ); + rect.topLeft.x -= (rect.extent.x - width); + rect.extent.x = width; + + setObjectBounds( id, &rect ); +} /* fitButtonToString */ + +#endif + +#if defined FEATURE_REALLOC || defined XW_FEATURE_UTILS +XP_U8* +palm_realloc( XP_U8* in, XP_U16 size ) +{ + MemPtr ptr = (MemPtr)in; + XP_U32 oldsize = MemPtrSize( ptr ); + MemPtr newptr = MemPtrNew( size ); + XP_ASSERT( !!newptr ); + XP_MEMCPY( newptr, ptr, oldsize ); + MemPtrFree( ptr ); + return newptr; +} /* palm_realloc */ + +XP_U16 +palm_snprintf( XP_UCHAR* buf, XP_U16 XP_UNUSED_DBG(len), + const XP_UCHAR* format, ... ) +{ + XP_U16 nChars; + /* PENDING use len to avoid writing too many chars */ + va_list ap; + + va_start( ap, format ); + nChars = StrVPrintF( (char*)buf, (char*)format, ap ); + va_end( ap ); + XP_ASSERT( len >= nChars ); + return nChars; +} /* palm_snprintf */ +#endif + +#ifdef FOR_GREMLINS +static Boolean +doNothing( EventPtr event ) +{ + return true; +} /* doNothing */ +#endif + +#ifdef DEBUG +void +palm_warnf( char* format, ... ) +{ + char buf[200]; + va_list ap; + + va_start( ap, format ); + StrVPrintF( buf, format, ap ); + va_end( ap ); + +#ifdef FOR_GREMLINS + /* If gremlins are active, we want all activity to stop here! That + means no cancellation */ + { + FormPtr form; + FieldPtr field; + + form = FrmInitForm( XW_GREMLIN_WARN_FORM_ID ); + + FrmSetActiveForm( form ); + + FrmSetEventHandler( form, doNothing ); + + field = getActiveObjectPtr( XW_GREMLIN_WARN_FIELD_ID ); + FldSetTextPtr( field, buf ); + FldRecalculateField( field, true ); + + FrmDrawForm( form ); +#if 1 + /* This next may freeze X (go to a console to kill POSE), but when + you look at the logs you'll see what event you were on when the + ASSERT fired. Otherwise the events just keep coming and POSE + fills up the log. */ + while(1); +#endif + /* this should NEVER return */ + (void)FrmDoDialog( form ); + } +#else + (void)FrmCustomAlert( XW_ERROR_ALERT_ID, buf, " ", " " ); +#endif +} /* palm_warnf */ + +void +palm_assert( Boolean b, int line, const char* func, const char* file ) +{ + if ( !b ) { + /* force file logging on if not already */ + FtrSet( APPID, LOG_FILE_FEATURE, 1 ); + XP_LOGF( "ASSERTION FAILED: line %d, %s(), %s", line, func, file ); + + XP_WARNF( "ASSERTION FAILED: line %d, %s(), %s", line, func, file ); + } +} /* palmassert */ + +static void +logToDB( const char* buf, const char* dbName, XP_U32 dbCreator, XP_U32 dbType ) +{ + const XP_U16 MAX_MEMO_SIZE = 4000; + const XP_U16 MAX_NRECORDS = 200; + DmOpenRef ref; + UInt16 nRecords, index; + UInt16 len = XP_STRLEN( buf ); + UInt16 hSize, slen; + MemHandle hand; + MemPtr ptr; + char tsBuf[20]; + UInt16 tsLen; + DateTimeType dtType; + LocalID dbID; + + TimSecondsToDateTime( TimGetSeconds(), &dtType ); + StrPrintF( tsBuf, "\n%d:%d:%d-", dtType.hour, dtType.minute, + dtType.second ); + tsLen = XP_STRLEN(tsBuf); + + (void)DmCreateDatabase( CARD_0, dbName, dbCreator, dbType, false ); + dbID = DmFindDatabase( CARD_0, dbName ); + ref = DmOpenDatabase( CARD_0, dbID, dmModeWrite ); + + nRecords = DmNumRecordsInCategory( ref, dmAllCategories ); + if ( nRecords == 0 ) { + index = dmMaxRecordIndex; + hSize = 0; + hand = DmNewRecord( ref, &index, 1 ); + DmReleaseRecord( ref, index, true ); + } else { + + while ( nRecords > MAX_NRECORDS ) { + index = 0; + DmSeekRecordInCategory( ref, &index, 0, dmSeekForward, + dmAllCategories); + DmRemoveRecord( ref, index ); + --nRecords; + } + + index = 0; + DmSeekRecordInCategory( ref, &index, nRecords, dmSeekForward, + dmAllCategories); + hand = DmGetRecord( ref, index ); + + XP_ASSERT( !!hand ); + hSize = MemHandleSize( hand ) - 1; + ptr = MemHandleLock( hand ); + slen = XP_STRLEN(ptr); + MemHandleUnlock(hand); + + if ( hSize > slen ) { + hSize = slen; + } + (void)DmReleaseRecord( ref, index, false ); + } + + if ( (hSize + len + tsLen) > MAX_MEMO_SIZE ) { + index = dmMaxRecordIndex; + hand = DmNewRecord( ref, &index, len + tsLen + 1 ); + hSize = 0; + } else { + (void)DmResizeRecord( ref, index, len + hSize + tsLen + 1 ); + hand = DmGetRecord( ref, index ); + } + + ptr = MemHandleLock( hand ); + DmWrite( ptr, hSize, tsBuf, tsLen ); + DmWrite( ptr, hSize + tsLen, buf, len + 1 ); + MemHandleUnlock( hand ); + + DmReleaseRecord( ref, index, true ); + DmCloseDatabase( ref ); +} /* logToDB */ + +static void +deleteDB( const char* dbName ) +{ + LocalID dbID; + dbID = DmFindDatabase( CARD_0, dbName ); + if ( 0 != dbID ) { + Err err = DmDeleteDatabase( CARD_0, dbID ); + XP_ASSERT( errNone == err ); + } else { + XP_WARNF( "%s(%s): got back 0", __func__, dbName ); + } +} /* deleteDB */ + +void +PalmClearLogs( void ) +{ + deleteDB( MEMO ); + deleteDB( LOGFILE ); +} + +static void +logToMemo( const char* buf ) +{ + UInt32 val = 0L; + Err err = FtrGet( APPID, LOG_MEMO_FEATURE, (UInt32*)&val ); + if ( errNone == err && val != 0 ) { + logToDB( buf, MEMO, 'memo', 'DATA' ); + } +} + +static void +logToFile( const char* buf ) +{ +#if 0 + logToDB( buf, LOGFILE, 'XWLG', 'TEXT' ); +#else + UInt32 val = 0L; + Err err = FtrGet( APPID, LOG_FILE_FEATURE, (UInt32*)&val ); + if ( errNone == err && val != 0 ) { + logToDB( buf, LOGFILE, 'XWLG', 'TEXT' ); + } +#endif +} + +void +palm_debugf( char* format, ...) +{ + char buf[200]; + va_list ap; + + va_start( ap, format ); + StrVPrintF( buf, format, ap ); + va_end( ap ); + + logToMemo( buf ); + logToFile( buf ); +} /* debugf */ + +void +palm_logf( char* format, ... ) +{ + char buf[200]; + va_list ap; + + va_start( ap, format ); + StrVPrintF( buf, format, ap ); + va_end( ap ); + + logToMemo( buf ); + logToFile( buf ); +} /* palm_logf */ + +#endif /* DEBUG */ diff --git a/xwords4/palm/palmutil.h b/xwords4/palm/palmutil.h new file mode 100644 index 000000000..5b5f9350a --- /dev/null +++ b/xwords4/palm/palmutil.h @@ -0,0 +1,105 @@ +// -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- +/**************************************************************************** + * * + * Copyright 1998-1999, 2001 by Eric House (xwords@eehouse.org). All rights reserved. * + * * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + ****************************************************************************/ + +#ifndef _PALMUTIL_H_ +#define _PALMUTIL_H_ + +#include +#include +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include "xwdefines.h" */ +/* #include "xwords.h" */ +/* #include "xwdebug.h" */ + +#include "palmmain.h" + +/* short myMemCmp( unsigned char* src1, unsigned char* src2, short size ); */ +/* void userError( CharPtr* str ); */ +/* void userErrorRes( short resId ); */ +/* void userErrorResS( short resId, CharPtr data ); */ +void beep( void ); + +MemPtr getActiveObjectPtr( UInt16 objectID ); +void getObjectBounds( UInt16 objectID, RectangleType* rectP ); +void setObjectBounds( UInt16 objectID, RectangleType* rectP ); + +void disOrEnable( FormPtr form, UInt16 id, Boolean enable ); +void disOrEnableSet( FormPtr form, const UInt16* id, Boolean enable ); + +void disOrEnableTri( FormPtr form, UInt16 id, XP_TriEnable enable ); + +void centerControls( FormPtr form, const UInt16* id, XP_U16 nIds ); + +void setBooleanCtrl( UInt16 objectID, Boolean isSet ); +Boolean getBooleanCtrl( UInt16 objectID ); + +void setFieldStr( XP_U16 id, const XP_UCHAR* buf ); +#ifdef XWFEATURE_RELAY +void getFieldStr( XP_U16 id, XP_UCHAR* buf, XP_U16 max ); +#endif +void setFieldEditable( UInt16 objectID, Boolean editable ); + +void postEmptyEvent( eventsEnum typ ); + +/* list item stuff */ +void initListData( MPFORMAL ListData* ld, XP_U16 nItems ); +void addListTextItem( MPFORMAL ListData* ld, const XP_UCHAR* txt ); +void setListChoices( ListData* ld, ListPtr list, void* closure ); +void setListSelection( ListData* ld, const char* selName ); +void sortList( ListData* ld ); +void freeListData( MPFORMAL ListData* ld ); + +/* this should work on either trigger or selector */ +void setSelectorFromList( UInt16 selectorID, ListPtr list, + short listSelIndex ); + +void sizeGadgetsForStrings( FormPtr form, ListPtr list, XP_U16 firstGadgetID ); +void drawGadgetsFromList( ListPtr list, XP_U16 idLow, XP_U16 idHigh, + XP_U16 hiliteItem ); + +XP_Bool penInGadget( const EventType* event, UInt16* whichGadget ); +void drawOneGadget( UInt16 id, const char* text, Boolean hilite ); +# ifdef XWFEATURE_FIVEWAY +XP_S16 getFocusOwner( void ); +void setFormFocus( FormPtr form, XP_U16 objectID ); +XP_Bool isFormObject( FormPtr form, XP_U16 objectID ); +void drawFocusRingOnGadget( PalmAppGlobals* globals, XP_U16 idLow, + XP_U16 idHigh ); +XP_Bool considerGadgetFocus( PalmAppGlobals* globals, const EventType* event, + XP_U16 idLow, XP_U16 idHigh ); + +XP_Bool tryRockerKey( XP_U16 key, XP_U16 selGadget, + XP_U16 idLow, XP_U16 idHigh ); +# endif + +void setFormRefcon( void* refcon ); +void* getFormRefcon(void); +void fitButtonToString( XP_U16 id ); + + +#ifdef DEBUG +void PalmClearLogs( void ); +#endif + +#endif diff --git a/xwords4/palm/pnolet.c b/xwords4/palm/pnolet.c new file mode 100644 index 000000000..f8349fd07 --- /dev/null +++ b/xwords4/palm/pnolet.c @@ -0,0 +1,161 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2004 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include "pnostate.h" +#include "pace_gen.h" +#include "pace_man.h" /* for the ExgSocketType flippers */ +#include "palmmain.h" + +typedef union ParamStub { + ExgAskParamType exgAskParamType; + ExgSocketType exgSocketType; +} ParamStub; + +unsigned long +realArmletEntryPoint( const void *emulStateP, + void *userData68KP, + Call68KFuncType* call68KFuncP ); + +/* With arm-palmos-gcc, there can't be any .GOT references in the entry point + (since those values get put inline BEFORE the function rather than + after.) */ +unsigned long +ArmletEntryPoint( const void *emulStateP, + void *userData68KP, + Call68KFuncType* call68KFuncP ) +{ + return realArmletEntryPoint( emulStateP, + userData68KP, + call68KFuncP ); +} + +#ifdef XWFEATURE_IR +static XP_Bool +convertParamToArm( UInt16 cmd, ParamStub* armParam, MemPtr parm68K ) +{ + XP_Bool revert; + + if ( cmd == sysAppLaunchCmdExgAskUser ) { + /* We don't read the data, but we do write to one field. */ + revert = XP_TRUE; + } else if ( cmd == sysAppLaunchCmdExgReceiveData ) { + flipEngSocketToArm( &armParam->exgSocketType, + (const unsigned char*)parm68K ); + revert = XP_TRUE; /* just to be safe */ + } else { + revert = XP_FALSE; + } + + return revert; +} /* convertParamToArm */ + +static void +convertParamFromArm( UInt16 cmd, MemPtr parm68K, ParamStub* armParam ) +{ + if ( cmd == sysAppLaunchCmdExgAskUser ) { + write_unaligned8( &((unsigned char*)parm68K)[4], + armParam->exgAskParamType.result ); + } else if ( cmd == sysAppLaunchCmdExgReceiveData ) { + flipEngSocketFromArm( parm68K, &armParam->exgSocketType ); + } else { + XP_ASSERT(0); + } +} +#endif + +unsigned long +realArmletEntryPoint( const void *emulStateP, + void *userData68KP, + Call68KFuncType* call68KFuncP ) +{ + PNOState* loc; + PNOState state; + PnoletUserData* dataP; + unsigned long result; + unsigned long oldR10; + UInt16 cmd; + MemPtr cmdPBP; + MemPtr oldVal; + ParamStub ptrStorage; + XP_Bool mustRevert; + + loc = getStorageLoc(); + + dataP = (PnoletUserData*)userData68KP; + dataP->stateSrc = (PNOState*)Byte_Swap32((unsigned long)&state); + dataP->stateDest = (PNOState*)Byte_Swap32((unsigned long)loc); + + state.gotTable = (unsigned long*) + Byte_Swap32((unsigned long)dataP->gotTable); + + if ( !dataP->recursive ) { + STACK_START(unsigned char, stack, 4 ); + ADD_TO_STACK4(stack, userData68KP, 0); + STACK_END(stack); + + state.emulStateP = emulStateP; + state.call68KFuncP = call68KFuncP; + + (*call68KFuncP)( emulStateP, + Byte_Swap32((unsigned long)dataP->storageCallback), + stack, 4 ); + } + + asm( "mov %0, r10" : "=r" (oldR10) ); + asm( "mov r10, %0" : : "r" (state.gotTable) ); + + cmd = Byte_Swap16( dataP->cmd ); + cmdPBP = (MemPtr)Byte_Swap32((unsigned long)dataP->cmdPBP); + +#ifdef XWFEATURE_IR + /* if the cmd is sysAppLaunchCmdExgAskUser or + sysAppLaunchCmdExgReceiveData then we're going to be making use of the + cmdPBP value in PilotMain. Need to convert it here. */ + mustRevert = convertParamToArm( cmd, &ptrStorage, cmdPBP ); +#endif + + oldVal = cmdPBP; + cmdPBP = &ptrStorage; + + result = PM2(PilotMain)( cmd, cmdPBP, Byte_Swap16(dataP->launchFlags) ); + +#ifdef XWFEATURE_IR + if ( mustRevert ) { + convertParamFromArm( cmd, oldVal, &ptrStorage ); + } +#endif + + asm( "mov r10, %0" : : "r" (oldR10) ); + return result; +} /* realArmletEntryPoint( */ + +PNOState* +getStorageLoc( void ) +{ + asm( "adr r0,data" ); + asm( "mov pc,lr" ); + asm( "data:" ); + /* we need sizeof(PNOState) worth of bytes after the data label. */ + asm( "nop" ); + asm( "nop" ); + return (PNOState*)0L; /* shut up compiler; overwrite me!!!! */ + /* The compiler's adding a "mov pc,lr" here too that we can overwrite. */ +} + diff --git a/xwords4/palm/pnostate.h b/xwords4/palm/pnostate.h new file mode 100644 index 000000000..362f25f5f --- /dev/null +++ b/xwords4/palm/pnostate.h @@ -0,0 +1,44 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ + +#ifndef _PNOSTATE_H_ +#define _PNOSTATE_H_ + +#include +#include + +/* from http://news.palmos.com/read/messages?id=159373 */ +typedef struct EmulStateType { + UInt32 instr; + UInt32 regD[8]; + UInt32 regA[8]; + UInt32 regPC; +} EmulStateType; + +/* This gets written into the code by the callback below. */ +typedef struct PNOState { + const EmulStateType* emulStateP; + Call68KFuncType* call68KFuncP; + void* gotTable; +} PNOState; + +/* I can't get the circular decls to work now */ +typedef void StorageCallback(/*PnoletUserData*/void* dataP); + +/* This is how armlet and 68K stub communicate on startup */ +typedef struct PnoletUserData { + unsigned long* pnoletEntry; + unsigned long* gotTable; + StorageCallback* storageCallback; /* armlet calls this */ + PNOState* stateSrc; /* armlet fills in */ + PNOState* stateDest; /* armlet fills in */ + + /* PilotMain params */ + MemPtr cmdPBP; + UInt16 cmd; + UInt16 launchFlags; + + /* Other.... */ + Boolean recursive; /* PilotMain called from inside PilotMain */ +} PnoletUserData; + +#endif diff --git a/xwords4/palm/prefsdlg.c b/xwords4/palm/prefsdlg.c new file mode 100644 index 000000000..22dd10c61 --- /dev/null +++ b/xwords4/palm/prefsdlg.c @@ -0,0 +1,444 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; compile-command: "make ARCH=68K_ONLY MEMDEBUG=TRUE"; -*- */ +/* + * Copyright 1999 - 2006 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef XWFEATURE_FIVEWAY +# include +#endif + +#include "prefsdlg.h" +#include "callback.h" +#include "palmutil.h" +#include "xwords4defines.h" + +void localPrefsToGlobal( PalmAppGlobals* globals ); +static void localPrefsToControls( PrefsDlgState* state ); +static void drawPrefsTypeGadgets( PalmAppGlobals* globals ); +static void showHidePrefsWidgets( PalmAppGlobals* globals, FormPtr form ); +static void checkPrefsHiliteGadget( PalmAppGlobals* globals, UInt16 selGadget ); +static void controlsToLocalPrefs( PrefsDlgState* state ); +static XP_Bool ignoredUnlessNewgame( XP_U16 id ); + +Boolean +PrefsFormHandleEvent( EventPtr event ) +{ + Boolean result = false; + PalmAppGlobals* globals; + PrefsDlgState* state; + FormPtr form; + Int16 chosen; + XP_S16 selGadget; + + CALLBACK_PROLOGUE(); + globals = getFormRefcon(); + state = globals->prefsDlgState; + + switch ( event->eType ) { + + case frmOpenEvent: + + if ( !state ) { + GlobalPrefsToLocal( globals ); + state = globals->prefsDlgState; + } + + state->playerBdSizeList = + getActiveObjectPtr( XW_PREFS_BDSIZE_LIST_ID ); + state->phoniesList = + getActiveObjectPtr( XW_PREFS_PHONIES_LIST_ID ); + + case frmUpdateEvent: + form = FrmGetActiveForm(); + + localPrefsToControls( state ); + + showHidePrefsWidgets( globals, form ); + + FrmDrawForm( form ); + + drawPrefsTypeGadgets( globals ); + break; + + case penDownEvent: + result = penInGadget( event, &selGadget ); + if ( result ) { + checkPrefsHiliteGadget( globals, selGadget ); + } + break; + +#ifdef XWFEATURE_FIVEWAY + case keyDownEvent: + selGadget = getFocusOwner(); + if ( selGadget >= 0 ) { + if ( tryRockerKey( event->data.keyDown.chr, selGadget, + XW_PREFS_ALLGAMES_GADGET_ID, + XW_PREFS_ONEGAME_GADGET_ID ) ) { + checkPrefsHiliteGadget( globals, selGadget ); + result = XP_TRUE; + } else if ( !globals->isNewGame + && vchrRockerCenter == event->data.keyDown.chr ) { + result = ignoredUnlessNewgame( selGadget ); + } + } + break; + + case frmObjectFocusTakeEvent: + case frmObjectFocusLostEvent: + result = considerGadgetFocus( globals, event, XW_PREFS_ALLGAMES_GADGET_ID, + XW_PREFS_ONEGAME_GADGET_ID ); + break; +#endif + + case ctlSelectEvent: + result = true; + switch ( event->data.ctlSelect.controlID ) { + +#ifdef XWFEATURE_SEARCHLIMIT + case XW_PREFS_NOHINTS_CHECKBOX_ID: { + Boolean checked = getBooleanCtrl( XW_PREFS_NOHINTS_CHECKBOX_ID ); + disOrEnable( FrmGetActiveForm(), XW_PREFS_HINTRECT_CHECKBOX_ID, + !checked ); + } + break; +#endif + case XW_PREFS_PHONIES_TRIGGER_ID: + chosen = LstPopupList( state->phoniesList ); + if ( chosen >= 0 ) { + setSelectorFromList( XW_PREFS_PHONIES_TRIGGER_ID, + state->phoniesList, chosen ); + state->phoniesAction = chosen; + } + break; + + case XW_PREFS_BDSIZE_SELECTOR_ID: + XP_ASSERT( globals->isNewGame ); /* above disables otherwise */ + chosen = LstPopupList( state->playerBdSizeList ); + if ( chosen >= 0 ) { + setSelectorFromList( XW_PREFS_BDSIZE_SELECTOR_ID, + state->playerBdSizeList, chosen ); + state->curBdSize = PALM_MAX_ROWS - (chosen*2); + } + break; + + case XW_PREFS_TIMERON_CHECKBOX_ID: + XP_ASSERT( globals->isNewGame ); + form = FrmGetActiveForm(); + showHidePrefsWidgets( globals, form ); + break; + + case XW_PREFS_OK_BUTTON_ID: + controlsToLocalPrefs( state ); + postEmptyEvent( prefsChangedEvent ); + globals->postponeDraw = true; + + case XW_PREFS_CANCEL_BUTTON_ID: + FrmReturnToForm( 0 ); + break; + } + break; + + default: + break; + } + + CALLBACK_EPILOGUE(); + return result; +} /* prefsFormHandleEvent */ + +static XP_Bool +ignoredUnlessNewgame( XP_U16 id ) +{ + XP_Bool ignored = XP_FALSE; + switch ( id ) { + case XW_PREFS_NOHINTS_CHECKBOX_ID: + case XW_PREFS_BDSIZE_SELECTOR_ID: + case XW_PREFS_TIMERON_CHECKBOX_ID: + case XW_PREFS_TIMER_FIELD_ID: +#ifdef FEATURE_TRAY_EDIT + case XW_PREFS_PICKTILES_CHECKBOX_ID: +#endif + ignored = XP_TRUE; + break; + } + return ignored; +} /* ignoredUnlessNewgame */ + +void +GlobalPrefsToLocal( PalmAppGlobals* globals ) +{ + PrefsDlgState* state = globals->prefsDlgState; + + if ( !state ) { + state = globals->prefsDlgState = XP_MALLOC( globals->mpool, + sizeof(*state) ); + } + + state->curBdSize = !!globals->game.model? + model_numRows( globals->game.model ) : PALM_MAX_ROWS; + + state->showColors = globals->gState.showColors; + state->smartRobot = globals->util.gameInfo->robotSmartness == SMART_ROBOT; + state->showGrid = globals->gState.showGrid; + state->showProgress = globals->gState.showProgress; + XP_MEMCPY( &state->cp, &globals->gState.cp, sizeof(state->cp) ); + XP_ASSERT( !!globals->game.server ); + + state->phoniesAction = globals->util.gameInfo->phoniesAction; + state->hintsNotAllowed = globals->gameInfo.hintsNotAllowed; +#ifdef XWFEATURE_SEARCHLIMIT + state->allowHintRect = globals->gameInfo.allowHintRect; +#endif + state->timerEnabled = globals->util.gameInfo->timerEnabled; + state->gameSeconds = globals->util.gameInfo->gameSeconds; +#ifdef FEATURE_TRAY_EDIT + state->allowPickTiles = globals->util.gameInfo->allowPickTiles; +#endif + +#ifdef XWFEATURE_BLUETOOTH + state->confirmBTConnect = globals->util.gameInfo->confirmBTConnect; +#endif + + state->stateTypeIsGlobal = globals->stateTypeIsGlobal; +} /* GlobalPrefsToLocal */ + +XP_Bool +LocalPrefsToGlobal( PalmAppGlobals* globals ) +{ + PrefsDlgState* state = globals->prefsDlgState; + XP_Bool erase = XP_FALSE; + + /* curBdSize handled elsewhere */ + + globals->gState.showColors = state->showColors; + + globals->util.gameInfo->robotSmartness = + state->smartRobot? SMART_ROBOT:DUMB_ROBOT; + + erase = globals->gState.showGrid != state->showGrid; + globals->gState.showGrid = state->showGrid; + + XP_MEMCPY( &globals->gState.cp, &state->cp, sizeof(globals->gState.cp) ); + + globals->gState.showProgress = state->showProgress; + + globals->util.gameInfo->phoniesAction = state->phoniesAction; + + globals->gameInfo.hintsNotAllowed = state->hintsNotAllowed; +#ifdef XWFEATURE_SEARCHLIMIT + globals->gameInfo.allowHintRect = state->allowHintRect; +#endif + globals->util.gameInfo->timerEnabled = state->timerEnabled; + globals->util.gameInfo->gameSeconds = state->gameSeconds; + +#ifdef FEATURE_TRAY_EDIT + globals->util.gameInfo->allowPickTiles = state->allowPickTiles; +#endif + +#ifdef XWFEATURE_BLUETOOTH + globals->util.gameInfo->confirmBTConnect = state->confirmBTConnect; +#endif + + return erase; +} /* LocalPrefsToGlobal */ + +static void +numToField( UInt16 id, XP_S16 num ) +{ + FieldPtr field; + char buf[16]; + + StrIToA( buf, num ); + field = getActiveObjectPtr( id ); + FldInsert( field, buf, StrLen(buf) ); +} /* numToField */ + +static void +localPrefsToControls( PrefsDlgState* state ) +{ + setSelectorFromList( XW_PREFS_BDSIZE_SELECTOR_ID, + state->playerBdSizeList, + (PALM_MAX_ROWS - state->curBdSize) / 2 ); + + setSelectorFromList( XW_PREFS_PHONIES_TRIGGER_ID, state->phoniesList, + state->phoniesAction ); + + setBooleanCtrl( XW_PREFS_PLAYERCOLORS_CHECKBOX_ID, state->showColors ); + setBooleanCtrl( XW_PREFS_PROGRESSBAR_CHECKBOX_ID, state->showProgress ); + setBooleanCtrl( XW_PREFS_NOHINTS_CHECKBOX_ID, state->hintsNotAllowed ); +#ifdef XWFEATURE_SEARCHLIMIT + setBooleanCtrl( XW_PREFS_HINTRECT_CHECKBOX_ID, state->allowHintRect ); +#endif + setBooleanCtrl( XW_PREFS_ROBOTSMART_CHECKBOX_ID, state->smartRobot ); + setBooleanCtrl( XW_PREFS_SHOWGRID_CHECKBOX_ID, state->showGrid ); + setBooleanCtrl( XW_PREFS_SHOWARROW_CHECKBOX_ID, state->cp.showBoardArrow ); + setBooleanCtrl( XW_PREFS_ROBOTSCORE_CHECKBOX_ID, + state->cp.showRobotScores ); + setBooleanCtrl( XW_PREFS_HIDETRAYVAL_CHECKBOX_ID, + state->cp.hideTileValues ); + + setBooleanCtrl( XW_PREFS_TIMERON_CHECKBOX_ID, state->timerEnabled ); + +#ifdef FEATURE_TRAY_EDIT + setBooleanCtrl( XW_PREFS_PICKTILES_CHECKBOX_ID, state->allowPickTiles ); +#endif + +#ifdef XWFEATURE_BLUETOOTH + setBooleanCtrl( XW_PREFS_BTCONFIRM_CHECKBOX_ID, state->confirmBTConnect ); +#endif + + numToField( XW_PREFS_TIMER_FIELD_ID, state->gameSeconds/60 ); +} /* localPrefsToControls */ + +static XP_S16 +fieldToNum( UInt16 id ) +{ + FieldPtr field; + char* txt; + + field = getActiveObjectPtr( id ); + txt = FldGetTextPtr( field ); + return StrAToI( txt ); +} /* fieldToNum */ + +static void +controlsToLocalPrefs( PrefsDlgState* state ) +{ + state->showColors = getBooleanCtrl( XW_PREFS_PLAYERCOLORS_CHECKBOX_ID ); + state->smartRobot = getBooleanCtrl( XW_PREFS_ROBOTSMART_CHECKBOX_ID ); + state->showGrid = getBooleanCtrl( XW_PREFS_SHOWGRID_CHECKBOX_ID ); + state->cp.showBoardArrow = + getBooleanCtrl( XW_PREFS_SHOWARROW_CHECKBOX_ID ); + state->cp.showRobotScores = + getBooleanCtrl( XW_PREFS_ROBOTSCORE_CHECKBOX_ID ); + state->cp.hideTileValues = + getBooleanCtrl( XW_PREFS_HIDETRAYVAL_CHECKBOX_ID ); + + state->showProgress = getBooleanCtrl( XW_PREFS_PROGRESSBAR_CHECKBOX_ID ); + + /* trapping ctlEnterEvent should mean it can't have changed, so no need + to test before grabbing the value. */ + state->hintsNotAllowed = getBooleanCtrl( XW_PREFS_NOHINTS_CHECKBOX_ID ); +#ifdef XWFEATURE_SEARCHLIMIT + state->allowHintRect = getBooleanCtrl( XW_PREFS_HINTRECT_CHECKBOX_ID ); +#endif + + state->timerEnabled = getBooleanCtrl( XW_PREFS_TIMERON_CHECKBOX_ID ); + state->gameSeconds = fieldToNum( XW_PREFS_TIMER_FIELD_ID ) * 60; + +#ifdef FEATURE_TRAY_EDIT + state->allowPickTiles = + getBooleanCtrl( XW_PREFS_PICKTILES_CHECKBOX_ID ); +#endif + +#ifdef XWFEATURE_BLUETOOTH + state->confirmBTConnect = getBooleanCtrl( XW_PREFS_BTCONFIRM_CHECKBOX_ID ); +#endif + +} /* controlsToLocalPrefs */ + +static void +drawPrefsTypeGadgets( PalmAppGlobals* globals ) +{ + ListPtr list; + UInt16 active; + + list = getActiveObjectPtr( XW_PREFS_TYPES_LIST_ID ); + XP_ASSERT( !!list ); + XP_ASSERT( !!globals->prefsDlgState ); + + active = globals->prefsDlgState->stateTypeIsGlobal ? + XW_PREFS_ALLGAMES_GADGET_ID : XW_PREFS_ONEGAME_GADGET_ID; + + drawGadgetsFromList( list, XW_PREFS_ALLGAMES_GADGET_ID, + XW_PREFS_ONEGAME_GADGET_ID, active ); +#ifdef XWFEATURE_FIVEWAY + drawFocusRingOnGadget( globals, XW_PREFS_ALLGAMES_GADGET_ID, + XW_PREFS_ONEGAME_GADGET_ID ); +#endif + LOG_RETURN_VOID(); +} /* drawPrefsTypeGadgets */ + +static void +doOneSet( FormPtr form, XP_U16 first, XP_U16 last, XP_Bool enable, + XP_Bool isNewGame ) +{ + while ( first <= last ) { + XP_TriEnable stat = enable? TRI_ENAB_ENABLED : TRI_ENAB_HIDDEN; + + if ( enable && !isNewGame && ignoredUnlessNewgame( first ) ) { + stat = TRI_ENAB_DISABLED; + } + disOrEnableTri( form, first, stat ); + + ++first; + } +} /* doOneSet */ + +/* Which set of controls is supposed to be visible changes depending on which + * gadget is selected. + */ +static void +showHidePrefsWidgets( PalmAppGlobals* globals, FormPtr form ) +{ + XP_Bool isGlobal; + XP_Bool isNewGame = globals->isNewGame; + XP_U16 firstToEnable, lastToEnable, firstToDisable, lastToDisable; + + isGlobal = globals->prefsDlgState->stateTypeIsGlobal; + + /* Need to do the disabling first */ + if ( isGlobal ) { + firstToEnable = XW_PREFS_FIRST_GLOBAL_ID; + lastToEnable = XW_PREFS_LAST_GLOBAL_ID; + firstToDisable = XW_PREFS_FIRST_PERGAME_ID; + lastToDisable = XW_PREFS_LAST_PERGAME_ID; + } else { + firstToDisable = XW_PREFS_FIRST_GLOBAL_ID; + lastToDisable = XW_PREFS_LAST_GLOBAL_ID; + firstToEnable = XW_PREFS_FIRST_PERGAME_ID; + lastToEnable = XW_PREFS_LAST_PERGAME_ID; + } + + doOneSet( form, firstToDisable, lastToDisable, XP_FALSE, isNewGame ); + doOneSet( form, firstToEnable, lastToEnable, XP_TRUE, isNewGame ); + + if ( !isGlobal ) { + Boolean on = getBooleanCtrl( XW_PREFS_TIMERON_CHECKBOX_ID ); + disOrEnable( form, XW_PREFS_TIMER_FIELD_ID, on ); + } +} /* showHidePrefsWidgets */ + +static void +checkPrefsHiliteGadget( PalmAppGlobals* globals, UInt16 selGadget ) +{ + FormPtr form = FrmGetActiveForm(); + Boolean result = false; + + XP_Bool globalChosen = selGadget == XW_PREFS_ALLGAMES_GADGET_ID; + XP_LOGF( "%s: selGadget=%d", __func__, selGadget ); + + result = globalChosen != globals->prefsDlgState->stateTypeIsGlobal; + + if ( result ) { + globals->prefsDlgState->stateTypeIsGlobal = globalChosen; + + showHidePrefsWidgets( globals, form ); + drawPrefsTypeGadgets( globals ); + } +} /* checkPrefsHiliteGadget */ diff --git a/xwords4/palm/prefsdlg.h b/xwords4/palm/prefsdlg.h new file mode 100644 index 000000000..f777147ad --- /dev/null +++ b/xwords4/palm/prefsdlg.h @@ -0,0 +1,43 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _PREFSDLG_H_ +#define _PREFSDLG_H_ + +#include + +#include "palmmain.h" + +/* How prefs work. + * + * Prefs can be called either directly from the main form or from the new + * game form. If it's called directly, it creates and initializes a + * PrefsDlgState instance. If it's called indirectly, the caller does that + * for it (or in the newGame case may do it anyway so it has defaults to use + * if it's never called). If the user cancels the direct call, any changes + * are ignored. If the user cancels when called from the newGame form we'll + * re-init the structure. + */ + +/* both newgame and prefs need to know about this */ +Boolean PrefsFormHandleEvent( EventPtr event ); +void GlobalPrefsToLocal( PalmAppGlobals* globals ); +XP_Bool LocalPrefsToGlobal( PalmAppGlobals* globals ); + +#endif diff --git a/xwords4/palm/undef_hack.h b/xwords4/palm/undef_hack.h new file mode 100644 index 000000000..c67f9941f --- /dev/null +++ b/xwords4/palm/undef_hack.h @@ -0,0 +1,6 @@ +/* HsPhoneTraps.h includes this gem: */ +/* #if (CPU_TYPE == CPU_68K) */ +/* #define PHN_LIB_TRAP(trapNum) SYS_TRAP(trapNum) */ +/* #endif */ + +#define PHN_LIB_TRAP(x) diff --git a/xwords4/palm/xptypes.h b/xwords4/palm/xptypes.h new file mode 100644 index 000000000..f6808675b --- /dev/null +++ b/xwords4/palm/xptypes.h @@ -0,0 +1,138 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1999-2000 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _XPTYPES_H_ +#define _XPTYPES_H_ + +#include +#include +#include +#include + +#define XP_TRUE ((XP_Bool)(1==1)) +#define XP_FALSE ((XP_Bool)(1==0)) + +typedef unsigned char XP_U8; +typedef signed char XP_S8; +typedef unsigned char XP_UCHAR; + +typedef UInt16 XP_U16; +typedef Int16 XP_S16; + +typedef unsigned long XP_U32; +typedef signed long XP_S32; + +typedef signed short XP_FontCode; /* not sure how I'm using this yet */ +typedef Boolean XP_Bool; +typedef XP_U32 XP_Time; + +void palm_debugf(char*, ...); +void palm_assert(Boolean b, int line, const char* func, const char* file ); +void palm_warnf( char* format, ... ); +void palm_logf( char* format, ... ); +XP_U16 palm_snprintf( XP_UCHAR* buf, XP_U16 len, const XP_UCHAR* format, ... ); +XP_S16 palm_memcmp( XP_U8* p1, XP_U8* p2, XP_U16 nBytes ); +XP_U8* palm_realloc(XP_U8* in, XP_U16 size); + +#define XP_CR "\n" + +#define XP_RANDOM() SysRandom(0) + +#ifdef MEM_DEBUG +# define XP_PLATMALLOC(nbytes) MemPtrNew(nbytes) +# define XP_PLATREALLOC(p,s) palm_realloc((p),(s)) +# define XP_PLATFREE(p) MemChunkFree(p) +#else +# define XP_MALLOC(p,nbytes) MemPtrNew(nbytes) +# define XP_REALLOC(p,ptr,nbytes) palm_realloc((ptr),(nbytes)) +# define XP_FREE(pool,p) MemChunkFree(p) +#endif + + +#define XP_MEMSET(src, val, nbytes) MemSet( (src), (nbytes), (val) ) +#define XP_MEMCPY(d,s,l) MemMove((d),(s),(l)) +#define XP_MEMCMP( a1, a2, l ) palm_memcmp((XP_U8*)(a1),(XP_U8*)(a2),(l)) +/* MemCmp is reputed not to work on some versions of PalmOS */ +/* #define XP_MEMCMP( a1, a2, l ) MemCmp((a1),(a2),(l)) */ +#define XP_STRLEN(s) StrLen((const char*)s) +#define XP_STRNCMP(s1,s2,l) StrNCompare((const char*)(s1), \ + (const char*)(s2),(l)) +#define XP_STRCMP(s1,s2) StrCompare((s1),(s2)) +#define XP_STRCAT(d,s) StrCat((d),(s)) +#define XP_SNPRINTF palm_snprintf + +#define XP_MIN(a,b) ((a)<(b)?(a):(b)) +#define XP_MAX(a,b) ((a)>(b)?(a):(b)) +#define XP_ABS(a) ((a)>=0?(a):-(a)) + +#ifdef DEBUG +#define XP_ASSERT(b) palm_assert(b, __LINE__, __func__, __FILE__) +#else +#define XP_ASSERT(b) +#endif + +#ifdef DEBUG +# define XP_DEBUGF(...) palm_debugf(__VA_ARGS__) +#else +# define XP_DEBUGF(...) +#endif + +#define XP_STATUSF XP_LOGF /* for now */ + +#ifdef DEBUG +#define XP_LOGF(...) palm_logf(__VA_ARGS__) +#define XP_WARNF(...) palm_warnf(__VA_ARGS__) +#else +#define XP_WARNF(...) +#define XP_LOGF(...) +#endif + +/* Assumes big-endian, of course */ +#if defined __LITTLE_ENDIAN +# include "pace_man.h" +# define XP_NTOHL(l) Byte_Swap32(l) +# define XP_NTOHS(s) Byte_Swap16(s) +# define XP_HTONL(l) Byte_Swap32(l) +# define XP_HTONS(s) Byte_Swap16(s) +#elif defined __BIG_ENDIAN +# define XP_NTOHL(l) (l) +# define XP_NTOHS(s) (s) +# define XP_HTONL(l) (l) +# define XP_HTONS(s) (s) +#else +# error "pick one!!!" +#endif + +#define XP_LD "%ld" +#define XP_P "%lx" + +#ifdef FEATURE_SILK +# define XP_UNUSED_SILK(x) x +#else +# define XP_UNUSED_SILK(x) XP_UNUSED(x) +#endif + +#ifndef XWFEATURE_IR +# define XP_UNUSED_IR(x) x +#else +# define XP_UNUSED_IR(x) XP_UNUSED(x) +#endif + +#endif + diff --git a/xwords4/palm/xwcolors.h b/xwords4/palm/xwcolors.h new file mode 100644 index 000000000..ca47d0373 --- /dev/null +++ b/xwords4/palm/xwcolors.h @@ -0,0 +1,50 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2000-2001 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _XWCOLORS_H_ +#define _XWCOLORS_H_ + +#include + +enum { + COLOR_BLACK, + COLOR_WHITE, + + COLOR_PLAYER1, + COLOR_PLAYER2, + COLOR_PLAYER3, + COLOR_PLAYER4, + + COLOR_DBL_LTTR, + COLOR_DBL_WORD, + COLOR_TRPL_LTTR, + COLOR_TRPL_WORD, + + COLOR_EMPTY, + COLOR_TILE, + COLOR_CURSOR, /* not read from resource */ + + COLOR_NCOLORS /* 12 */ +}; + +typedef struct DrawingPrefs { + IndexedColorType drawColors[COLOR_NCOLORS]; +} DrawingPrefs; + +#endif diff --git a/xwords4/palm/xwords4defines.h b/xwords4/palm/xwords4defines.h new file mode 100644 index 000000000..50f97e3ca --- /dev/null +++ b/xwords4/palm/xwords4defines.h @@ -0,0 +1,451 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 1999 - 2008 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _XWORDS4DEFINES_H_ +#define _XWORDS4DEFINES_H_ + + + +#define RECOMMENDED_SBAR_WIDTH 7 +#define SBAR_MIN 13 +#define SBAR_MAX 15 +#define SBAR_PAGESIZE 13 +#define SBAR_START_VALUE SBAR_MIN + +#define TRAY_HEIGHT_LR 21 +#define TRAY_BUTTON_HEIGHT_LR 10 + +#define TRAY_HEIGHT_HR 16 +#define TRAY_BUTTON_HEIGHT_HR 8 +#define TRAY_BUTTON_WIDTH 9 + +#define FLIP_BUTTON_WIDTH 8 +#define FLIP_BUTTON_HEIGHT FLIP_BUTTON_WIDTH +#define BOARD_TOP 8 + +#define TRAY_BUTTONS_Y_LR (160-TRAY_HEIGHT_LR) +#define TRAY_BUTTONS_Y_HR (160-TRAY_HEIGHT_HR) +#define SHOWTRAY_BUTTON_Y (160-FLIP_BUTTON_HEIGHT-5) + +#define IR_STATUS_HEIGHT 12 + +#define PALM_FLIP_LEFT 160-FLIP_BUTTON_WIDTH +#define PALM_TRAY_BUTTON_LEFT 143 + +#define XW_MAIN_FORM 1000 +#define XW_NEWGAMES_FORM 1001 +#define XW_ERROR_ALERT_ID 1002 +#define XW_DICTINFO_FORM 1003 +#define XW_ASK_FORM_ID 1004 +#define XW_PASSWORD_DIALOG_ID 1005 +#define XW_BLANK_DIALOG_ID 1006 +#define XW_COLORPREF_DIALOG_ID 1007 +#define XW_PREFS_FORM 1008 +#define XW_SAVEDGAMES_DIALOG_ID 1009 +#define XW_HINTCONFIG_FORM_ID 1010 +#define XW_CONNS_FORM 1011 +#ifdef FOR_GREMLINS +# define XW_GREMLIN_WARN_FORM_ID 1012 +#endif + +#define XW_ASK_MENU_ID 1001 +#define ASK_COPY_PULLDOWN_ID 1000 +#define ASK_SELECTALL_PULLDOWN_ID 1001 + +#define XW_MAIN_MENU_ID 1000 +#define XW_MAIN_FLIP_BUTTON_ID 1016 +#define XW_MAIN_VALUE_BUTTON_ID 1017 +#define XW_MAIN_TRAY_BUTTON_ID 1018 +#define XW_MAIN_SCROLLBAR_ID 1019 +#ifndef EIGHT_TILES +#define XW_MAIN_DONE_BUTTON_ID 1020 +#define XW_MAIN_JUGGLE_BUTTON_ID 1021 +#define XW_MAIN_TRADE_BUTTON_ID 1022 +#define XW_MAIN_HIDE_BUTTON_ID 1023 +#endif +#define XW_MAIN_HINT_BUTTON_ID 1024 +#define XW_MAIN_SHOWTRAY_BUTTON_ID 1026 +//#define XW_MAIN_OK_BUTTON_ID 1026 + +#ifdef XWFEATURE_BLUETOOTH +# define XW_BTSTATUS_GADGET_ID 1099 /* change later */ +#endif + +#ifdef FOR_GREMLINS +# define GREMLIN_BOARD_GADGET_IDAUTOID 1027 +# define GREMLIN_TRAY_GADGET_IDAUTOID 1028 +#endif + +/* File menu */ +#define XW_NEWGAME_PULLDOWN_ID 1050 +#define XW_SAVEDGAMES_PULLDOWN_ID 1051 +#define XW_BEAMDICT_PULLDOWN_ID 1052 +#define XW_BEAMBOARD_PULLDOWN_ID 1053 +#define XW_PREFS_PULLDOWN_ID 1054 +#define XW_ABOUT_PULLDOWN_ID 1055 + +/* Game menu */ +#define XW_TILEVALUES_PULLDOWN_ID 1056 +#define XW_TILESLEFT_PULLDOWN_ID 1057 +#define XW_PASSWORDS_PULLDOWN_ID 1058 +#define XW_HISTORY_PULLDOWN_ID 1059 +#define XW_FINISH_PULLDOWN_ID 1060 +#ifndef XWFEATURE_STANDALONE_ONLY +# define XW_RESENDIR_PULLDOWN_ID 1061 +#endif + +/* Move menu */ +#define XW_HINT_PULLDOWN_ID 1062 +#define XW_NEXTHINT_PULLDOWN_ID 1063 +#ifdef XWFEATURE_SEARCHLIMIT +# define XW_HINTCONFIG_PULLDOWN_ID 1064 +#endif +#define XW_UNDOCUR_PULLDOWN_ID 1065 +#define XW_UNDOLAST_PULLDOWN_ID 1066 +#define XW_DONE_PULLDOWN_ID 1067 +#define XW_JUGGLE_PULLDOWN_ID 1068 +#define XW_TRADEIN_PULLDOWN_ID 1069 +#define XW_HIDESHOWTRAY_PULLDOWN_ID 1070 + +#ifdef FEATURE_DUALCHOOSE +# define XW_RUN68K_PULLDOWN_ID 1071 +# define XW_RUNARM_PULLDOWN_ID 1072 +#endif + +/* debug menu */ +#ifdef DEBUG +# define XW_LOGFILE_PULLDOWN_ID 2000 +# define XW_LOGMEMO_PULLDOWN_ID 2001 +# define XW_CLEARLOGS_PULLDOWN_ID 2002 +# define XW_NETSTATS_PULLDOWN_ID 2003 +# define XW_MEMSTATS_PULLDOWN_ID 2004 +# define XW_BTSTATS_PULLDOWN_ID 2005 +#endif + +#ifdef FOR_GREMLINS +#define XW_GREMLIN_DIVIDER_RIGHT 2010 +#define XW_GREMLIN_DIVIDER_LEFT 2011 +#endif + +#define XW_DICT_SELECTOR_ID 1038 +#define XW_OK_BUTTON_ID 1039 +#define XW_CANCEL_BUTTON_ID 1040 +/* #define XW_DICT_BUTTON_ID 1040 */ + +#define MAX_GAMENAME_LENGTH 32 +#define MAX_PLAYERNAME_LENGTH 32 + +#define NUM_PLAYER_COLS 4 /* name, local, robot and passwd */ +#define PALM_MAX_ROWS 15 /* max is a 15x15 grid on palm */ +#define PALM_MAX_COLS 15 /* max is a 15x15 grid on palm */ + +#define NUM_BOARD_SIZES 3 /* 15x15, 13x13 and 11x11 */ + +#define XW_PLAYERNAME_1_FIELD_ID 2100 +#define XW_ROBOT_1_CHECKBOX_ID 2101 +#define XW_REMOTE_1_CHECKBOX_ID 2102 +#define XW_PLAYERPASSWD_1_TRIGGER_ID 2103 + +#define XW_PLAYERNAME_2_FIELD_ID 2104 +#define XW_ROBOT_2_CHECKBOX_ID 2105 +#define XW_REMOTE_2_CHECKBOX_ID 2106 +#define XW_PLAYERPASSWD_2_TRIGGER_ID 2107 + +#define XW_PLAYERNAME_3_FIELD_ID 2108 +#define XW_ROBOT_3_CHECKBOX_ID 2109 +#define XW_REMOTE_3_CHECKBOX_ID 2110 +#define XW_PLAYERPASSWD_3_TRIGGER_ID 2111 + +#define XW_PLAYERNAME_4_FIELD_ID 2112 +#define XW_ROBOT_4_CHECKBOX_ID 2113 +#define XW_REMOTE_4_CHECKBOX_ID 2114 +#define XW_PLAYERPASSWD_4_TRIGGER_ID 2115 + +#define XW_NPLAYERS_LIST_ID 2121 +#define XW_NPLAYERS_SELECTOR_ID 2122 +#define XW_PREFS_BUTTON_ID 2123 +#define XW_GINFO_JUGGLE_ID 2124 + +#ifndef XWFEATURE_STANDALONE_ONLY +#define XW_SOLO_GADGET_ID 2125 +#define XW_SERVER_GADGET_ID 2126 +#define XW_CLIENT_GADGET_ID 2127 +#define XW_SERVERTYPES_LIST_ID 2128 +#endif + +#ifdef FOR_GREMLINS +# define XW_GREMLIN_WARN_FIELD_ID 2129 +#endif + + +/* we need to hide these labels, so no AUTOID */ +#ifndef XWFEATURE_STANDALONE_ONLY +# define XW_LOCAL_LABEL_ID 2130 +# define XW_TOTALP_FIELD_ID 2131 +# define XW_LOCALP_LABEL_ID 2132 +#endif + +#define REFCON_GADGET_ID 3000 + +#define XW_ASK_TXT_FIELD_ID 2200 +#define XW_ASK_YES_BUTTON_ID 2201 +#define XW_ASK_NO_BUTTON_ID 2202 +#define XW_ASK_SCROLLBAR_ID 2203 + +#define MAX_PASSWORD_LENGTH 4 /* server.c has no limit */ +#define XW_PASSWORD_CANCEL_BUTTON 2300 +#define XW_PASSWORD_NAME_LABEL 2301 +#define XW_PASSWORD_NEWNAME_LABEL 2302 +#define XW_PASSWORD_NAME_FIELD 2303 +#define XW_PASSWORD_PASS_FIELD 2304 +#define XW_PASSWORD_OK_BUTTON 2305 + +#define XW_BLANK_LIST_ID 2401 +#define XW_BLANK_LABEL_FIELD_ID 2402 +#define XW_BLANK_OK_BUTTON_ID 2403 +#define XW_BLANK_PICK_BUTTON_ID 2404 +#define XW_BLANK_BACKUP_BUTTON_ID 2405 + +#define XW_COLORS_FACTORY_BUTTON_ID 2520 +#define XW_COLORS_OK_BUTTON_ID 2521 +#define XW_COLORS_CANCEL_BUTTON_ID 2522 + +#define STRL_RES_TYPE 'StrL' +#define XW_STRL_RESOURCE_ID 1000 + +#define BOARD_RES_TYPE 'Xbrd' +#define BOARD_RES_ID 1000 + +#define COLORS_RES_TYPE 'Clrs' +#define COLORS_RES_ID 1000 + +#define CARD_0 0 +#ifdef DEBUG +# define XW_GAMES_DBNAME "xw4games_dbg" +# define XWORDS_GAMES_TYPE 'Xwdg' +# define XW_PREFS_DBNAME "xw4prefs_dbg" +# define XWORDS_PREFS_TYPE 'Xwpd' +#else +# define XW_GAMES_DBNAME "xw4games" +# define XWORDS_GAMES_TYPE 'Xwgm' +# define XW_PREFS_DBNAME "xw4prefs" +# define XWORDS_PREFS_TYPE 'Xwpr' +#endif + +#define XW_DICTINFO_LIST_ID 2601 +#define XW_DICTINFO_TRIGGER_ID 2602 +#define XW_PHONIES_TRIGGER_ID 2603 +#define XW_PHONIES_LABLE_ID 2604 +#define XW_PHONIES_LIST_ID 2605 +#define XW_DICTINFO_DONE_BUTTON_ID 2606 +#define XW_DICTINFO_BEAM_BUTTON_ID 2607 +#define XW_DICTINFO_CANCEL_BUTTON_ID 2608 + +/* + * prefs dialog + */ +#define XW_PREFS_ALLGAMES_GADGET_ID 2700 +#define XW_PREFS_ONEGAME_GADGET_ID 2701 +#define XW_PREFS_TYPES_LIST_ID 2702 + +/* global */ +#define XW_PREFS_PLAYERCOLORS_CHECKBOX_ID 2708 +#define XW_PREFS_PROGRESSBAR_CHECKBOX_ID 2709 +#define XW_PREFS_SHOWGRID_CHECKBOX_ID 2710 +#define XW_PREFS_SHOWARROW_CHECKBOX_ID 2711 +#define XW_PREFS_ROBOTSCORE_CHECKBOX_ID 2712 +#define XW_PREFS_HIDETRAYVAL_CHECKBOX_ID 2713 + +/* per-game */ +#define XW_PREFS_ROBOTSMART_CHECKBOX_ID 2715 +#define XW_PREFS_PHONIES_LABEL_ID 2716 +#define XW_PREFS_PHONIES_TRIGGER_ID 2717 +#define XW_PREFS_BDSIZE_LABEL_ID 2718 +#define XW_PREFS_BDSIZE_SELECTOR_ID 2719 +#define XW_PREFS_NOHINTS_CHECKBOX_ID 2720 +#define XW_PREFS_TIMERON_CHECKBOX_ID 2721 +#define XW_PREFS_TIMER_FIELD_ID 2722 + +#ifdef FEATURE_TRAY_EDIT +# define XW_PREFS_PICKTILES_CHECKBOX_ID 2723 +# ifdef XWFEATURE_SEARCHLIMIT +# define XW_PREFS_HINTRECT_CHECKBOX_ID 2724 +# endif +#else +# ifdef XWFEATURE_SEARCHLIMIT +# define XW_PREFS_HINTRECT_CHECKBOX_ID 2723 +# endif +#endif + +#ifdef XWFEATURE_BLUETOOTH +# define XW_PREFS_BTCONFIRM_CHECKBOX_ID 2725 +#endif + +#ifdef XWFEATURE_FIVEWAY +/* These should be in same order as BoardObjectType */ +# define XW_BOARD_GADGET_ID 3001 +# define XW_SCOREBOARD_GADGET_ID 3002 +# define XW_TRAY_GADGET_ID 3003 +#endif + + +/* These aren't part of the hide/show thing as they're displayed only + * explicitly byother controls */ +#define XW_PREFS_PHONIES_LIST_ID 2750 +#define XW_PREFS_BDSIZE_LIST_ID 2751 + +/* These are used to set/clear the "pages" of the prefs dialog. */ +#define XW_PREFS_FIRST_GLOBAL_ID XW_PREFS_PLAYERCOLORS_CHECKBOX_ID +#define XW_PREFS_LAST_GLOBAL_ID XW_PREFS_HIDETRAYVAL_CHECKBOX_ID +#define XW_PREFS_FIRST_PERGAME_ID XW_PREFS_ROBOTSMART_CHECKBOX_ID + +/* #if defined XWFEATURE_SEARCHLIMIT */ +/* # define XW_PREFS_LAST_PERGAME_ID XW_PREFS_HINTRECT_CHECKBOX_ID */ +/* #elif defined FEATURE_TRAY_EDIT */ +/* # define XW_PREFS_LAST_PERGAME_ID XW_PREFS_PICKTILES_CHECKBOX_ID */ +/* #else */ +/* # define XW_PREFS_LAST_PERGAME_ID XW_PREFS_TIMER_FIELD_ID */ +/* #endif */ + +#define XW_PREFS_LAST_PERGAME_ID XW_PREFS_BTCONFIRM_CHECKBOX_ID + +#define XW_PREFS_CANCEL_BUTTON_ID 2726 +#define XW_PREFS_OK_BUTTON_ID 2727 + +/* + * saved games dialog + */ +#define XW_SAVEDGAMES_LIST_ID 2800 +#define XW_SAVEDGAMES_NAME_FIELD 2801 +#define XW_SAVEDGAMES_USE_BUTTON 2802 +#define XW_SAVEDGAMES_DUPE_BUTTON 2803 +#define XW_SAVEDGAMES_DELETE_BUTTON 2804 +#define XW_SAVEDGAMES_OPEN_BUTTON 2805 +#define XW_SAVEDGAMES_DONE_BUTTON 2806 +#define MAX_GAME_NAME_LENGTH 31 + +/* + * Connections dlg (XW_CONNS_FORM) + */ +#define XW_CONNS_CANCEL_BUTTON_ID 2900 +#define XW_CONNS_OK_BUTTON_ID 2901 +#define XW_CONNS_TYPE_TRIGGER_ID 2902 +#define XW_CONNS_TYPE_LIST_ID 2903 +#ifdef XWFEATURE_RELAY +# define XW_CONNS_RELAY_LABEL_ID 2904 +# define XW_CONNS_COOKIE_FIELD_ID 2905 +# define XW_CONNS_COOKIE_LABEL_ID 2906 +# define XW_CONNS_PORT_LABEL_ID 2907 +# define XW_CONNS_RELAY_FIELD_ID 2908 +# define XW_CONNS_PORT_FIELD_ID 2909 +#endif + +#ifdef XWFEATURE_BLUETOOTH +# define XW_CONNS_BT_HOSTNAME_LABEL_ID 2911 +# define XW_CONNS_BT_HOSTTRIGGER_ID 2912 +# define XW_CONNS_BTCONFIRM_CHECKBOX_ID 2913 +#endif + +/* + * selector for number of tiles during hint + */ +#define XW_HINTCONFIG_MINLIST_ID 2950 +#define XW_HINTCONFIG_MAXLIST_ID 2951 +#define XW_HINTCONFIG_MAXSELECTOR_ID 2952 +#define XW_HINTCONFIG_MINSELECTOR_ID 2953 +#define XW_HINTCONFIG_OK_ID 2954 +#define XW_HINTCONFIG_CANCEL_ID 2955 + +#define PALM_BOARD_TOP 8 +#define PALM_GRIDLESS_BOARD_TOP 2 + +#define PALM_BOARD_SCALE 10 +#define PALM_SCORE_LEFT 0 +#define PALM_SCORE_TOP 0 +#define PALM_SCORE_HEIGHT BOARD_TOP + +#define PALM_GRIDLESS_SCORE_WIDTH 22 +#define PALM_GRIDLESS_SCORE_LEFT (160-PALM_GRIDLESS_SCORE_WIDTH) +#define PALM_GRIDLESS_SCORE_TOP 42 + +#define PALM_TIMER_TOP 0 +#define PALM_TIMER_HEIGHT PALM_SCORE_HEIGHT + +/* #define PALM_TRAY_LEFT 0 */ +#define PALM_TRAY_TOP (160-PALM_TRAY_SCALEV-1) +#define PALM_TRAY_TOP_MAX 144 /* the lowest we can put the top */ +#define PALM_TRAY_WIDTH 143 +#ifdef EIGHT_TILES +#define PALM_TRAY_SCALEV 18 +#else +#define PALM_TRAY_SCALEV 20 +#endif +#define PALM_DIVIDER_WIDTH 3 + +#define PALM_BOARD_LEFT_LH 9 +#define PALM_BOARD_LEFT_RH 0 +#define PALM_TRAY_LEFT_LH 17 +#define PALM_TRAY_LEFT_RH 0 + + +/* resource IDs */ +#define DOWN_ARROW_RESID 2001 +#define RIGHT_ARROW_RESID 2002 +#define FLIP_BUTTON_BMP_RES_ID 2003 +#define VALUE_BUTTON_BMP_RES_ID 2004 +#define HINT_BUTTON_BMP_RES_ID 2005 +#define TRAY_BUTTONS_BMP_RES_ID 2007 +#define SHOWTRAY_BUTTON_BMP_RES_ID 2008 +#define STAR_BMP_RES_ID 2009 + +#ifdef XWFEATURE_BLUETOOTH +# define BTSTATUS_NONE_RES_ID 2010 +# define BTSTATUS_LISTENING_RES_ID 2011 +# define BTSTATUS_SEEKING_RES_ID 2012 +# define BTSTATUS_CONNECTED_RES_ID 2013 +#endif + +#define STRL_RES_TYPE 'StrL' +#define STRL_RES_ID 0x03e8 + +#if 0 +# define DMFINDDATABASE(g,c,n) DmFindDatabase( (c),(n) ) +# define DMOPENDATABASE(g,c,i,m) DmOpenDatabase( (c),(i),(m)) +# define DMCLOSEDATABASE(d) DmCloseDatabase( d ) +#else +# define DMFINDDATABASE(g,c,n) (g)->gamesDBID +# define DMOPENDATABASE(g,c,i,m) (g)->gamesDBP +# define DMCLOSEDATABASE(d) +#endif + +#define kFrmNavHeaderFlagsObjectFocusStartState 0x00000001 +#define kFrmNavHeaderFlagsAppFocusStartState 0x00000002 + +/* versioning stuff */ +#ifdef XWFEATURE_BLUETOOTH +# define XW_PALM_VERSION_STRING "4.3b6" +#else +/* There's a separate branch for 2.4 releases now. */ +# define XW_PALM_VERSION_STRING "4.2.1" +#endif +#define CUR_PREFS_VERS 0x0405 + + + +#endif diff --git a/xwords4/relay/.cvsignore b/xwords4/relay/.cvsignore new file mode 100644 index 000000000..de3791288 --- /dev/null +++ b/xwords4/relay/.cvsignore @@ -0,0 +1 @@ +xwrelay diff --git a/xwords4/relay/Makefile b/xwords4/relay/Makefile new file mode 100644 index 000000000..8b2ffbe6a --- /dev/null +++ b/xwords4/relay/Makefile @@ -0,0 +1,56 @@ +# -*- mode: Makefile; -*- +# Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +CXX = g++ +CC=$(CXX) +SRC = xwrelay.cpp \ + cref.cpp \ + ctrl.cpp \ + tpool.cpp \ + states.cpp \ + timermgr.cpp \ + configs.cpp \ + crefmgr.cpp \ + permid.cpp \ + lstnrmgr.cpp \ + +# STATIC ?= -static + +OBJ = $(patsubst %.cpp,%.o,$(SRC)) +LDFLAGS += -pthread -g -lmcheck $(STATIC) +CPPFLAGS += -DSPAWN_SELF -g -Wall -DSVN_REV=\"$(shell svnversion -n .)\" +# turn on semaphore debugging +# CPPFLAGS += -DDEBUG_LOCKS + +memdebug all: xwrelay + +xwrelay: $(OBJ) + +clean: + rm -f xwrelay $(OBJ) + +tags: + etags *.cpp *.h + +tarball: + tar cvfz RELAY_SRC.tgz \ + ../relay/*.{cpp,h} \ + ../relay/Makefile \ + ../relay/xwrelay.conf + +help: + @echo $(MAKE) [STATIC=\"-static\"] \ No newline at end of file diff --git a/xwords4/relay/configs.cpp b/xwords4/relay/configs.cpp new file mode 100644 index 000000000..d0779ec61 --- /dev/null +++ b/xwords4/relay/configs.cpp @@ -0,0 +1,127 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ + +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include + +#include "configs.h" + +#define MAX_LINE 128 + +#ifndef _HEARTBEAT +# define _HEARTBEAT 60 +#endif + +#ifndef _ALLCONN +# define _ALLCONN 120 +#endif + +RelayConfigs* RelayConfigs::instance = NULL; + + +/* static */ RelayConfigs* +RelayConfigs::GetConfigs() +{ + return instance; +} + +/* static */ void +RelayConfigs::InitConfigs( const char* cfile ) +{ + assert( instance == NULL ); + instance = new RelayConfigs( cfile ); +} + +RelayConfigs::RelayConfigs( const char* cfile ) + : m_allConnInterval(_ALLCONN) + , m_heartbeatInterval(_HEARTBEAT) +{ + /* There's an order here. Open multiple files, if present. File in /etc + is first, but overridden by local file which is in turn overridden by + file passed in. */ + parse( "/etc/xwrelay/xwrelay.conf" ); + parse( "./xwrelay.conf" ); + parse( cfile ); +} /* RelayConfigs::RelayConfigs */ + +void +RelayConfigs::GetPorts( std::vector::const_iterator* iter, std::vector::const_iterator* end) +{ + *iter = m_ports.begin(); + *end = m_ports.end(); +} + +void +RelayConfigs::parse( const char* fname ) +{ + if ( fname != NULL ) { + FILE* f = fopen( fname, "r" ); + if ( f != NULL ) { + logf( XW_LOGINFO, "config: reading from %s", fname ); + char line[MAX_LINE]; + + for ( ; ; ) { + if ( !fgets( line, sizeof(line), f ) ) { + break; + } + + int len = strlen( line ); + if ( line[len-1] == '\n' ) { + line[--len] = '\0'; + } + + if ( len == 0 || line[0] == '#' ) { + continue; + } + + char* value = strchr( line, '=' ); + if ( value == NULL ) { + continue; + } + + *value++ = '\0'; /* terminate "key" substring */ + if ( 0 == strcmp( line, "HEARTBEAT" ) ) { + m_heartbeatInterval = atoi( value ); + } else if ( 0 == strcmp( line, "ALLCONN" ) ) { + m_allConnInterval = atoi( value ); + } else if ( 0 == strcmp( line, "CTLPORT" ) ) { + m_ctrlport = atoi( value ); + } else if ( 0 == strcmp( line, "PORT" ) ) { + m_ports.push_back( atoi( value ) ); + } else if ( 0 == strcmp( line, "NTHREADS" ) ) { + m_nWorkerThreads = atoi( value ); + } else if ( 0 == strcmp( line, "SERVERNAME" ) ) { + m_serverName = value; + } else if ( 0 == strcmp( line, "IDFILE" ) ) { + m_idFileName = value; + } else if ( 0 == strcmp( line, "LOGLEVEL" ) ) { + m_logLevel = atoi(value); + } else { + logf( XW_LOGERROR, "unknown key %s with value %s\n", + line, value ); + assert( 0 ); + } + } + fclose( f ); + } + } +} /* parse */ diff --git a/xwords4/relay/configs.h b/xwords4/relay/configs.h new file mode 100644 index 000000000..864a5c1b9 --- /dev/null +++ b/xwords4/relay/configs.h @@ -0,0 +1,65 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ + +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CONFIGS_H_ +#define _CONFIGS_H_ + +#include +#include +#include "xwrelay_priv.h" + +using namespace std; + +class RelayConfigs { + + public: + static void InitConfigs( const char* confFile ); + static RelayConfigs* GetConfigs(); + + ~RelayConfigs() {} + + void GetPorts( vector::const_iterator* iter, vector::const_iterator* end); + int GetCtrlPort() { return m_ctrlport; } + int GetNWorkerThreads() { return m_nWorkerThreads; } + time_t GetAllConnectedInterval() { return m_allConnInterval; } + time_t GetHeartbeatInterval() { return m_heartbeatInterval; } + const char* GetServerName() { return m_serverName.c_str(); } + const char* GetIdFileName() { return m_idFileName.c_str(); } + int GetLogLevel(void) { return m_logLevel; } + void SetLogLevel(int ll) { m_logLevel = ll; } + + private: + RelayConfigs( const char* cfile ); + void parse( const char* fname ); + + time_t m_allConnInterval; + time_t m_heartbeatInterval; + int m_ctrlport; + vector m_ports; + int m_logLevel; + int m_nWorkerThreads; + string m_serverName; + string m_idFileName; + + static RelayConfigs* instance; +}; + + +#endif diff --git a/xwords4/relay/cref.cpp b/xwords4/relay/cref.cpp new file mode 100644 index 000000000..39778ca86 --- /dev/null +++ b/xwords4/relay/cref.cpp @@ -0,0 +1,862 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ + +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "cref.h" +#include "xwrelay.h" +#include "mlock.h" +#include "tpool.h" +#include "states.h" +#include "timermgr.h" +#include "configs.h" +#include "crefmgr.h" + +using namespace std; + +/***************************************************************************** + * SocketsIterator class + *****************************************************************************/ + +SocketsIterator::SocketsIterator( SocketMap::iterator iter, + SocketMap::iterator end, + pthread_mutex_t* rwlock ) + : m_iter( iter ) + , m_end( end ) + , m_mutex( rwlock ) +{ +} + +SocketsIterator::~SocketsIterator() +{ + pthread_mutex_unlock( m_mutex ); +} + +int +SocketsIterator::Next() +{ + int socket = 0; + if ( m_iter != m_end ) { + socket = m_iter->first; + ++m_iter; + } + return socket; +} + +/***************************************************************************** + * CookieRef class + *****************************************************************************/ + +CookieRef::CookieRef( const char* cookie, const char* connName, CookieID id ) + : m_heatbeat(RelayConfigs::GetConfigs()->GetHeartbeatInterval()) + , m_cookie(cookie==NULL?"":cookie) + , m_connName(connName) + , m_cookieID(id) + , m_totalSent(0) + , m_curState(XWS_INITED) + , m_nextState(XWS_INITED) + , m_eventQueue() + , m_nextHostID(HOST_ID_SERVER) + , m_nPlayersTotal(0) + , m_nPlayersHere(0) +{ + logf( XW_LOGINFO, "creating cref for cookie %s, connName %s", + m_cookie.c_str(), m_connName.c_str() ); +} + +CookieRef::~CookieRef() +{ + cancelAllConnectedTimer(); + + /* get rid of any sockets still contained */ + XWThreadPool* tPool = XWThreadPool::GetTPool(); + + for ( ; ; ) { + map::iterator iter = m_sockets.begin(); + + if ( iter == m_sockets.end() ) { + break; + } + + int socket = iter->second.m_socket; + tPool->CloseSocket( socket ); + m_sockets.erase( iter ); + } + + logf( XW_LOGINFO, "CookieRef for %d being deleted; sent %d bytes", + m_cookieID, m_totalSent ); +} /* ~CookieRef */ + +void +CookieRef::_Connect( int socket, HostID hid, int nPlayersH, int nPlayersT ) +{ + CRefMgr::Get()->Associate( socket, this ); + if ( hid == HOST_ID_NONE ) { + hid = nextHostID(); + logf( XW_LOGINFO, "assigned host id: %x", hid ); + } + pushConnectEvent( socket, hid, nPlayersH, nPlayersT ); + handleEvents(); +} + +void +CookieRef::_Reconnect( int socket, HostID hid, int nPlayersH, int nPlayersT ) +{ + CRefMgr::Get()->Associate( socket, this ); +/* MutexLock ml( &m_EventsMutex ); */ + pushReconnectEvent( socket, hid, nPlayersH, nPlayersT ); + handleEvents(); +} + +void +CookieRef::_Disconnect( int socket, HostID hostID ) +{ + CRefMgr::Get()->Disassociate( socket, this ); + + CRefEvent evt; + evt.type = XWE_DISCONNMSG; + evt.u.discon.socket = socket; + evt.u.discon.srcID = hostID; + m_eventQueue.push_back( evt ); + + handleEvents(); +} + +void +CookieRef::_Shutdown() +{ + CRefEvent evt; + evt.type = XWE_SHUTDOWN; + m_eventQueue.push_back( evt ); + + handleEvents(); +} /* _Shutdown */ + +int +CookieRef::SocketForHost( HostID dest ) +{ + int socket; + map::iterator iter = m_sockets.find( dest ); + if ( iter == m_sockets.end() ) { + socket = -1; + } else { + socket = iter->second.m_socket; + } + logf( XW_LOGVERBOSE0, "returning socket=%d for hostid=%x", socket, dest ); + return socket; +} + +/* The idea here is: have we never seen the XW_ST_ALLCONNECTED state. This + needs to include any states reachable from XW_ST_ALLCONNECTED from which + recovery back to XW_ST_ALLCONNECTED is possible. This is used to decide + whether to admit a connection based on its cookie -- whether that cookie + should join an existing cref or get a new one? */ +int +CookieRef::NeverFullyConnected() +{ + return m_curState != XWS_ALLCONNECTED + && m_curState != XWS_MISSING; +} + +int +CookieRef::AcceptingReconnections( HostID hid, int nPlayersH, int nPlayersT ) +{ + int accept = 0; + /* First, do we have room. Second, are we missing this guy? */ + + if ( m_curState != XWS_INITED + && m_curState != XWS_CONNECTING + && m_curState != XWS_MISSING ) { + /* do nothing; reject */ + logf( XW_LOGINFO, "reject: bad state %s", stateString(m_curState) ); + } else if ( HostKnown( hid ) ) { + logf( XW_LOGINFO, "reject: known hid" ); + /* do nothing: reject */ + } else { + if ( m_nPlayersTotal == 0 ) { + accept = 1; + } else if ( nPlayersH + m_nPlayersHere <= m_nPlayersTotal ) { + accept = 1; + } else { + logf( XW_LOGINFO, "reject: m_nPlayersTotal=%d, m_nPlayersHere=%d", + m_nPlayersTotal, m_nPlayersHere ); + } + } + + return accept; +} /* AcceptingReconnections */ + +void +CookieRef::notifyDisconn( const CRefEvent* evt ) +{ + int socket = evt->u.disnote.socket; + unsigned char buf[] = { + XWRELAY_DISCONNECT_YOU, + evt->u.disnote.why + }; + + send_with_length( socket, buf, sizeof(buf) ); +} /* notifyDisconn */ + +void +CookieRef::removeSocket( int socket ) +{ + int count; + { +/* RWWriteLock rwl( &m_sockets_rwlock ); */ + + count = m_sockets.size(); + assert( count > 0 ); + map::iterator iter = m_sockets.begin(); + while ( iter != m_sockets.end() ) { + if ( iter->second.m_socket == socket ) { + m_sockets.erase(iter); + --count; + break; + } + ++iter; + } + } + + if ( count == 0 ) { + pushLastSocketGoneEvent(); + } +} /* removeSocket */ + +int +CookieRef::HasSocket( int socket ) +{ + int found = 0; + + map::iterator iter = m_sockets.begin(); + while ( iter != m_sockets.end() ) { + if ( iter->second.m_socket == socket ) { + found = 1; + break; + } + ++iter; + } + return found; +} /* HasSocket */ + +#ifdef RELAY_HEARTBEAT +void +CookieRef::_HandleHeartbeat( HostID id, int socket ) +{ +/* MutexLock ml( &m_EventsMutex ); */ + pushHeartbeatEvent( id, socket ); + handleEvents(); +} /* HandleHeartbeat */ + +void +CookieRef::_CheckHeartbeats( time_t now ) +{ + logf( XW_LOGINFO, "CookieRef::_CheckHeartbeats" ); + + map::iterator iter = m_sockets.begin(); + while ( iter != m_sockets.end() ) { + time_t last = iter->second.m_lastHeartbeat; + if ( (now - last) > GetHeartbeat() ) { + pushHeartFailedEvent( iter->second.m_socket ); + } + ++iter; + } + + handleEvents(); +} /* CheckHeartbeats */ +#endif + +void +CookieRef::_Forward( HostID src, HostID dest, unsigned char* buf, int buflen ) +{ + pushForwardEvent( src, dest, buf, buflen ); + handleEvents(); +} /* Forward */ + +void +CookieRef::_Remove( int socket ) +{ + pushRemoveSocketEvent( socket ); + handleEvents(); +} /* Forward */ + +void +CookieRef::pushConnectEvent( int socket, HostID srcID, + int nPlayersH, int nPlayersT ) +{ + CRefEvent evt; + evt.type = XWE_CONNECTMSG; + evt.u.con.socket = socket; + evt.u.con.srcID = srcID; + evt.u.con.nPlayersH = nPlayersH; + evt.u.con.nPlayersT = nPlayersT; + m_eventQueue.push_back( evt ); +} /* pushConnectEvent */ + +void +CookieRef::pushReconnectEvent( int socket, HostID srcID, + int nPlayersH, int nPlayersT ) +{ + CRefEvent evt; + evt.type = XWE_RECONNECTMSG; + evt.u.con.socket = socket; + evt.u.con.srcID = srcID; + evt.u.con.nPlayersH = nPlayersH; + evt.u.con.nPlayersT = nPlayersT; + m_eventQueue.push_back( evt ); +} /* pushReconnectEvent */ + +#ifdef RELAY_HEARTBEAT +void +CookieRef::pushHeartbeatEvent( HostID id, int socket ) +{ + CRefEvent evt; + evt.type = XWE_HEARTRCVD; + evt.u.heart.id = id; + evt.u.heart.socket = socket; + m_eventQueue.push_back( evt ); +} + +void +CookieRef::pushHeartFailedEvent( int socket ) +{ + CRefEvent evt; + evt.type = XWE_HEARTFAILED; + evt.u.heart.socket = socket; + m_eventQueue.push_back( evt ); +} +#endif + +void +CookieRef::pushForwardEvent( HostID src, HostID dest, + unsigned char* buf, int buflen ) +{ + logf( XW_LOGVERBOSE1, "pushForwardEvent: %d -> %d", src, dest ); + CRefEvent evt; + evt.type = XWE_FORWARDMSG; + evt.u.fwd.src = src; + evt.u.fwd.dest = dest; + evt.u.fwd.buf = buf; + evt.u.fwd.buflen = buflen; + m_eventQueue.push_back( evt ); +} + +void +CookieRef::pushRemoveSocketEvent( int socket ) +{ + CRefEvent evt; + evt.type = XWE_REMOVESOCKET; + evt.u.rmsock.socket = socket; + m_eventQueue.push_back( evt ); +} + +void +CookieRef::pushNotifyDisconEvent( int socket, XWREASON why ) +{ + CRefEvent evt; + evt.type = XWE_NOTIFYDISCON; + evt.u.disnote.socket = socket; + evt.u.disnote.why = why; + m_eventQueue.push_back( evt ); +} + +void +CookieRef::pushLastSocketGoneEvent() +{ + CRefEvent evt; + evt.type = XWE_NOMORESOCKETS; + m_eventQueue.push_back( evt ); +} + +void +CookieRef::handleEvents() +{ + /* Assumption: has mutex!!!! */ + while ( m_eventQueue.size () > 0 ) { + CRefEvent evt = m_eventQueue.front(); + m_eventQueue.pop_front(); + + XW_RELAY_ACTION takeAction; + if ( getFromTable( m_curState, evt.type, &takeAction, &m_nextState ) ) { + + logf( XW_LOGINFO, "%s: %s -> %s on evt %s, act=%s", __func__, + stateString(m_curState), stateString(m_nextState), + eventString(evt.type), actString(takeAction) ); + + switch( takeAction ) { + + case XWA_CHKCOUNTS: + checkCounts( &evt ); + break; + + case XWA_SEND_1ST_RSP: + case XWA_SEND_1ST_RERSP: + setAllConnectedTimer(); + increasePlayerCounts( &evt ); + sendResponse( &evt, takeAction == XWA_SEND_1ST_RSP ); + break; + + case XWA_SEND_RSP: + case XWA_SEND_RERSP: + increasePlayerCounts( &evt ); + sendResponse( &evt, takeAction == XWA_SEND_RSP ); + break; + + case XWA_FWD: + forward( &evt ); + break; + + case XWA_TIMERDISCONN: + disconnectSockets( 0, XWRELAY_ERROR_TIMEOUT ); + break; + + case XWA_SHUTDOWN: + disconnectSockets( 0, XWRELAY_ERROR_SHUTDOWN ); + break; + + case XWA_HEARTDISCONN: + notifyOthers( evt.u.heart.socket, XWRELAY_DISCONNECT_OTHER, + XWRELAY_ERROR_HEART_OTHER ); + setAllConnectedTimer(); + reducePlayerCounts( evt.u.discon.socket ); + disconnectSockets( evt.u.heart.socket, + XWRELAY_ERROR_HEART_YOU ); + break; + + case XWA_DISCONNECT: + setAllConnectedTimer(); + reducePlayerCounts( evt.u.discon.socket ); + removeSocket( evt.u.discon.socket ); + /* Don't notify. This is a normal part of a game ending. */ + break; + + case XWA_NOTEHEART: + noteHeartbeat( &evt ); + break; + + case XWA_NOTIFYDISCON: + notifyDisconn( &evt ); + break; + + case XWA_REMOVESOCKET: + setAllConnectedTimer(); + reducePlayerCounts( evt.u.rmsock.socket ); + notifyOthers( evt.u.rmsock.socket, XWRELAY_DISCONNECT_OTHER, + XWRELAY_ERROR_LOST_OTHER ); + removeSocket( evt.u.rmsock.socket ); + break; + + case XWA_SENDALLHERE: + case XWA_SNDALLHERE_2: + cancelAllConnectedTimer(); + sendAllHere( takeAction == XWA_SENDALLHERE ); + break; + + case XWA_REJECT: + + case XWA_NONE: + /* nothing to do for these */ + break; + + + default: + assert(0); + break; + } + + m_curState = m_nextState; + } + } +} /* handleEvents */ + +void +CookieRef::send_with_length( int socket, unsigned char* buf, int bufLen ) +{ + SocketWriteLock slock( socket ); + if ( slock.socketFound() ) { + + if ( send_with_length_unsafe( socket, buf, bufLen ) ) { + RecordSent( bufLen, socket ); + } else { + /* ok that the slock above is still in scope */ + killSocket( socket, "couldn't send" ); + } + } +} + +static void +putNetShort( unsigned char** bufpp, unsigned short s ) +{ + s = htons( s ); + memcpy( *bufpp, &s, sizeof(s) ); + *bufpp += sizeof(s); +} + +void +CookieRef::increasePlayerCounts( const CRefEvent* evt ) +{ + int nPlayersH = evt->u.con.nPlayersH; + int nPlayersT = evt->u.con.nPlayersT; + HostID hid = evt->u.con.srcID; + + logf( XW_LOGVERBOSE1, "increasePlayerCounts: hid=%d, nPlayersH=%d, nPlayersT=%d", + hid, nPlayersH, nPlayersT ); + + if ( hid == HOST_ID_SERVER ) { + assert( m_nPlayersTotal == 0 ); + m_nPlayersTotal = nPlayersT; + } else { + assert( nPlayersT == 0 ); /* should catch this earlier!!! */ + assert( m_nPlayersTotal == 0 || m_nPlayersHere <= m_nPlayersTotal ); + } + m_nPlayersHere += nPlayersH; + + logf( XW_LOGVERBOSE1, "increasePlayerCounts: here=%d; total=%d", + m_nPlayersHere, m_nPlayersTotal ); + + CRefEvent newevt; + newevt.type = (m_nPlayersHere == m_nPlayersTotal) ? + XWE_ALLHERE : XWE_SOMEMISSING; + m_eventQueue.push_back( newevt ); +} /* increasePlayerCounts */ + +void +CookieRef::reducePlayerCounts( int socket ) +{ + logf( XW_LOGVERBOSE1, "reducePlayerCounts on socket %d", socket ); + map::iterator iter = m_sockets.begin(); + while ( iter != m_sockets.end() ) { + + if ( iter->second.m_socket == socket ) { + assert( iter->first != 0 ); + + if ( iter->first == HOST_ID_SERVER ) { + m_nPlayersTotal = 0; + } else { + assert( iter->second.m_nPlayersT == 0 ); + } + m_nPlayersHere -= iter->second.m_nPlayersH; + + logf( XW_LOGVERBOSE1, "reducePlayerCounts: m_nPlayersHere=%d; m_nPlayersTotal=%d", + m_nPlayersHere, m_nPlayersTotal ); + + break; + } + ++iter; + } +} /* reducePlayerCounts */ + +/* Determine if adding this device to the game would give us too many + players. */ +void +CookieRef::checkCounts( const CRefEvent* evt ) +{ + int nPlayersH = evt->u.con.nPlayersH; +/* int nPlayersT = evt->u.con.nPlayersT; */ + HostID hid = evt->u.con.srcID; + int success; + + logf( XW_LOGVERBOSE1, "checkCounts: hid=%d, nPlayers=%d, m_nPlayersTotal = %d, " + "m_nPlayersHere = %d", + hid, nPlayersH, m_nPlayersTotal, m_nPlayersHere ); + + if ( hid == HOST_ID_SERVER ) { + success = m_nPlayersTotal == 0; + } else { + success = (m_nPlayersTotal == 0) /* if no server present yet */ + || (m_nPlayersTotal >= m_nPlayersHere + nPlayersH); + } + logf( XW_LOGVERBOSE1, "success = %d", success ); + + CRefEvent newevt; + if ( success ) { + newevt = *evt; + newevt.type = XWE_OKTOSEND; + } else { + newevt.type = XWE_COUNTSBAD; + } + m_eventQueue.push_back( newevt ); +} /* checkCounts */ + +void +CookieRef::setAllConnectedTimer() +{ + time_t inHowLong; + inHowLong = RelayConfigs::GetConfigs()->GetAllConnectedInterval(); + TimerMgr::GetTimerMgr()->SetTimer( inHowLong, + s_checkAllConnected, this, 0 ); +} + +void +CookieRef::cancelAllConnectedTimer() +{ + TimerMgr::GetTimerMgr()->ClearTimer( s_checkAllConnected, this ); +} + +void +CookieRef::sendResponse( const CRefEvent* evt, int initial ) +{ + int socket = evt->u.con.socket; + HostID id = evt->u.con.srcID; + int nPlayersH = evt->u.con.nPlayersH; + int nPlayersT = evt->u.con.nPlayersT; + + assert( id != HOST_ID_NONE ); + logf( XW_LOGINFO, "remembering pair: hostid=%x, socket=%d", id, socket ); + HostRec hr(socket, nPlayersH, nPlayersT); + m_sockets.insert( pair(id,hr) ); + + /* Now send the response */ + unsigned char buf[1 + /* cmd */ + sizeof(short) + /* heartbeat */ + sizeof(CookieID) + + 1 /* hostID */ + ]; + + unsigned char* bufp = buf; + + *bufp++ = initial ? XWRELAY_CONNECT_RESP : XWRELAY_RECONNECT_RESP; + putNetShort( &bufp, GetHeartbeat() ); + putNetShort( &bufp, GetCookieID() ); + logf( XW_LOGVERBOSE0, "writing hostID of %d into msg", id ); + *bufp++ = (char)id; + + send_with_length( socket, buf, bufp - buf ); + logf( XW_LOGVERBOSE0, "sent XWRELAY_CONNECTRESP" ); +} /* sendResponse */ + +void +CookieRef::forward( const CRefEvent* evt ) +{ + unsigned char* buf = evt->u.fwd.buf; + int buflen = evt->u.fwd.buflen; + HostID dest = evt->u.fwd.dest; + + int destSocket = SocketForHost( dest ); + + if ( destSocket != -1 ) { + /* This is an ugly hack!!!! */ + *buf = XWRELAY_MSG_FROMRELAY; + send_with_length( destSocket, buf, buflen ); + + /* also note that we've heard from src recently */ +#ifdef RELAY_HEARTBEAT + HostID src = evt->u.fwd.src; + pushHeartbeatEvent( src, SocketForHost(src) ); +#endif + } else { + /* We're not really connected yet! */ + } +} /* forward */ + +void +CookieRef::send_msg( int socket, HostID id, XWRelayMsg msg, XWREASON why ) +{ + unsigned char buf[10]; + short tmp; + int len = 0; + buf[len++] = msg; + + switch ( msg ) { + case XWRELAY_DISCONNECT_OTHER: + buf[len++] = why; + tmp = htons( id ); + memcpy( &buf[len], &tmp, 2 ); + len += 2; + break; + default: + logf( XW_LOGINFO, "not handling message %d", msg ); + assert(0); + } + + send_with_length( socket, buf, sizeof(buf) ); +} /* send_msg */ + +void +CookieRef::notifyOthers( int socket, XWRelayMsg msg, XWREASON why ) +{ + assert( socket != 0 ); + +/* RWReadLock ml( &m_sockets_rwlock ); */ + + map::iterator iter = m_sockets.begin(); + while ( iter != m_sockets.end() ) { + int other = iter->second.m_socket; + if ( other != socket ) { + send_msg( other, iter->first, msg, why ); + } + ++iter; + } +} /* notifyOthers */ + +void +CookieRef::sendAllHere( int includeName ) +{ + unsigned char buf[1 + 1 + MAX_CONNNAME_LEN]; + unsigned char* bufp = buf; + + *bufp++ = XWRELAY_ALLHERE; + *bufp++ = includeName? 1 : 0; + + if ( includeName ) { + const char* connName = ConnName(); + int len = strlen( connName ); + assert( len < MAX_CONNNAME_LEN ); + *bufp++ = (char)len; + memcpy( bufp, connName, len ); + bufp += len; + } + + map::iterator iter = m_sockets.begin(); + while ( iter != m_sockets.end() ) { + send_with_length( iter->second.m_socket, buf, bufp-buf ); + ++iter; + } +} /* sendAllHere */ + +void +CookieRef::disconnectSockets( int socket, XWREASON why ) +{ + if ( socket == 0 ) { +/* RWReadLock ml( &m_sockets_rwlock ); */ + map::iterator iter = m_sockets.begin(); + while ( iter != m_sockets.end() ) { + assert( iter->second.m_socket != 0 ); + disconnectSockets( iter->second.m_socket, why ); + ++iter; + } + } else { + pushNotifyDisconEvent( socket, why ); + pushRemoveSocketEvent( socket ); + } +} /* disconnectSockets */ + +void +CookieRef::noteHeartbeat( const CRefEvent* evt ) +{ + int socket = evt->u.heart.socket; + HostID id = evt->u.heart.id; + +/* RWWriteLock rwl( &m_sockets_rwlock ); */ + + map::iterator iter = m_sockets.find(id); + if ( iter == m_sockets.end() ) { + logf( XW_LOGERROR, "no socket for HostID %x", id ); + } else { + + /* PENDING If the message came on an unexpected socket, kill the + connection. An attack is the most likely explanation. */ + assert( iter->second.m_socket == socket ); + + logf( XW_LOGVERBOSE1, "upping m_lastHeartbeat from %d to %d", + iter->second.m_lastHeartbeat, now() ); + iter->second.m_lastHeartbeat = now(); + } +} /* noteHeartbeat */ + +/* timer callback */ +/* static */ void +CookieRef::s_checkAllConnected( void* closure ) +{ + /* Need to ensure */ + CookieRef* self = (CookieRef*)closure; + SafeCref scr(self); + scr.CheckAllConnected(); +} + +void +CookieRef::_CheckAllConnected() +{ + logf( XW_LOGVERBOSE0, "checkAllConnected" ); +/* MutexLock ml( &m_EventsMutex ); */ + CRefEvent newEvt; + newEvt.type = XWE_CONNTIMER; + m_eventQueue.push_back( newEvt ); + handleEvents(); +} + + +void +CookieRef::logf( XW_LogLevel level, const char* format, ... ) +{ + char buf[256]; + int len; + + len = snprintf( buf, sizeof(buf), "cid:%d ", m_cookieID ); + + va_list ap; + va_start( ap, format ); + vsnprintf( buf + len, sizeof(buf) - len, format, ap ); + va_end(ap); + + ::logf( level, buf ); +} + +void +CookieRef::_PrintCookieInfo( string& out ) +{ + out += "Cookie="; + out += Cookie(); + out += "\n"; + out += "connName="; + char buf[MAX_CONNNAME_LEN+MAX_COOKIE_LEN]; + + snprintf( buf, sizeof(buf), "%s\n", ConnName() ); + out += buf; + + snprintf( buf, sizeof(buf), "id=%d\n", GetCookieID() ); + out += buf; + + snprintf( buf, sizeof(buf), "Bytes sent=%d\n", m_totalSent ); + out += buf; + + snprintf( buf, sizeof(buf), "Total players=%d\n", m_nPlayersTotal ); + out += buf; + snprintf( buf, sizeof(buf), "Players here=%d\n", m_nPlayersHere ); + out += buf; + + snprintf( buf, sizeof(buf), "State=%s\n", stateString( m_curState ) ); + out += buf; + + /* Timer state: how long since last heartbeat; how long til disconn timer + fires. */ + + /* n messages */ + /* open since when */ + + snprintf( buf, sizeof(buf), "Hosts connected=%d; cur time = %ld\n", + m_sockets.size(), now() ); + out += buf; + map::iterator iter = m_sockets.begin(); + while ( iter != m_sockets.end() ) { + snprintf( buf, sizeof(buf), " HostID=%d; socket=%d;last hbeat=%ld\n", + iter->first, iter->second.m_socket, + iter->second.m_lastHeartbeat ); + out += buf; + ++iter; + } + +} /* PrintCookieInfo */ diff --git a/xwords4/relay/cref.h b/xwords4/relay/cref.h new file mode 100644 index 000000000..e3890b639 --- /dev/null +++ b/xwords4/relay/cref.h @@ -0,0 +1,210 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ + +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef _CREF_H_ +#define _CREF_H_ + +#include +#include +#include +#include +#include +#include "xwrelay_priv.h" +#include "xwrelay.h" +#include "states.h" + +using namespace std; + +class CookieMapIterator; /* forward */ + +class HostRec { + public: + HostRec(int socket, int nPlayersH, int nPlayersT) + : m_socket(socket) + , m_nPlayersH(nPlayersH) + , m_nPlayersT(nPlayersT) + , m_lastHeartbeat(now()) + {} + ~HostRec() {} + int m_socket; + int m_nPlayersH; + int m_nPlayersT; + time_t m_lastHeartbeat; +}; + +class CookieRef { + + private: + /* These classes have access to CookieRef. All others should go through + SafeCref instances. */ + friend class CRefMgr; + friend class SafeCref; + friend class CookieMapIterator; + + CookieRef( const char* cookie, const char* connName, CookieID id ); + ~CookieRef(); + + /* Within this cookie, remember that this hostID and socket go together. + If the hostID is HOST_ID_SERVER, it's the server. */ + CookieID GetCookieID() { return m_cookieID; } + + int HostKnown( HostID host ) { return -1 != SocketForHost( host ); } + int CountSockets() { return m_sockets.size(); } + int HasSocket( int socket ); + const char* Cookie() { return m_cookie.c_str(); } + const char* ConnName() { return m_connName.c_str(); } + + short GetHeartbeat() { return m_heatbeat; } + int SocketForHost( HostID dest ); + + int NeverFullyConnected(); + int AcceptingReconnections( HostID hid, int nPlayersH, int nPlayersT ); + + /* for console */ + void _PrintCookieInfo( string& out ); + void PrintSocketInfo( string& out, int socket ); + + static CookieMapIterator GetCookieIterator(); + + /* Nuke an existing */ + static void Delete( CookieID id ); + static void Delete( const char* name ); + + void _Connect( int socket, HostID srcID, int nPlayersH, int nPlayersT ); + void _Reconnect( int socket, HostID srcID, int nPlayersH, int nPlayersT ); + void _Disconnect(int socket, HostID hostID ); + void _Shutdown(); + void _HandleHeartbeat( HostID id, int socket ); + void _CheckHeartbeats( time_t now ); + void _Forward( HostID src, HostID dest, unsigned char* buf, int buflen ); + void _Remove( int socket ); + void _CheckAllConnected(); + + int ShouldDie() { return m_curState == XWS_DEAD; } + + void logf( XW_LogLevel level, const char* format, ... ); + + typedef struct CRefEvent { + XW_RELAY_EVENT type; + union { + struct { + HostID src; + HostID dest; + unsigned char* buf; + int buflen; + } fwd; + struct { + int socket; + int nPlayersH; + int nPlayersT; + HostID srcID; + } con; + struct { + int socket; + HostID srcID; + } discon; + struct { + HostID id; + int socket; + } heart; + struct { + time_t now; + } htime; + struct { + int socket; + } rmsock; + struct { + int socket; + XWREASON why; + } disnote; + } u; + } CRefEvent; + + void send_with_length( int socket, unsigned char* buf, int bufLen ); + void send_msg( int socket, HostID id, XWRelayMsg msg, XWREASON why ); + + void RecordSent( int nBytes, int socket ) { + m_totalSent += nBytes; + } + + void pushConnectEvent( int socket, HostID srcID, + int nPlayersH, int nPlayersT ); + void pushReconnectEvent( int socket, HostID srcID, + int nPlayersH, int nPlayersT ); + void pushHeartbeatEvent( HostID id, int socket ); + void pushHeartFailedEvent( int socket ); + + void pushForwardEvent( HostID src, HostID dest, unsigned char* buf, + int buflen ); + void pushDestBadEvent(); + void pushLastSocketGoneEvent(); + void pushRemoveSocketEvent( int socket ); + void pushNotifyDisconEvent( int socket, XWREASON why ); + + void handleEvents(); + + void sendResponse( const CRefEvent* evt, int initial ); + void increasePlayerCounts( const CRefEvent* evt ); + void reducePlayerCounts( int socket ); + void checkCounts( const CRefEvent* evt ); + + void setAllConnectedTimer(); + void cancelAllConnectedTimer(); + + void forward( const CRefEvent* evt ); + void checkFromServer( const CRefEvent* evt ); + void notifyOthers( int socket, XWRelayMsg msg, XWREASON why ); + + void disconnectSockets( int socket, XWREASON why ); + void noteHeartbeat(const CRefEvent* evt); + void notifyDisconn(const CRefEvent* evt); + void removeSocket( int socket ); + void sendAllHere( int includeName ); + + HostID nextHostID() { return ++m_nextHostID; } + /* timer callback */ + static void s_checkAllConnected( void* closure ); + + map m_sockets; +/* pthread_rwlock_t m_sockets_rwlock; */ + + short m_heatbeat; /* might change per carrier or something. */ + string m_cookie; /* cookie used for initial connections */ + string m_connName; /* globally unique name */ + CookieID m_cookieID; /* Unique among current games on this server */ + int m_totalSent; + + /* Guard the event queue. Only one thread at a time can post to the + queue, but once in a thread can post new events while processing + current ones. */ +/* pthread_mutex_t m_EventsMutex; */ + + + XW_RELAY_STATE m_curState; + XW_RELAY_STATE m_nextState; + deque m_eventQueue; + + HostID m_nextHostID; + int m_nPlayersTotal; + int m_nPlayersHere; +}; /* CookieRef */ + +#endif diff --git a/xwords4/relay/crefmgr.cpp b/xwords4/relay/crefmgr.cpp new file mode 100644 index 000000000..55b3e808f --- /dev/null +++ b/xwords4/relay/crefmgr.cpp @@ -0,0 +1,614 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ + +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include + +#include "crefmgr.h" +#include "cref.h" +#include "mlock.h" +#include "permid.h" +#include "configs.h" +#include "timermgr.h" + +class SocketStuff { + public: + SocketStuff( CookieRef* cref ) + : m_cref(cref) + { + pthread_mutex_init( &m_writeMutex, NULL ); + } + ~SocketStuff() { pthread_mutex_destroy( &m_writeMutex ); } + CookieRef* m_cref; + pthread_mutex_t m_writeMutex; /* so only one thread writes at a time */ +}; + +static CRefMgr* s_instance = NULL; + +/* static */ CRefMgr* +CRefMgr::Get() +{ + if ( s_instance == NULL ) { + s_instance = new CRefMgr(); + } + return s_instance; +} /* Get */ + +CRefMgr::CRefMgr() + : m_nextCID(0) +{ + /* should be using pthread_once() here */ + pthread_mutex_init( &m_guard, NULL ); + pthread_mutex_init( &m_SocketStuffMutex, NULL ); + pthread_rwlock_init( &m_cookieMapRWLock, NULL ); +} + +CRefMgr::~CRefMgr() +{ + assert( this == s_instance ); + + pthread_mutex_destroy( &m_guard ); + pthread_rwlock_destroy( &m_cookieMapRWLock ); + + s_instance = NULL; +} + +void +CRefMgr::CloseAll() +{ + /* Get every cref instance, shut it down */ + + RWWriteLock rwl( &m_cookieMapRWLock ); + CookieMap::iterator iter = m_cookieMap.begin(); + while ( iter != m_cookieMap.end() ) { + CookieRef* cref = iter->second; + { + SafeCref scr( cref ); + scr.Shutdown(); + } + ++iter; + } + +} /* CloseAll */ + +CookieRef* +CRefMgr::FindOpenGameFor( const char* cORn, int isCookie, + HostID hid, int nPlayersH, int nPlayersT ) +{ + logf( XW_LOGINFO, "FindOpenGameFor with %s", cORn ); + CookieRef* cref = NULL; + RWReadLock rwl( &m_cookieMapRWLock ); + + CookieMap::iterator iter = m_cookieMap.begin(); + while ( iter != m_cookieMap.end() ) { + cref = iter->second; + if ( isCookie ) { + if ( 0 == strcmp( cref->Cookie(), cORn ) ) { + if ( cref->NeverFullyConnected() ) { + break; + } + } + } else { + if ( 0 == strcmp( cref->ConnName(), cORn ) ) { + if ( cref->AcceptingReconnections( hid, + nPlayersH, nPlayersH ) ) { + break; + } + } + } + ++iter; + } + + return (iter == m_cookieMap.end()) ? NULL : cref; +} /* FindOpenGameFor */ + +CookieID +CRefMgr::nextCID( const char* connName ) +{ + /* Later may want to guarantee that wrap-around doesn't cause an overlap. + But that's really only a theoretical possibility. */ + return ++m_nextCID; +} /* nextCID */ + +CookieID +CRefMgr::cookieIDForConnName( const char* connName ) +{ + CookieID cid = 0; + /* for now, just walk the existing data structure and see if the thing's + in use. If it isn't, return a new id. */ + + RWReadLock rwl( &m_cookieMapRWLock ); + + CookieMap::iterator iter = m_cookieMap.begin(); + while ( iter != m_cookieMap.end() ) { + CookieRef* cref = iter->second; + if ( 0 == strcmp( cref->ConnName(), connName ) ) { + cid = iter->first; + break; + } + ++iter; + } + + return cid; +} /* cookieIDForConnName */ + +CookieRef* +CRefMgr::getMakeCookieRef_locked( const char* cORn, int isCookie, HostID hid, + int nPlayersH, int nPlayersT ) +{ + CookieRef* cref; + +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "tlm %p", &m_guard ); +#endif + pthread_mutex_lock( &m_guard ); +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "slm %p", &m_guard ); +#endif + + /* We have a cookie from a new connection. This may be the first time + it's been seen, or there may be a game currently in the + XW_ST_CONNECTING state. So we need to look up the cookie first, then + generate new connName and cookieIDs if it's not found. */ + + cref = FindOpenGameFor( cORn, isCookie, hid, nPlayersH, nPlayersT ); + if ( cref == NULL ) { + string s; + const char* connName; + const char* cookie = NULL; + if ( isCookie ) { + cookie = cORn; + s = PermID::GetNextUniqueID(); + connName = s.c_str(); + } else { + connName = cORn; + } + + CookieID cid = cookieIDForConnName( connName ); + if ( cid == 0 ) { + cid = nextCID( connName ); + } + + cref = AddNew( cookie, connName, cid ); + } + + if ( cref == NULL ) { +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "ULM %p", &m_guard ); +#endif + pthread_mutex_unlock( &m_guard ); + } + + return cref; +} /* getMakeCookieRef_locked */ + +void +CRefMgr::Associate( int socket, CookieRef* cref ) +{ + MutexLock ml( &m_SocketStuffMutex ); + SocketMap::iterator iter = m_SocketStuff.find( socket ); + if ( iter != m_SocketStuff.end() ) { + logf( XW_LOGINFO, "replacing existing cref/threadID pair for socket %d", socket ); + } + + SocketStuff* stuff = new SocketStuff( cref ); + m_SocketStuff.insert( pair< int, SocketStuff* >( socket, stuff ) ); +} + +void +CRefMgr::Disassociate( int socket, CookieRef* cref ) +{ + MutexLock ml( &m_SocketStuffMutex ); + SocketMap::iterator iter = m_SocketStuff.find( socket ); + if ( iter == m_SocketStuff.end() ) { + logf( XW_LOGERROR, "can't find cref/threadID pair for socket %d", socket ); + } else { + SocketStuff* stuff = iter->second; + assert( stuff->m_cref == cref ); + delete stuff; + m_SocketStuff.erase( iter ); + } +} + +pthread_mutex_t* +CRefMgr::GetWriteMutexForSocket( int socket ) +{ + MutexLock ml( &m_SocketStuffMutex ); + SocketMap::iterator iter = m_SocketStuff.find( socket ); + if ( iter != m_SocketStuff.end() ) { + SocketStuff* stuff = iter->second; + return &stuff->m_writeMutex; + } + logf( XW_LOGERROR, "GetWriteMutexForSocket: not found" ); + return NULL; +} /* GetWriteMutexForSocket */ + +void +CRefMgr::RemoveSocketRefs( int socket ) +{ + SafeCref scr( socket ); + scr.Remove( socket ); +} + +void +CRefMgr::PrintSocketInfo( int socket, string& out ) +{ + SafeCref scr( socket ); + const char* name = scr.Cookie(); + if ( name != NULL && name[0] != '\0' ) { + char buf[64]; + + snprintf( buf, sizeof(buf), "* socket: %d\n", socket ); + out += buf; + + snprintf( buf, sizeof(buf), " in cookie: %s\n", name ); + out += buf; + } +} + +/* static */ SocketsIterator +CRefMgr::MakeSocketsIterator() +{ + pthread_mutex_lock( &m_SocketStuffMutex ); + SocketsIterator iter( m_SocketStuff.begin(), m_SocketStuff.end(), + &m_SocketStuffMutex ); + return iter; +} + +CookieRef* +CRefMgr::getCookieRef_locked( CookieID cookieID ) +{ +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "tlm %p", &m_guard ); +#endif + pthread_mutex_lock( &m_guard ); +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "slm %p", &m_guard ); +#endif + + CookieRef* cref = getCookieRef_impl( cookieID ); + + if ( cref == NULL ) { +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "ULM %p", &m_guard ); +#endif + pthread_mutex_unlock( &m_guard ); + } + + return cref; +} /* getCookieRef_locked */ + +CookieRef* +CRefMgr::getCookieRef_locked( int socket ) +{ +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "tlm %p", &m_guard ); +#endif + pthread_mutex_lock( &m_guard ); +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "slm %p", &m_guard ); +#endif + CookieRef* cref = NULL; + + CookieMap::iterator iter = m_cookieMap.begin(); + while ( iter != m_cookieMap.end() ) { + CookieRef* tmp = iter->second; + if ( tmp->HasSocket( socket ) ) { + cref = tmp; + break; + } + ++iter; + } + + if ( cref == NULL ) { +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "ULM %p", &m_guard ); +#endif + pthread_mutex_unlock( &m_guard ); + } + + return cref; +} /* getCookieRef_locked */ + +int +CRefMgr::checkCookieRef_locked( CookieRef* cref ) +{ + int exists = 1; + assert( cref != NULL ); + +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "tlm %p", &m_guard ); +#endif + pthread_mutex_lock( &m_guard ); +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "slm %p", &m_guard ); +#endif + + pthread_mutex_t* cref_mutex = m_crefMutexes[cref]; + logf( XW_LOGINFO, "checkCookieRef_locked: cref_mutex=%p", cref_mutex ); + + if ( cref_mutex == NULL ) { +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "ULM %p", &m_guard ); +#endif + pthread_mutex_unlock( &m_guard ); + exists = 0; + } + + return exists; +} /* checkCookieRef_locked */ + +int +CRefMgr::LockCref( CookieRef* cref ) +{ + /* assertion: m_guard is locked */ + + pthread_mutex_t* cref_mutex = m_crefMutexes[cref]; + if ( cref_mutex == NULL ) { + cref_mutex = (pthread_mutex_t*)malloc( sizeof( *cref_mutex ) ); + pthread_mutex_init( cref_mutex, NULL ); + m_crefMutexes[cref] = cref_mutex; + } +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "tlm %p", cref_mutex ); +#endif + pthread_mutex_lock( cref_mutex ); +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "slm %p", cref_mutex ); +#endif + +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "ULM %p", &m_guard ); +#endif + pthread_mutex_unlock( &m_guard ); + return 1; +} /* LockCref */ + +void +CRefMgr::UnlockCref( CookieRef* cref ) +{ + pthread_mutex_t* cref_mutex = m_crefMutexes[cref]; +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "ULM %p", cref_mutex ); +#endif + pthread_mutex_unlock( cref_mutex ); +} + +#ifdef RELAY_HEARTBEAT +/* static */ void +CRefMgr::heartbeatProc( void* closure ) +{ + CRefMgr* self = (CRefMgr*)closure; + self->checkHeartbeats( now() ); +} /* heartbeatProc */ +#endif + +CookieRef* +CRefMgr::AddNew( const char* cookie, const char* connName, CookieID id ) +{ + CookieRef* exists = getCookieRef_impl( id ); + assert( exists == NULL ); + + RWWriteLock rwl( &m_cookieMapRWLock ); + logf( XW_LOGINFO, "making new cref: %d", id ); + CookieRef* ref = new CookieRef( cookie, connName, id ); + m_cookieMap.insert( pair(ref->GetCookieID(), ref ) ); + logf( XW_LOGINFO, "paired cookie %s/connName %s with id %d", + (cookie?cookie:"NULL"), connName, ref->GetCookieID() ); + +#ifdef RELAY_HEARTBEAT + if ( m_cookieMap.size() == 1 ) { + RelayConfigs* cfg = RelayConfigs::GetConfigs(); + short heartbeat = cfg->GetHeartbeatInterval(); + TimerMgr::GetTimerMgr()->SetTimer( heartbeat, heartbeatProc, this, + heartbeat ); + } +#endif + + return ref; +} /* AddNew */ + +void +CRefMgr::Delete( CookieRef* cref ) +{ + RWWriteLock rwl( &m_cookieMapRWLock ); + + CookieMap::iterator iter = m_cookieMap.begin(); + while ( iter != m_cookieMap.end() ) { + CookieRef* ref = iter->second; + if ( ref == cref ) { + logf( XW_LOGINFO, "erasing cref" ); + m_cookieMap.erase( iter ); + break; + } + ++iter; + } + + pthread_mutex_t* cref_mutex = m_crefMutexes[cref]; +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "ULM %p", cref_mutex ); +#endif + pthread_mutex_unlock( cref_mutex ); + pthread_mutex_destroy( cref_mutex ); + free( cref_mutex ); + + map::iterator iter2; + iter2 = m_crefMutexes.find(cref); + m_crefMutexes.erase( iter2 ); + + delete cref; + +#ifdef RELAY_HEARTBEAT + if ( m_cookieMap.size() == 0 ) { + TimerMgr::GetTimerMgr()->ClearTimer( heartbeatProc, this ); + } +#endif + + logf( XW_LOGINFO, "CRefMgr::Delete done" ); +} + +void +CRefMgr::Delete( CookieID id ) +{ + CookieRef* cref = getCookieRef_impl( id ); + if ( cref != NULL ) { + Delete( cref ); + } +} /* Delete */ + +void +CRefMgr::Delete( const char* connName ) +{ + CookieID id = cookieIDForConnName( connName ); + Delete( id ); +} /* Delete */ + +CookieRef* +CRefMgr::getCookieRef_impl( CookieID cookieID ) +{ + CookieRef* ref = NULL; + RWReadLock rwl( &m_cookieMapRWLock ); + + CookieMap::iterator iter = m_cookieMap.find( cookieID ); + while ( iter != m_cookieMap.end() ) { + CookieRef* sec = iter->second; + if ( sec->GetCookieID() == cookieID ) { + ref = sec; + break; + } + ++iter; + } + return ref; +} + +#ifdef RELAY_HEARTBEAT +void +CRefMgr::checkHeartbeats( time_t now ) +{ + vector crefs; + + { + RWReadLock rwl( &m_cookieMapRWLock ); + CookieMap::iterator iter = m_cookieMap.begin(); + while ( iter != m_cookieMap.end() ) { + crefs.push_back(iter->second); + ++iter; + } + } + + unsigned int i; + for ( i = 0; i < crefs.size(); ++i ) { + SafeCref scr( crefs[i] ); + scr.CheckHeartbeats( now ); + } +} /* checkHeartbeats */ +#endif + +/* static */ CookieMapIterator +CRefMgr::GetCookieIterator() +{ + CookieMapIterator iter; + return iter; +} + + +CookieMapIterator::CookieMapIterator() + : _iter( CRefMgr::Get()->m_cookieMap.begin() ) +{ +} + +CookieID +CookieMapIterator::Next() +{ + CookieID id = 0; + if ( _iter != CRefMgr::Get()->m_cookieMap.end() ) { + CookieRef* cref = _iter->second; + id = cref->GetCookieID(); + ++_iter; + } + return id; +} + +////////////////////////////////////////////////////////////////////////////// +// SafeCref +////////////////////////////////////////////////////////////////////////////// + +SafeCref::SafeCref( const char* cORn, int isCookie, HostID hid, + int nPlayersH, int nPlayersT ) + : m_cref( NULL ) + , m_mgr( CRefMgr::Get() ) +{ + CookieRef* cref; + + cref = m_mgr->getMakeCookieRef_locked( cORn, isCookie, hid, + nPlayersH, nPlayersT ); + if ( cref != NULL ) { + if ( m_mgr->LockCref( cref ) ) { + m_cref = cref; + } + } +} + +SafeCref::SafeCref( CookieID connID ) + : m_cref( NULL ) + , m_mgr( CRefMgr::Get() ) +{ + CookieRef* cref = m_mgr->getCookieRef_locked( connID ); + if ( cref != NULL ) { + if ( m_mgr->LockCref( cref ) ) { + m_cref = cref; + } + } +} + +SafeCref::SafeCref( int socket ) + : m_cref( NULL ) + , m_mgr( CRefMgr::Get() ) +{ + CookieRef* cref = m_mgr->getCookieRef_locked( socket ); + if ( cref != NULL ) { + if ( m_mgr->LockCref( cref ) ) { + m_cref = cref; + } + } +} + +SafeCref::SafeCref( CookieRef* cref ) + : m_cref( NULL ) + , m_mgr( CRefMgr::Get() ) +{ + if ( m_mgr->checkCookieRef_locked( cref ) ) { + if ( m_mgr->LockCref( cref ) ) { + m_cref = cref; + } + } +} + +SafeCref::~SafeCref() +{ + if ( m_cref != NULL ) { + if ( m_cref->ShouldDie() ) { + m_mgr->Delete( m_cref ); + } else { + m_mgr->UnlockCref( m_cref ); + } + } +} diff --git a/xwords4/relay/crefmgr.h b/xwords4/relay/crefmgr.h new file mode 100644 index 000000000..5f5b1b33f --- /dev/null +++ b/xwords4/relay/crefmgr.h @@ -0,0 +1,228 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ + +/* + * Copyright 2005-2007 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CREFMGR_H_ +#define _CREFMGR_H_ + +#include "cref.h" + +typedef map CookieMap; +class CookieMapIterator; +class SocketStuff; +typedef map< int, SocketStuff* > SocketMap; + +class SocketsIterator { + public: + SocketsIterator( SocketMap::iterator iter, SocketMap::iterator end, + pthread_mutex_t* mutex ); + ~SocketsIterator(); + int Next(); + private: + SocketMap::iterator m_iter; + SocketMap::iterator m_end; + pthread_mutex_t* m_mutex; /* locked */ +}; + + +class CRefMgr { + /* Maintain access to CookieRef instances, ultimately to ensure that no + single instance is being acted on by more than one thread at a time, + and that once one is destroyed no additional threads attempt to access + it. + */ + + public: + static CRefMgr* Get(); + + CRefMgr(); + ~CRefMgr(); + + void CloseAll(); + + CookieMapIterator GetCookieIterator(); + + /* PENDING. These need to go through SafeCref */ + void Delete( CookieID id ); + void Delete( CookieRef* cref ); + void Delete( const char* connName ); + CookieID CookieIdForName( const char* name ); + + /* For use from ctrl only!!!! */ + void LockAll() { pthread_rwlock_wrlock( &m_cookieMapRWLock ); } + void UnlockAll() { pthread_rwlock_unlock( &m_cookieMapRWLock ); } + + /* Track sockets independent of cookie refs */ + void Associate( int socket, CookieRef* cref ); + void Disassociate( int socket, CookieRef* cref ); + pthread_mutex_t* GetWriteMutexForSocket( int socket ); + void RemoveSocketRefs( int socket ); + void PrintSocketInfo( int socket, string& out ); + SocketsIterator MakeSocketsIterator(); + + private: + friend class SafeCref; + CookieRef* getMakeCookieRef_locked( const char* cORn, int isCookie, + HostID hid, + int nPlayersH, int nPlayersT ); + CookieRef* getCookieRef_locked( CookieID cookieID ); + CookieRef* getCookieRef_locked( int socket ); + int checkCookieRef_locked( CookieRef* cref ); + CookieRef* getCookieRef_impl( CookieID cookieID ); + CookieRef* AddNew( const char* cookie, const char* connName, CookieID id ); + CookieRef* FindOpenGameFor( const char* cORn, int isCookie, + HostID hid, int nPlayersH, int nPlayersT ); + + CookieID cookieIDForConnName( const char* connName ); + CookieID nextCID( const char* connName ); + + static void heartbeatProc( void* closure ); + void checkHeartbeats( time_t now ); + + CookieID m_nextCID; + + int LockCref( CookieRef* cref ); + void UnlockCref( CookieRef* cref ); + + pthread_mutex_t m_guard; + map m_crefMutexes; + + pthread_rwlock_t m_cookieMapRWLock; + CookieMap m_cookieMap; + + pthread_mutex_t m_SocketStuffMutex; + SocketMap m_SocketStuff; + + friend class CookieMapIterator; +}; /* CRefMgr */ + + +class SafeCref { + + /* Stack-based class that keeps more than one thread from accessing a + CookieRef instance at a time. */ + + public: + SafeCref( const char* cookieOrConnName, int cookie, HostID hid, + int nPlayersH, int nPlayersT ); + SafeCref( CookieID cid ); + SafeCref( int socket ); + SafeCref( CookieRef* cref ); + ~SafeCref(); + + int Forward( HostID src, HostID dest, unsigned char* buf, int buflen ) { + if ( IsValid() ) { + m_cref->_Forward( src, dest, buf, buflen ); + return 1; + } else { + return 0; + } + } + int Connect( int socket, HostID srcID, int nPlayersH, int nPlayersT ) { + if ( IsValid() ) { + m_cref->_Connect( socket, srcID, nPlayersH, nPlayersT ); + return 1; + } else { + return 0; + } + } + int Reconnect( int socket, HostID srcID, int nPlayersH, int nPlayersT ) { + if ( IsValid() ) { + m_cref->_Reconnect( socket, srcID, nPlayersH, nPlayersT ); + return 1; + } else { + return 0; + } + } + void Disconnect(int socket, HostID hostID ) { + if ( IsValid() ) { + m_cref->_Disconnect( socket, hostID ); + } + } + void Shutdown() { + if ( IsValid() ) { + m_cref->_Shutdown(); + } + } + void Remove( int socket ) { + if ( IsValid() ) { + m_cref->_Remove( socket ); + } + } + +#ifdef RELAY_HEARTBEAT + int HandleHeartbeat( HostID id, int socket ) { + if ( IsValid() ) { + m_cref->_HandleHeartbeat( id, socket ); + return 1; + } else { + return 0; + } + } + void CheckHeartbeats( time_t now ) { + if ( IsValid() ) { + m_cref->_CheckHeartbeats( now ); + } + } +#endif + + void PrintCookieInfo( string& out ) { + if ( IsValid() ) { + m_cref->_PrintCookieInfo( out ); + } + } + void CheckAllConnected() { + if ( IsValid() ) { + m_cref->_CheckAllConnected(); + } + } + const char* Cookie() { + if ( IsValid() ) { + return m_cref->Cookie(); + } else { + return ""; /* so don't crash.... */ + } + } + const char* ConnName() { + if ( IsValid() ) { + return m_cref->ConnName(); + } else { + return ""; /* so don't crash.... */ + } + } + + + private: + int IsValid() { return m_cref != NULL; } + + CookieRef* m_cref; + CRefMgr* m_mgr; +}; + + +class CookieMapIterator { + public: + CookieMapIterator(); + ~CookieMapIterator() {} + CookieID Next(); + private: + CookieMap::const_iterator _iter; +}; + +#endif diff --git a/xwords4/relay/ctrl.cpp b/xwords4/relay/ctrl.cpp new file mode 100644 index 000000000..319bef00f --- /dev/null +++ b/xwords4/relay/ctrl.cpp @@ -0,0 +1,600 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ + +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include /* gethostbyname */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ctrl.h" +#include "cref.h" +#include "crefmgr.h" +#include "mlock.h" +#include "xwrelay_priv.h" +#include "configs.h" +#include "lstnrmgr.h" + +/* this is *only* for testing. Don't abuse!!!! */ +extern pthread_rwlock_t gCookieMapRWLock; + +/* Return of true means exit the ctrl thread */ +typedef bool (*CmdPtr)( int socket, const char** args ); + +typedef struct FuncRec { + const char* name; + CmdPtr func; +} FuncRec; + +vector g_ctrlSocks; +pthread_mutex_t g_ctrlSocksMutex = PTHREAD_MUTEX_INITIALIZER; + + +static bool cmd_quit( int socket, const char** args ); +static bool cmd_print( int socket, const char** args ); +static bool cmd_lock( int socket, const char** args ); +static bool cmd_help( int socket, const char** args ); +static bool cmd_start( int socket, const char** args ); +static bool cmd_stop( int socket, const char** args ); +static bool cmd_kill_eject( int socket, const char** args ); +static bool cmd_get( int socket, const char** args ); +static bool cmd_set( int socket, const char** args ); +static bool cmd_shutdown( int socket, const char** args ); +static bool cmd_rev( int socket, const char** args ); +static bool cmd_uptime( int socket, const char** args ); +static bool cmd_crash( int socket, const char** args ); + +static int +match( string* cmd, const char * const* first, int incr, int count ) +{ + int cmdlen = cmd->length(); + int nFound = 0; + const char* cmdFound = NULL; + int which = -1; + int i; + for ( i = 0; (i < count) && (nFound <= 1); ++i ) { + if ( 0 == strncmp( cmd->c_str(), *first, cmdlen ) ) { + ++nFound; + which = i; + cmdFound = *first; + } + first = (char* const*)(((char*)first) + incr); + } + + if ( nFound == 1 ) { + cmd->assign(cmdFound); + } else { + which = -1; + } + return which; +} + +static void +print_to_sock( int sock, bool addCR, const char* what, ... ) +{ + char buf[256]; + + va_list ap; + va_start( ap, what ); + vsnprintf( buf, sizeof(buf) - 1, what, ap ); + va_end(ap); + + if ( addCR ) { + strncat( buf, "\n", sizeof(buf) ); + } + send( sock, buf, strlen(buf), 0 ); +} + +static const FuncRec gFuncs[] = { + { "?", cmd_help }, + { "crash", cmd_crash }, + { "eject", cmd_kill_eject }, + { "get", cmd_get }, + { "help", cmd_help }, + { "kill", cmd_kill_eject }, + { "lock", cmd_lock }, + { "print", cmd_print }, + { "quit", cmd_quit }, + { "rev", cmd_rev }, + { "set", cmd_set }, + { "shutdown", cmd_shutdown }, + { "start", cmd_start }, + { "stop", cmd_stop }, + { "uptime", cmd_uptime }, +}; + +static bool +cmd_quit( int socket, const char** args ) +{ + if ( 0 == strcmp( "help", args[1] ) ) { + print_to_sock( socket, true, "* %s (disconnect from ctrl port)", + args[0] ); + return false; + } else { + print_to_sock( socket, true, "bye bye" ); + return true; + } +} + +static void +print_cookies( int socket, CookieID theID ) +{ + CRefMgr* cmgr = CRefMgr::Get(); + CookieMapIterator iter = cmgr->GetCookieIterator(); + CookieID id; + for ( id = iter.Next(); id != 0; id = iter.Next() ) { + if ( theID == 0 || theID == id ) { + SafeCref scr( id ); + string s; + scr.PrintCookieInfo( s ); + + print_to_sock( socket, true, s.c_str() ); + } + } +} + +static bool +cmd_start( int socket, const char** args ) +{ + print_to_sock( socket, true, "* %s (unimplemented)", args[0] ); + return false; +} + +static bool +cmd_stop( int socket, const char** args ) +{ + print_to_sock( socket, true, "* %s (unimplemented)", args[0] ); + return false; +} + +static bool +cmd_kill_eject( int socket, const char** args ) +{ + int found = 0; + int isKill = 0 == strcmp( args[0], "kill" ); + + if ( 0 == strcmp( args[1], "socket" ) ) { + int victim = atoi( args[2] ); + if ( victim != 0 ) { + killSocket( victim, "ctrl command" ); + found = 1; + } + } else if ( 0 == strcmp( args[1], "cref" ) ) { + const char* idhow = args[2]; + const char* id = args[3]; + if ( idhow != NULL && id != NULL ) { + if ( 0 == strcmp( idhow, "name" ) ) { + CRefMgr::Get()->Delete( id ); + found = 1; + } else if ( 0 == strcmp( idhow, "id" ) ) { + CRefMgr::Get()->Delete( atoi( id ) ); + found = 1; + } + } + } else if ( 0 == strcmp( args[1], "relay" ) ) { + print_to_sock( socket, true, "not yet unimplemented" ); + } + + const char* expl = isKill? + "silently remove from game" + : "remove from game with error to device"; + if ( !found ) { + const char* msg = + "* %s socket -- %s\n" + " %s cref connName \n" + " %s cref id " + ; + print_to_sock( socket, true, msg, args[0], expl, args[0], args[0] ); + } + return false; +} /* cmd_kill_eject */ + +static bool +cmd_get( int socket, const char** args ) +{ + bool needsHelp = true; + + string attr(args[1]); + const char* const attrs[] = { "help", "listeners", "loglevel" }; + int index = match( &attr, attrs, sizeof(attrs[0]), + sizeof(attrs)/sizeof(attrs[0])); + + switch( index ) { + case 0: + break; + case 1: { + char buf[128]; + int len = 0; + ListenersIter iter(&g_listeners, false); + for ( ; ; ) { + int listener = iter.next(); + if ( listener == -1 ) { + break; + } + len += snprintf( &buf[len], sizeof(buf)-len, "%d,", listener ); + } + print_to_sock( socket, true, "%s", buf ); + needsHelp = false; + } + break; + case 2: { + RelayConfigs* rc = RelayConfigs::GetConfigs(); + if ( NULL != rc ) { + print_to_sock( socket, true, "loglevel=%d\n", + rc->GetLogLevel() ); + needsHelp = false; + } else { + logf( XW_LOGERROR, "RelayConfigs::GetConfigs() => NULL" ); + } + } + break; + + default: + print_to_sock( socket, true, "unknown or ambiguous attribute: %s", attr.c_str() ); + } + + if ( needsHelp ) { + /* includes help */ + print_to_sock( socket, false, + "* %s -- lists all attributes (unimplemented)\n" + "* %s listener\n" + "* %s loglevel\n" + , args[0], args[0], args[0] ); + } + + return false; +} /* cmd_get */ + +static bool +cmd_set( int socket, const char** args ) +{ + const char* val = args[2]; + const char* const attrs[] = { "help", "listeners", "loglevel" }; + string attr(args[1]); + int index = match( &attr, attrs, sizeof(attrs[0]), + sizeof(attrs)/sizeof(attrs[0])); + + bool needsHelp = true; + switch( index ) { + case 1: + if ( NULL != val && val[0] != '\0' ) { + istringstream str( val ); + vector sv; + while ( !str.eof() ) { + int sock; + char comma; + str >> sock >> comma; + logf( XW_LOGERROR, "%s: read %d", __func__, sock ); + sv.push_back( sock ); + } + g_listeners.SetAll( &sv ); + needsHelp = false; + } + break; + case 2: + if ( NULL != val && val[0] != '\0' ) { + RelayConfigs* rc = RelayConfigs::GetConfigs(); + if ( rc != NULL ) { + rc->SetLogLevel( atoi(val) ); + needsHelp = false; + } + } + break; + default: + break; + } + + if ( needsHelp ) { + print_to_sock( socket, true, + "* %s listeners ,[,..,]\n" + "* %s loglevel " + ,args[0], args[0] ); + } + return false; +} + +static bool +cmd_rev( int socket, const char** args ) +{ + if ( 0 == strcmp( args[1], "help" ) ) { + print_to_sock( socket, true, + "* %s -- prints svn rev number of build", + args[0] ); + } else { + print_to_sock( socket, true, "svn rev: %s", SVN_REV ); + } + return false; +} + +static bool +cmd_uptime( int socket, const char** args ) +{ + if ( 0 == strcmp( args[1], "help" ) ) { + print_to_sock( socket, true, + "* %s -- prints how long the relay's been running", + args[0] ); + } else { + time_t seconds = now(); + + int days = seconds / (24*60*60); + seconds %= (24*60*60); + + int hours = seconds / (60*60); + seconds %= (60*60); + + int minutes = seconds / 60; + seconds %= 60; + + print_to_sock( socket, true, + "uptime: %d days, %d hours, %d minutes, %ld seconds", + days, hours, minutes, seconds ); + } + return false; +} + +static bool +cmd_crash( int socket, const char** args ) +{ + if ( 0 == strcmp( args[1], "help" ) ) { + print_to_sock( socket, true, + "* %s -- fires an assert (debug case) or divides-by-zero", + args[0] ); + } else { + assert(0); + int i = 1; + while ( i > 0 ) --i; + return 6/i > 0; + } + return false; +} + +static bool +cmd_shutdown( int socket, const char** args ) +{ + print_to_sock( socket, true, + "* %s -- shuts down relay (exiting main) (unimplemented)", + args[0] ); + return false; +} + +static void +print_cookies( int socket, const char* cookie, const char* connName ) +{ + CookieMapIterator iter = CRefMgr::Get()->GetCookieIterator(); + CookieID id; + + for ( id = iter.Next(); id != 0; id = iter.Next() ) { + SafeCref scr( id ); + if ( cookie != NULL && 0 == strcmp( scr.Cookie(), cookie ) ) { + /* print this one */ + } else if ( connName != NULL && + 0 == strcmp( scr.ConnName(), connName ) ) { + /* print this one */ + } else { + continue; + } + string s; + scr.PrintCookieInfo( s ); + + print_to_sock( socket, true, s.c_str() ); + } +} + +static void +print_socket_info( int out, int which ) +{ + string s; + CRefMgr::Get()->PrintSocketInfo( which, s ); + print_to_sock( out, 1, s.c_str() ); +} + +static void +print_sockets( int out, int sought ) +{ + SocketsIterator iter = CRefMgr::Get()->MakeSocketsIterator(); + int sock; + while ( (sock = iter.Next()) != 0 ) { + if ( sought == 0 || sought == sock ) { + print_socket_info( out, sock ); + } + } +} + +static bool +cmd_print( int socket, const char** args ) +{ + logf( XW_LOGINFO, "cmd_print called" ); + int found = 0; + if ( 0 == strcmp( "cref", args[1] ) ) { + if ( 0 == strcmp( "all", args[2] ) ) { + print_cookies( socket, (CookieID)0 ); + found = 1; + } else if ( 0 == strcmp( "cookie", args[2] ) ) { + print_cookies( socket, args[3], NULL ); + found = 1; + } else if ( 0 == strcmp( "connName", args[2] ) ) { + print_cookies( socket, NULL, args[3] ); + found = 1; + } else if ( 0 == strcmp( "id", args[2] ) ) { + print_cookies( socket, atoi(args[3]) ); + found = 1; + } + } else if ( 0 == strcmp( "socket", args[1] ) ) { + if ( 0 == strcmp( "all", args[2] ) ) { + print_sockets( socket, 0 ); + found = 1; + } else if ( 0 == strcmp( "id", args[2] ) ) { + print_sockets( socket, atoi(args[3]) ); + found = 1; + } + } + + if ( !found ) { + const char* str = + "* %s cref all\n" + " %s cref name \n" + " %s cref connName \n" + " %s cref id \n" + " %s socket all\n" + " %s socket -- print info about crefs and sockets"; + print_to_sock( socket, true, str, + args[0], args[0], args[0], args[0], args[0], args[0] ); + } + return 0; +} /* cmd_print */ + +static bool +cmd_lock( int socket, const char** args ) +{ + CRefMgr* mgr = CRefMgr::Get(); + if ( 0 == strcmp( "on", args[1] ) ) { + mgr->LockAll(); + } else if ( 0 == strcmp( "off", args[1] ) ) { + mgr->UnlockAll(); + } else { + print_to_sock( socket, true, "* %s [on|off] -- lock/unlock access mutex", + args[0] ); + } + + return 0; +} /* cmd_lock */ + +static bool +cmd_help( int socket, const char** args ) +{ + if ( 0 == strcmp( "help", args[1] ) ) { + print_to_sock( socket, true, "* %s -- prints this", args[0] ); + } else { + + const char* help[] = { NULL, "help", NULL, NULL }; + const FuncRec* fp = gFuncs; + const FuncRec* last = fp + (sizeof(gFuncs) / sizeof(gFuncs[0])); + while ( fp < last ) { + help[0] = fp->name; + (*fp->func)( socket, help ); + ++fp; + } + } + return 0; +} + +static void +print_prompt( int socket ) +{ + print_to_sock( socket, false, "=> " ); +} + +static void* +ctrl_thread_main( void* arg ) +{ + int sock = (int)arg; + + { + MutexLock ml( &g_ctrlSocksMutex ); + g_ctrlSocks.push_back( sock ); + } + + for ( ; ; ) { + string cmd, arg1, arg2, arg3; + print_prompt( sock ); + + char buf[512]; + ssize_t nGot = recv( sock, buf, sizeof(buf)-1, 0 ); + if ( nGot <= 1 ) { /* break when just \n comes in */ + break; + } else if ( nGot > 2 ) { + /* if nGot is 2, reuse prev string */ + buf[nGot] = '\0'; + istringstream s( buf ); + s >> cmd >> arg1 >> arg2 >> arg3; + } + + int index = match( &cmd, (char*const*)&gFuncs[0].name, sizeof(gFuncs[0]), + sizeof(gFuncs)/sizeof(gFuncs[0]) ); + const char* args[] = { + cmd.c_str(), + arg1.c_str(), + arg2.c_str(), + arg3.c_str() + }; + if ( index == -1 ) { + print_to_sock( sock, 1, "unknown or ambiguous command: \"%s\"", cmd.c_str() ); + (void)cmd_help( sock, args ); + } else if ( (*gFuncs[index].func)( sock, args ) ) { + break; + } + } + + close ( sock ); + + MutexLock ml( &g_ctrlSocksMutex ); + vector::iterator iter = g_ctrlSocks.begin(); + while ( iter != g_ctrlSocks.end() ) { + if ( *iter == sock ) { + g_ctrlSocks.erase(iter); + break; + } + } + return NULL; +} /* ctrl_thread_main */ + +void +run_ctrl_thread( int ctrl_sock ) +{ + logf( XW_LOGINFO, "calling accept on socket %d\n", ctrl_sock ); + + sockaddr newaddr; + socklen_t siz = sizeof(newaddr); + int newSock = accept( ctrl_sock, &newaddr, &siz ); + logf( XW_LOGINFO, "got one for ctrl: %d", newSock ); + + pthread_t thread; + int result = pthread_create( &thread, NULL, + ctrl_thread_main, (void*)newSock ); + pthread_detach( thread ); + + assert( result == 0 ); +} + +void +stop_ctrl_threads() +{ + MutexLock ml( &g_ctrlSocksMutex ); + vector::iterator iter = g_ctrlSocks.begin(); + while ( iter != g_ctrlSocks.end() ) { + int sock = *iter++; + print_to_sock( sock, 1, "relay going down..." ); + close( sock ); + } +} diff --git a/xwords4/relay/ctrl.h b/xwords4/relay/ctrl.h new file mode 100644 index 000000000..96de4bdbe --- /dev/null +++ b/xwords4/relay/ctrl.h @@ -0,0 +1,28 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ + +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef _CTRL_H_ +#define _CTRL_H_ + +void run_ctrl_thread( int ctrl_sock ); +void stop_ctrl_threads(); + +#endif diff --git a/xwords4/relay/lstnrmgr.cpp b/xwords4/relay/lstnrmgr.cpp new file mode 100644 index 000000000..dc7b6efe8 --- /dev/null +++ b/xwords4/relay/lstnrmgr.cpp @@ -0,0 +1,203 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ + +/* + * Copyright 2007 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include + +#include "lstnrmgr.h" +#include "mlock.h" + +bool +ListenerMgr::AddListener( int port ) +{ + logf( XW_LOGINFO, "%s(%d)", __func__, port ); + MutexLock ml( &m_mutex ); + return addOne( port ); +} + +void +ListenerMgr::SetAll( const vector* ivp ) +{ + logf( XW_LOGINFO, "%s", __func__ ); + MutexLock ml( &m_mutex ); + + vector have; + map::iterator iter2 = m_socks_to_ports.begin(); + while ( iter2 != m_socks_to_ports.end() ) { + have.push_back(iter2->second); + ++iter2; + } + std::sort(have.begin(), have.end()); + + vector want = *ivp; + std::sort(want.begin(), want.end()); + + /* Now go through both lists in order, removing and adding as + appropriate. */ + size_t iWant = 0; + size_t iHave = 0; + while ( (iHave < have.size()) || (iWant < want.size()) ) { + assert( iHave <= have.size() && iWant <= want.size() ); + while( (iWant < want.size()) + && ((iHave == have.size() || want[iWant] < have[iHave])) ) { + addOne( want[iWant] ); + ++iWant; + } + while ( (iHave < have.size()) + && (iWant == want.size() || (have[iHave] < want[iWant])) ) { + removePort( have[iHave] ); + ++iHave; + } + while ( (iHave < have.size()) && (iWant < want.size()) + && (have[iHave] == want[iWant]) ) { + /* keep both */ + ++iWant; ++iHave; + } + } + +} /* SetAll */ + +/* void */ +/* ListenerMgr::RemoveListener( int listener ) */ +/* { */ +/* MutexLock ml( &m_mutex ); */ +/* removeFD( listener ); */ +/* } */ + +void +ListenerMgr::RemoveAll() +{ + MutexLock ml( &m_mutex ); + for ( ; ; ) { + map::const_iterator iter = m_socks_to_ports.begin(); + if ( iter == m_socks_to_ports.end() ) { + break; + } + removeSocket( iter->first ); + } +} + +void +ListenerMgr::AddToFDSet( fd_set* rfds ) +{ + MutexLock ml( &m_mutex ); + map::const_iterator iter = m_socks_to_ports.begin(); + while ( iter != m_socks_to_ports.end() ) { + FD_SET( iter->first, rfds ); + ++iter; + } +} + +int +ListenerMgr::GetHighest() +{ + int highest = 0; + MutexLock ml( &m_mutex ); + map::const_iterator iter = m_socks_to_ports.begin(); + while ( iter != m_socks_to_ports.end() ) { + if ( iter->first > highest ) { + highest = iter->first; + } + ++iter; + } + return highest; +} + +bool +ListenerMgr::PortInUse( int port ) +{ + MutexLock ml( &m_mutex ); + return portInUse( port ); +} + +void +ListenerMgr::removeSocket( int sock ) +{ + /* Assumption: we have the mutex! */ + logf( XW_LOGINFO, "%s(%d)", __func__, sock ); + map::iterator iter = m_socks_to_ports.find( sock ); + assert( iter != m_socks_to_ports.end() ); + m_socks_to_ports.erase(iter); + close(sock); +} + +void +ListenerMgr::removePort( int port ) +{ + /* Assumption: we have the mutex! */ + logf( XW_LOGINFO, "%s(%d)", __func__, port ); + map::iterator iter = m_socks_to_ports.begin(); + while ( iter != m_socks_to_ports.end() ) { + if ( iter->second == port ) { + int sock = iter->first; + close(sock); + m_socks_to_ports.erase(iter); + break; + } + ++iter; + } + assert( iter != m_socks_to_ports.end() ); /* we must have found it! */ +} + +bool +ListenerMgr::addOne( int port ) +{ + logf( XW_LOGINFO, "%s(%d)", __func__, port ); + /* Assumption: we have the mutex! */ + assert( !portInUse(port) ); + bool success = false; + int sock = make_socket( INADDR_ANY, port ); + success = sock != -1; + if ( success ) { + m_socks_to_ports.insert( pair(sock, port) ); + } + return success; +} + +bool +ListenerMgr::portInUse( int port ) +{ + /* Assumption: we have the mutex! */ + bool found = false; + map::const_iterator iter = m_socks_to_ports.begin(); + while ( iter != m_socks_to_ports.end() ) { + if ( iter->second == port ) { + found = true; + break; + } + ++iter; + } + return found; +} + +int +ListenersIter::next() +{ + int result = -1; + if ( m_iter != m_lm->m_socks_to_ports.end() ) { + result = m_fds? m_iter->first : m_iter->second; + ++m_iter; + } +/* logf( XW_LOGINFO, "%s=>%d", __func__, result ); */ + return result; +} + diff --git a/xwords4/relay/lstnrmgr.h b/xwords4/relay/lstnrmgr.h new file mode 100644 index 000000000..9416e2383 --- /dev/null +++ b/xwords4/relay/lstnrmgr.h @@ -0,0 +1,73 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ + +/* + * Copyright 2007 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _LSTNRMGR_H_ +#define _LSTNRMGR_H_ + +#include +#include +#include +#include "xwrelay_priv.h" + +using namespace std; + +class ListenerMgr { + public: + void RemoveAll(); +/* void RemoveListener( int listener ); */ + bool AddListener( int port ); + void SetAll( const vector* iv ); /* replace current set with this new one */ + void AddToFDSet( fd_set* rfds ); + int GetHighest(); + bool PortInUse( int port ); + + private: + void removeSocket( int sock ); + void removePort( int port ); + bool addOne( int listener ); + bool portInUse( int port ); + + map m_socks_to_ports; + pthread_mutex_t m_mutex; + friend class ListenersIter; +}; + +class ListenersIter { + public: + ListenersIter(ListenerMgr* lm, bool fds) { + m_fds = fds; + m_lm = lm; + pthread_mutex_lock( &m_lm->m_mutex ); + m_iter = lm->m_socks_to_ports.begin(); + } + + ~ListenersIter() { + pthread_mutex_unlock( &m_lm->m_mutex ); + } + + int next(); + + private: + bool m_fds; + map::const_iterator m_iter; + ListenerMgr* m_lm; +}; + +#endif diff --git a/xwords4/relay/mlock.h b/xwords4/relay/mlock.h new file mode 100644 index 000000000..9e9af3224 --- /dev/null +++ b/xwords4/relay/mlock.h @@ -0,0 +1,130 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ + +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _MLOCK_H_ +#define _MLOCK_H_ + +#include + +#include "xwrelay_priv.h" +#include "cref.h" +#include "crefmgr.h" + +class MutexLock { + public: + MutexLock( pthread_mutex_t* mutex ) { + m_mutex = mutex; +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "tlm %p", mutex ); +#endif + pthread_mutex_lock( mutex ); +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "slm %p", mutex ); +#endif + } + ~MutexLock() { +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "ULM %p", m_mutex ); +#endif + pthread_mutex_unlock( m_mutex ); + } + + private: + pthread_mutex_t* m_mutex; +}; + +class SocketWriteLock { + public: + SocketWriteLock( int socket ) + : m_socket( socket ) + , m_mutex( CRefMgr::Get()->GetWriteMutexForSocket( socket ) ) + { +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "tlm %p for socket %d", m_mutex, socket ); +#endif + if ( m_mutex != NULL ) { + pthread_mutex_lock( m_mutex ); + } +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "slm %p for socket %d", m_mutex, socket ); +#endif + } + + ~SocketWriteLock() { +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "ULM %p for socket %d", m_mutex, m_socket ); +#endif + if ( m_mutex != NULL ) { + pthread_mutex_unlock( m_mutex ); + } + } + + int socketFound() { return (int)(m_mutex != NULL); } + + private: + int m_socket; + pthread_mutex_t* m_mutex; +}; + +class RWReadLock { + public: + RWReadLock( pthread_rwlock_t* rwl ) { +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "tlrr %p", rwl ); +#endif + pthread_rwlock_rdlock( rwl ); +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "slrr %p", rwl ); +#endif + _rwl = rwl; + } + ~RWReadLock() { + pthread_rwlock_unlock( _rwl ); +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "ULRR %p", _rwl ); +#endif + } + + private: + pthread_rwlock_t* _rwl; +}; + +class RWWriteLock { + public: + RWWriteLock( pthread_rwlock_t* rwl ) : _rwl(rwl) { +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "tlww %p", rwl ); +#endif + pthread_rwlock_wrlock( rwl ); +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "slww %p", rwl ); +#endif + } + ~RWWriteLock() { + pthread_rwlock_unlock( _rwl ); +#ifdef DEBUG_LOCKS + logf( XW_LOGINFO, "ULWW %p", _rwl ); +#endif + } + private: + pthread_rwlock_t* _rwl; +}; + +#endif diff --git a/xwords4/relay/permid.cpp b/xwords4/relay/permid.cpp new file mode 100644 index 000000000..7bb23bc85 --- /dev/null +++ b/xwords4/relay/permid.cpp @@ -0,0 +1,77 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ + +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "permid.h" + +#include +#include +#include "mlock.h" + +using namespace std; + +pthread_mutex_t PermID::s_guard = PTHREAD_MUTEX_INITIALIZER; +string PermID::s_serverName; +string PermID::s_idFileName; + +string +PermID::GetNextUniqueID() +{ + const char* fileName = s_idFileName.c_str(); + MutexLock ml( &s_guard ); + + string s = s_serverName; + assert( s.length() > 0 ); + s += ":"; + + char buf[32]; /* should last for a while :-) */ + + FILE* f = fopen( fileName, "r+" ); + if ( f ) { + fscanf( f, "%s\n", buf ); + rewind( f ); + } else { + f = fopen( fileName, "w" ); + assert ( f != NULL ); + buf[0] = '0'; + buf[1] = '\0'; + } + + int n = atoi(buf) + 1; + sprintf( buf, "%d", n ); + + fprintf( f, "%s\n", buf ); + fclose( f ); + + s += buf; + + return s; +} + +/* static */ void +PermID::SetServerName( const char* name ) +{ + s_serverName = name; +} + +/* static */ void +PermID::SetIDFileName( const char* name ) +{ + s_idFileName = name; +} diff --git a/xwords4/relay/permid.h b/xwords4/relay/permid.h new file mode 100644 index 000000000..0136c4ad0 --- /dev/null +++ b/xwords4/relay/permid.h @@ -0,0 +1,50 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ + +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef _PERMID_H +#define _PERMID_H + +#include +#include + +/* Able to get a unique ID for every game ever started. A simple file + * somewhere stores an ascii string representing a number. That's + * incremented, saved and returned each time. In a world with multiple relays + * (multiple machines) the ID could include a host identifier to guarantee + * uniqueness. + */ +class PermID { + public: + static void SetServerName( const char* name ); + static void SetIDFileName( const char* name ); + static std::string GetNextUniqueID(); + + private: + static pthread_mutex_t s_guard; /* guard access to the whole + process */ + static std::string s_serverName; /* All ID's generated start with + this, which is supposed to be + unique to this relay + instance. */ + static std::string s_idFileName; /* The incremented part of the + name is stored where? */ +}; +#endif diff --git a/xwords4/relay/states.cpp b/xwords4/relay/states.cpp new file mode 100644 index 000000000..ceed6eede --- /dev/null +++ b/xwords4/relay/states.cpp @@ -0,0 +1,236 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "assert.h" +#include "states.h" +#include "xwrelay_priv.h" + +typedef struct StateTable { + XW_RELAY_STATE stateStart; + XW_RELAY_EVENT stateEvent; + XW_RELAY_ACTION stateAction; + XW_RELAY_STATE stateEnd; /* Do I need this? Or does the code that + performs the action determine the state? */ +} StateTable; + +/* Connecting. The problem is that we don't know how many devices to expect. + So hosts connect and get a response. Hosts send messages to be forwarded. + We may or may not have an ID for the recipient host. If we do, we forward. + If we don't, we drop. No big deal. So is there any difference between + CONNECTING and CONNECTED? I don't think so. Messages come in and we try + to forward. Connection requests come in and we accept them if the host in + question is unknown. NOT QUITE. There's a DOS vulnerability there. It's + best if we can put the game in a state where others can't connect, if the + window where new devices can sign in using a given cookie is fairly small. + + New rules on accepting connections and reconnections: + + - Connect and reconnect messages contain nPlayersHere (local) and + nPlayersTotal params. + + - On connect action, we either note the total expected, or increase the + total we have. Ditto on reconnect. On disconnect, we take the departing + hosts marbles away from the game. + + - We only accept [re]connections when we're missing players, and we only + forward messages when all devices/players are accounted for. There's a + notification sent when the last shows up, so devices can send pending + messages at that time. + + - There's at least one bug with this scheme: we don't know how many players + to expect until the server shows up, so we'll keep accepting clients + until that happens. Probably need a config variable that sets an upper + bound on the number of players. + */ + +StateTable g_stateTable[] = { + +{ XWS_INITED, XWE_CONNECTMSG, XWA_CHKCOUNTS, XWS_CHKCOUNTS_INIT }, +{ XWS_CHKCOUNTS_INIT, XWE_OKTOSEND, XWA_SEND_1ST_RSP, XWS_CHK_ALLHERE }, +{ XWS_CHKCOUNTS_INIT, XWE_COUNTSBAD, XWA_REJECT, XWS_INITED }, +{ XWS_CONNECTING, XWE_CONNECTMSG, XWA_CHKCOUNTS, XWS_CHKCOUNTS }, +{ XWS_CHKCOUNTS, XWE_OKTOSEND, XWA_SEND_1ST_RSP, XWS_CHK_ALLHERE }, +{ XWS_CHKCOUNTS, XWE_COUNTSBAD, XWA_REJECT, XWS_CONNECTING }, + +{ XWS_MISSING, XWE_CONNECTMSG, XWA_CHKCOUNTS, XWS_CHKCOUNTS_MISS }, +{ XWS_CHKCOUNTS_MISS, XWE_OKTOSEND, XWA_SEND_1ST_RSP, XWS_CHK_ALLHERE_2 }, +{ XWS_CHKCOUNTS_MISS, XWE_COUNTSBAD, XWA_REJECT, XWS_MISSING }, + + +{ XWS_CONNECTING, XWE_CONNECTMSG, XWA_SEND_RSP, XWS_CHK_ALLHERE }, +{ XWS_CHK_ALLHERE, XWE_ALLHERE, XWA_SENDALLHERE, XWS_ALLCONNECTED }, +{ XWS_CHK_ALLHERE, XWE_SOMEMISSING, XWA_NONE, XWS_CONNECTING }, + +{ XWS_ALLCONNECTED, XWE_DISCONNMSG, XWA_DISCONNECT, XWS_MISSING }, +{ XWS_CONNECTING, XWE_DISCONNMSG, XWA_DISCONNECT, XWS_CONNECTING }, +{ XWS_MISSING, XWE_DISCONNMSG, XWA_DISCONNECT, XWS_MISSING }, + + /* I'm seeing this but not sure how to handle. Might disconnect be + needed now */ +{ XWS_MISSING, XWE_FORWARDMSG, XWA_DISCONNECT, XWS_MISSING }, + +{ XWS_ANY, XWE_NOMORESOCKETS, XWA_NONE, XWS_DEAD }, +{ XWS_ANY, XWE_SHUTDOWN, XWA_SHUTDOWN, XWS_DEAD }, + +{ XWS_INITED, XWE_RECONNECTMSG, XWA_SEND_RERSP, XWS_CHK_ALLHERE_2 }, +{ XWS_MISSING, XWE_RECONNECTMSG, XWA_SEND_RERSP, XWS_CHK_ALLHERE_2 }, +{ XWS_CHK_ALLHERE_2, XWE_ALLHERE, XWA_SNDALLHERE_2, XWS_ALLCONNECTED }, +{ XWS_CHK_ALLHERE_2, XWE_SOMEMISSING, XWA_NONE, XWS_MISSING }, + +{ XWS_CONNECTING, XWE_REMOVESOCKET, XWA_REMOVESOCKET, XWS_CONNECTING }, +{ XWS_ALLCONNECTED, XWE_REMOVESOCKET, XWA_REMOVESOCKET, XWS_MISSING }, +{ XWS_MISSING, XWE_REMOVESOCKET, XWA_REMOVESOCKET, XWS_MISSING }, + +#ifdef RELAY_HEARTBEAT +{ XWS_ALLCONNECTED, XWE_HEARTFAILED, XWA_HEARTDISCONN, XWS_MISSING }, +{ XWS_CONNECTING, XWE_HEARTFAILED, XWA_HEARTDISCONN, XWS_CONNECTING }, +{ XWS_MISSING, XWE_HEARTFAILED, XWA_HEARTDISCONN, XWS_MISSING }, + + /* Heartbeat arrived */ +{ XWS_CONNECTING, XWE_HEARTRCVD, XWA_NOTEHEART, XWS_CONNECTING }, +{ XWS_ALLCONNECTED, XWE_HEARTRCVD, XWA_NOTEHEART, XWS_ALLCONNECTED }, +{ XWS_MISSING, XWE_HEARTRCVD, XWA_NOTEHEART, XWS_MISSING }, +#endif + + /* Connect timer */ +{ XWS_CONNECTING, XWE_CONNTIMER, XWA_TIMERDISCONN, XWS_DEAD }, +{ XWS_MISSING, XWE_CONNTIMER, XWA_TIMERDISCONN, XWS_DEAD }, +{ XWS_ALLCONNECTED, XWE_CONNTIMER, XWA_NONE, XWS_ALLCONNECTED }, + +{ XWS_CONNECTING, XWE_NOTIFYDISCON, XWA_NOTIFYDISCON, XWS_CONNECTING }, +{ XWS_ALLCONNECTED, XWE_NOTIFYDISCON, XWA_NOTIFYDISCON, XWS_MISSING }, +{ XWS_MISSING, XWE_NOTIFYDISCON, XWA_NOTIFYDISCON, XWS_MISSING }, +{ XWS_DEAD, XWE_NOTIFYDISCON, XWA_NOTIFYDISCON, XWS_DEAD }, + + /* This is our bread-n-butter */ +{ XWS_ALLCONNECTED, XWE_FORWARDMSG, XWA_FWD, XWS_ALLCONNECTED }, + +{ XWS_DEAD, XWE_REMOVESOCKET, XWA_REMOVESOCKET, XWS_DEAD } + +}; + + +bool +getFromTable( XW_RELAY_STATE curState, XW_RELAY_EVENT curEvent, + XW_RELAY_ACTION* takeAction, XW_RELAY_STATE* nextState ) +{ + bool found = false; + StateTable* stp = g_stateTable; + const StateTable* end = stp + sizeof(g_stateTable)/sizeof(g_stateTable[0]); + while ( stp < end ) { + if ( stp->stateStart == curState || stp->stateStart == XWS_ANY ) { + if ( stp->stateEvent == curEvent || stp->stateEvent == XWE_ANY ) { + *takeAction = stp->stateAction; + *nextState = stp->stateEnd; + found = true; + break; + } + } + ++stp; + } + + if ( !found ) { + logf( XW_LOGERROR, "==> ERROR :: unable to find transition from %s " + "on event %s", + stateString(curState), eventString(curEvent) ); + } + return found; +} /* getFromTable */ + +#define CASESTR(s) case s: return #s + +const char* +stateString( XW_RELAY_STATE state ) +{ + switch( state ) { + CASESTR(XWS_NONE); + CASESTR(XWS_ANY); + CASESTR(XWS_INITED); + CASESTR(XWS_CONNECTING); + CASESTR(XWS_ALLCONNECTED); + CASESTR(XWS_WAITING_RECON); + CASESTR(XWS_DEAD); + CASESTR(XWS_CHECKING_CONN); + CASESTR(XWS_CHECKINGDEST); + CASESTR(XWS_CHECKING_CAN_LOCK); + CASESTR(XWS_MISSING); + CASESTR(XWS_CHK_ALLHERE); + CASESTR(XWS_CHK_ALLHERE_2); + CASESTR(XWS_CHKCOUNTS_INIT); + CASESTR(XWS_CHKCOUNTS_MISS); + CASESTR(XWS_CHKCOUNTS); + } + assert(0); + return ""; +} + +const char* +eventString( XW_RELAY_EVENT evt ) +{ + switch( evt ) { + CASESTR(XWE_NONE); + CASESTR(XWE_CONNECTMSG); + CASESTR(XWE_RECONNECTMSG); + CASESTR(XWE_DISCONNMSG); + CASESTR(XWE_FORWARDMSG); +#ifdef RELAY_HEARTBEAT + CASESTR(XWE_HEARTRCVD); + CASESTR(XWE_HEARTFAILED); +#endif + CASESTR(XWE_CONNTIMER); + CASESTR(XWE_ANY); + CASESTR(XWE_REMOVESOCKET); + CASESTR(XWE_NOMORESOCKETS); + CASESTR(XWE_NOTIFYDISCON); + CASESTR(XWE_ALLHERE); + CASESTR(XWE_SOMEMISSING); + CASESTR(XWE_OKTOSEND); + CASESTR(XWE_COUNTSBAD); + CASESTR(XWE_SHUTDOWN); + } + assert(0); + return ""; +} + +const char* +actString( XW_RELAY_ACTION act ) +{ + switch ( act ) { + CASESTR(XWA_NONE); + CASESTR(XWA_SEND_1ST_RSP); + CASESTR(XWA_SEND_1ST_RERSP); + CASESTR(XWA_CHKCOUNTS); + CASESTR(XWA_REJECT); + CASESTR(XWA_SEND_RSP); + CASESTR(XWA_SEND_RERSP); + CASESTR(XWA_SENDALLHERE); + CASESTR(XWA_SNDALLHERE_2); + CASESTR(XWA_FWD); + CASESTR(XWA_NOTEHEART); + CASESTR(XWA_TIMERDISCONN); + CASESTR(XWA_DISCONNECT); + CASESTR(XWA_NOTIFYDISCON); + CASESTR(XWA_REMOVESOCKET); + CASESTR(XWA_HEARTDISCONN); + CASESTR(XWA_SHUTDOWN); + } + assert(0); + return ""; +} +#undef CASESTR diff --git a/xwords4/relay/states.h b/xwords4/relay/states.h new file mode 100644 index 000000000..cc33fa84a --- /dev/null +++ b/xwords4/relay/states.h @@ -0,0 +1,160 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _STATES_H_ +#define _STATES_H_ + + +/* states */ +typedef +enum { + XWS_NONE + ,XWS_ANY /* wildcard */ + + ,XWS_CHKCOUNTS_INIT /* from initial state, check if all players + are here. Success should be an error, + actually: 1-device game. */ + + ,XWS_CHKCOUNTS_MISS /* from the missing state */ + ,XWS_CHKCOUNTS /* check from any other state */ + + ,XWS_CHK_ALLHERE /* Need to see if all expected devices/players + are on board. */ + + ,XWS_CHK_ALLHERE_2 /* same as above, but triggered by a reconnect + rather than a connect request */ + + ,XWS_INITED /* Relay's running and the object's been + created, but nobody's signed up yet. This + is a very short-lived state since an + incoming connection is why the object was + created. */ + + ,XWS_CONNECTING /* At least one device has connected, but no + packets have yet arrived to be + forwarded. */ + + ,XWS_CHECKING_CONN /* While we're still not fully connected a + message comes in */ + + ,XWS_ALLCONNECTED /* All devices are connected and ready for the + relay to do its work. This is the state + we're in most of the time. */ + + ,XWS_MISSING /* We've been fully connected before but lost + somebody. Once [s]he's back we can be + fully connected again. */ + + ,XWS_WAITING_RECON /* At least one device has been timed out or + sent a disconnect message. We can't flow + messages in this state, and will be killing + all connections if we don't hear back from + the missing guy soon. */ + + ,XWS_CHECKINGDEST /* Checking for valid socket */ + + ,XWS_CHECKING_CAN_LOCK /* Is this message one that implies all + players are present? */ + + ,XWS_DEAD /* About to kill the object */ +} XW_RELAY_STATE; + + +/* events */ +typedef enum { + XWE_NONE + + ,XWE_OKTOSEND + ,XWE_COUNTSBAD + + ,XWE_ALLHERE /* notify that all expected players are arrived */ + ,XWE_SOMEMISSING /* notify that some expected players are still missing */ + + ,XWE_CONNECTMSG /* A device is connecting using the cookie for + this object */ + + ,XWE_RECONNECTMSG /* A device is re-connecting using the + connID for this object */ + + ,XWE_DISCONNMSG /* disconnect socket from this game/cref */ + + ,XWE_FORWARDMSG /* A message needs forwarding */ + +#ifdef RELAY_HEARTBEAT + ,XWE_HEARTRCVD /* A heartbeat message arrived */ + ,XWE_HEARTFAILED +#endif + ,XWE_CONNTIMER /* timer for did we get all players hooked + up */ + ,XWE_REMOVESOCKET /* Need to remove socket from this cref */ + + ,XWE_NOTIFYDISCON /* Send a discon */ + + ,XWE_NOMORESOCKETS /* last socket's been removed */ + + ,XWE_SHUTDOWN /* shutdown this game */ + + ,XWE_ANY /* wildcard; matches all */ +} XW_RELAY_EVENT; + + +/* actions */ +typedef enum { + XWA_NONE + + ,XWA_SEND_1ST_RSP + ,XWA_SEND_1ST_RERSP + + ,XWA_CHKCOUNTS + + ,XWA_REJECT + + ,XWA_SEND_RSP /* Send a connection response */ + ,XWA_SEND_RERSP + + ,XWA_SENDALLHERE /* Let all devices know we're in business */ + ,XWA_SNDALLHERE_2 /* Ditto, but for a reconnect */ + + ,XWA_FWD /* Forward a message */ + + ,XWA_NOTEHEART /* Record heartbeat received */ + + ,XWA_TIMERDISCONN /* disconnect all because of a timer */ + + ,XWA_DISCONNECT + + ,XWA_NOTIFYDISCON + + ,XWA_REMOVESOCKET + + ,XWA_HEARTDISCONN + + ,XWA_SHUTDOWN + +} XW_RELAY_ACTION; + +bool getFromTable( XW_RELAY_STATE curState, XW_RELAY_EVENT curEvent, + XW_RELAY_ACTION* takeAction, XW_RELAY_STATE* nextState ); + + +const char* stateString( XW_RELAY_STATE state ); +const char* eventString( XW_RELAY_EVENT evt ); +const char* actString( XW_RELAY_ACTION act ); + +#endif diff --git a/xwords4/relay/timermgr.cpp b/xwords4/relay/timermgr.cpp new file mode 100644 index 000000000..e6720189f --- /dev/null +++ b/xwords4/relay/timermgr.cpp @@ -0,0 +1,190 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ + +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include "timermgr.h" +#include "xwrelay_priv.h" +#include "configs.h" +#include "mlock.h" + +TimerMgr::TimerMgr() + : m_nextFireTime(0) +{ + pthread_mutex_init( &m_timersMutex, NULL ); +} + +/* static */TimerMgr* +TimerMgr::GetTimerMgr() +{ + static TimerMgr* mgr = NULL; + if ( mgr == NULL ) { + mgr = new TimerMgr(); + } + return mgr; +} + +void +TimerMgr::SetTimer( time_t inMillis, TimerProc proc, void* closure, + int interval ) +{ + logf( XW_LOGINFO, "setTimer: now = %d", now() ); + TimerInfo ti; + ti.proc = proc; + ti.closure = closure; + ti.when = now() + inMillis; + ti.interval = interval; + + MutexLock ml( &m_timersMutex ); + + if ( getTimer( proc, closure ) ) { + clearTimerImpl( proc, closure ); + } + + m_timers.push_back( ti ); + + figureNextFire(); + logf( XW_LOGINFO, "setTimer done" ); +} + +time_t +TimerMgr::GetPollTimeout() +{ + MutexLock ml( &m_timersMutex ); + + time_t tout = m_nextFireTime; + if ( tout == 0 ) { + tout = -1; + } else { + tout -= now(); + if ( tout < 0 ) { + tout = 0; + } + tout *= 1000; + } + return tout; +} /* GetPollTimeout */ + +int +TimerMgr::getTimer( TimerProc proc, void* closure ) +{ + list::iterator iter; + for ( iter = m_timers.begin(); iter != m_timers.end(); ++iter ) { + if ( (*iter).proc == proc + && (*iter).closure == closure ) { + return 1; + } + } + + return 0; +} /* getTimer */ + +void +TimerMgr::figureNextFire() +{ + /* Don't call this unless have the lock!!! */ + time_t t = 0x7FFFFFFF; + time_t cur = now(); + + list::iterator iter = m_timers.begin(); + + while ( iter != m_timers.end() ) { + time_t when = iter->when; + + if ( when == 0 ) { + if ( iter->interval ) { + when = iter->when = cur + iter->interval; + } else { + m_timers.erase(iter); + iter = m_timers.begin(); + continue; + } + } + + if ( when < t ) { + t = when; + } + ++iter; + } + + m_nextFireTime = t == 0x7FFFFFFF? 0 : t; +} /* figureNextFire */ + +void +TimerMgr::ClearTimer( TimerProc proc, void* closure ) +{ + MutexLock ml( &m_timersMutex ); + clearTimerImpl( proc, closure ); +} + +void +TimerMgr::FireElapsedTimers() +{ + pthread_mutex_lock( &m_timersMutex ); + + time_t curTime = now(); + + vector procs; + vector closures; + + /* loop until we get through without firing a single one. Only fire one + per run, though, since calling erase invalidates the iterator. + PENDING: all this needs a mutex!!!! */ + list::iterator iter; + for ( iter = m_timers.begin(); iter != m_timers.end(); ++iter ) { + TimerInfo* tip = &(*iter); + if ( tip->when <= curTime ) { + + procs.push_back(tip->proc); + closures.push_back(tip->closure); + + if ( tip->interval ) { + tip->when += tip->interval; + } else { + tip->when = 0; /* flag for removal */ + } + } + } + + pthread_mutex_unlock( &m_timersMutex ); + vector::iterator iter1 = procs.begin(); + vector::iterator iter2 = closures.begin(); + while ( iter1 != procs.end() ) { + (*iter1++)(*iter2++); + } + + MutexLock ml( &m_timersMutex ); + figureNextFire(); +} /* fireElapsedTimers */ + +void +TimerMgr::clearTimerImpl( TimerProc proc, void* closure ) +{ + list::iterator iter; + for ( iter = m_timers.begin(); iter != m_timers.end(); ++iter ) { + TimerInfo* tip = &(*iter); + if ( tip->proc == proc && tip->closure == closure ) { + m_timers.erase(iter); + break; + } + } +} diff --git a/xwords4/relay/timermgr.h b/xwords4/relay/timermgr.h new file mode 100644 index 000000000..52137d47b --- /dev/null +++ b/xwords4/relay/timermgr.h @@ -0,0 +1,71 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ + +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _TIMERMGR_H_ +#define _TIMERMGR_H_ + +#include "list" + +#include + +#include "xwrelay_priv.h" + +using namespace std; + +typedef void (*TimerProc)( void* closure ); + + +class TimerMgr { + + public: + static TimerMgr* GetTimerMgr(); + + void SetTimer( time_t inMillis, TimerProc proc, void* closure, + int interval ); /* 0 means non-recurring */ + void ClearTimer( TimerProc proc, void* closure ); + + time_t GetPollTimeout(); + void FireElapsedTimers(); + + private: + + typedef struct { + TimerProc proc; + void* closure; + time_t when; + int interval; + } TimerInfo; + + + TimerMgr(); + static void sighandler( int signal ); + + /* run once we have the mutex */ + void clearTimerImpl( TimerProc proc, void* closure ); + int getTimer( TimerProc proc, void* closure ); + void figureNextFire(); + + pthread_mutex_t m_timersMutex; + list m_timers; + + time_t m_nextFireTime; +}; + +#endif diff --git a/xwords4/relay/tpool.cpp b/xwords4/relay/tpool.cpp new file mode 100644 index 000000000..ce2d0d486 --- /dev/null +++ b/xwords4/relay/tpool.cpp @@ -0,0 +1,351 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ + +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tpool.h" +#include "xwrelay_priv.h" +#include "xwrelay.h" +#include "timermgr.h" +#include "mlock.h" + +XWThreadPool* XWThreadPool::g_instance = NULL; + +/* static */ XWThreadPool* +XWThreadPool::GetTPool() +{ + XWThreadPool* me = g_instance; + if ( me == NULL ) { + me = new XWThreadPool(); + g_instance = me; + } + return me; +} + +XWThreadPool::XWThreadPool() + : m_timeToDie(0) + , m_nThreads(0) +{ + pthread_rwlock_init( &m_activeSocketsRWLock, NULL ); + pthread_mutex_init ( &m_queueMutex, NULL ); + + pthread_cond_init( &m_queueCondVar, NULL ); + + int fd[2]; + if ( pipe( fd ) ) { + logf( XW_LOGERROR, "pipe failed" ); + } + m_pipeRead = fd[0]; + m_pipeWrite = fd[1]; +} + +XWThreadPool::~XWThreadPool() +{ + pthread_cond_destroy( &m_queueCondVar ); + + pthread_rwlock_destroy( &m_activeSocketsRWLock ); + pthread_mutex_destroy ( &m_queueMutex ); +} /* ~XWThreadPool */ + +void +XWThreadPool::Setup( int nThreads, packet_func pFunc ) +{ + m_nThreads = nThreads; + m_pFunc = pFunc; + + pthread_t thread; + + int i; + for ( i = 0; i < nThreads; ++i ) { + int result = pthread_create( &thread, NULL, tpool_main, this ); + assert( result == 0 ); + pthread_detach( thread ); + } + + int result = pthread_create( &thread, NULL, listener_main, this ); + assert( result == 0 ); +} + +void +XWThreadPool::Stop() +{ + m_timeToDie = 1; + + int i; + for ( i = 0; i < m_nThreads; ++i ) { + enqueue( 0 ); + } + + interrupt_poll(); +} + +void +XWThreadPool::AddSocket( int socket ) +{ + logf( XW_LOGINFO, "AddSocket(%d)", socket ); + { + RWWriteLock ml( &m_activeSocketsRWLock ); + m_activeSockets.push_back( socket ); + } + interrupt_poll(); +} + +int +XWThreadPool::RemoveSocket( int socket ) +{ + int found = 0; + { + RWWriteLock ml( &m_activeSocketsRWLock ); + + vector::iterator iter = m_activeSockets.begin(); + while ( iter != m_activeSockets.end() ) { + if ( *iter == socket ) { + m_activeSockets.erase( iter ); + found = 1; + break; + } + ++iter; + } + } + return found; +} /* RemoveSocket */ + +void +XWThreadPool::CloseSocket( int socket ) +{ + int do_interrupt = 0; + if ( !RemoveSocket( socket ) ) { + RWWriteLock rwl( &m_activeSocketsRWLock ); + deque::iterator iter = m_queue.begin(); + while ( iter != m_queue.end() ) { + if ( *iter == socket ) { + m_queue.erase( iter ); + do_interrupt = 1; + break; + } + ++iter; + } + } +/* close( socket ); */ +/* if ( do_interrupt ) { */ + /* We always need to interrupt the poll because the socket we're closing + will be in the list being listened to. That or we need to drop sockets + that have been removed on some other thread while the poll call's + blocking.*/ + interrupt_poll(); +/* } */ +} + +int +XWThreadPool::get_process_packet( int socket ) +{ + short packetSize; + assert( sizeof(packetSize) == 2 ); + + ssize_t nRead = recv( socket, &packetSize, + sizeof(packetSize), MSG_WAITALL ); + if ( nRead != 2 ) { + killSocket( socket, "nRead != 2" ); + return 0; + } + + packetSize = ntohs( packetSize ); + if ( packetSize < 0 || packetSize > MAX_MSG_LEN ) { + killSocket( socket, "packetSize wrong" ); + return 0; + } + + unsigned char buf[MAX_MSG_LEN]; + nRead = recv( socket, buf, packetSize, MSG_WAITALL ); + if ( nRead != packetSize ) { + killSocket( socket, "nRead != packetSize" ); + return 0; + } + logf( XW_LOGINFO, "read %d bytes\n", nRead ); + + logf( XW_LOGINFO, "calling m_pFunc" ); + int success = (*m_pFunc)( buf, packetSize, socket ); + + return success; +} /* get_process_packet */ + +/* static */ void* +XWThreadPool::tpool_main( void* closure ) +{ + XWThreadPool* me = (XWThreadPool*)closure; + return me->real_tpool_main(); +} + +void* +XWThreadPool::real_tpool_main() +{ + logf( XW_LOGINFO, "tpool worker thread starting" ); + for ( ; ; ) { + + pthread_mutex_lock( &m_queueMutex ); + while ( !m_timeToDie && m_queue.size() == 0 ) { + pthread_cond_wait( &m_queueCondVar, &m_queueMutex ); + } + + if ( m_timeToDie ) { + break; + } + + int socket = m_queue.front(); + m_queue.pop_front(); + pthread_mutex_unlock( &m_queueMutex ); + logf( XW_LOGINFO, "worker thread got socket %d from queue", socket ); + + if ( get_process_packet( socket ) ) { + AddSocket( socket ); + } /* else drop it: error */ + } + logf( XW_LOGINFO, "tpool worker thread exiting" ); + return NULL; +} + +void +XWThreadPool::interrupt_poll() +{ + logf( XW_LOGINFO, __func__ ); + unsigned char byt = 0; + int nSent = write( m_pipeWrite, &byt, 1 ); + if ( nSent != 1 ) { + logf( XW_LOGERROR, "errno = %s (%d)", strerror(errno), errno ); + } +} + +void* +XWThreadPool::real_listener() +{ + int flags = POLLIN | POLLERR | POLLHUP; + TimerMgr* tmgr = TimerMgr::GetTimerMgr(); + + for ( ; ; ) { + + pthread_rwlock_rdlock( &m_activeSocketsRWLock ); + int nSockets = m_activeSockets.size() + 1; /* for pipe */ + pollfd* fds = (pollfd*)malloc( sizeof(fds[0]) * nSockets ); + pollfd* curfd = fds; + char* log = (char*)malloc( 4 * nSockets ); + log[0] = '\0'; + int len = 0; + + curfd->fd = m_pipeRead; + curfd->events = flags; + len += sprintf( log+len, "%d,", curfd->fd ); + ++curfd; + + vector::iterator iter = m_activeSockets.begin(); + while ( iter != m_activeSockets.end() ) { + curfd->fd = *iter++; + curfd->events = flags; + len += sprintf( log+len, "%d,", curfd->fd ); + ++curfd; + } + pthread_rwlock_unlock( &m_activeSocketsRWLock ); + + int nMillis = tmgr->GetPollTimeout(); + + logf( XW_LOGINFO, "polling %s nmillis=%d", log, nMillis ); + int nEvents = poll( fds, nSockets, nMillis ); + logf( XW_LOGINFO, "back from poll: %d", nEvents ); + if ( m_timeToDie ) { + break; + } + + if ( nEvents == 0 ) { + tmgr->FireElapsedTimers(); + } else if ( nEvents < 0 ) { + logf( XW_LOGERROR, "errno: %s (%d)", strerror(errno), errno ); + } + + if ( fds[0].revents != 0 ) { + logf( XW_LOGINFO, "poll interrupted" ); + assert( fds[0].revents == POLLIN ); + unsigned char byt; + read( fds[0].fd, &byt, 1 ); + --nEvents; + } + + if ( nEvents > 0 ) { + --nSockets; + curfd = &fds[1]; + + int i; + for ( i = 0; i < nSockets && nEvents > 0; ++i ) { + + if ( curfd->revents != 0 ) { + int socket = curfd->fd; + if ( !RemoveSocket( socket ) ) { + /* no further processing if it's been removed while + we've been sleeping in poll */ + --nEvents; + continue; + } + + if ( curfd->revents == POLLIN + || curfd->revents == POLLPRI ) { + logf( XW_LOGINFO, "enqueuing %d", socket ); + enqueue( socket ); + } else { + logf( XW_LOGERROR, "odd revents: %d", curfd->revents ); + killSocket( socket, "error/hup in poll()" ); + } + --nEvents; + } + ++curfd; + } + assert( nEvents == 0 ); + } + + free( fds ); + free( log ); + } + + logf( XW_LOGINFO, "real_listener returning" ); + return NULL; +} /* real_listener */ + +/* static */ void* +XWThreadPool::listener_main( void* closure ) +{ + XWThreadPool* me = (XWThreadPool*)closure; + return me->real_listener(); +} + +void +XWThreadPool::enqueue( int socket ) +{ + MutexLock ml( &m_queueMutex ); + m_queue.push_back( socket ); + + logf( XW_LOGINFO, "calling pthread_cond_signal" ); + pthread_cond_signal( &m_queueCondVar ); + /* implicit unlock */ +} diff --git a/xwords4/relay/tpool.h b/xwords4/relay/tpool.h new file mode 100644 index 000000000..62e331a8e --- /dev/null +++ b/xwords4/relay/tpool.h @@ -0,0 +1,85 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ + +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Runs a single thread polling for activity on any of the sockets in its + * list. When there is activity, removes the socket from that list and adds + * it ot a queue of socket ready for reading. Starts up a bunch of threads + * waiting on that queue. When a new socket appears, a thread grabs the + * socket, reads from it, passes the buffer on, and puts the socket back in + * the list being read from in our main thread. + */ + +#include +#include + +using namespace std; + +class XWThreadPool { + + public: + static XWThreadPool* GetTPool(); + typedef int (*packet_func)( unsigned char* buf, int bufLen, int socket ); + + XWThreadPool(); + ~XWThreadPool(); + + void Setup( int nThreads, packet_func pFunc ); + void Stop(); + + /* Add to set being listened on */ + void AddSocket( int socket ); + /* remove from tpool altogether, and close */ + void CloseSocket( int socket ); + + void Poll(); + + private: + /* Remove from set being listened on */ + int RemoveSocket( int socket ); + + void enqueue( int socket ); + int get_process_packet( int socket ); + void interrupt_poll(); + + void* real_tpool_main(); + static void* tpool_main( void* closure ); + + void* real_listener(); + static void* listener_main( void* closure ); + + /* Sockets main thread listens on */ + vector m_activeSockets; + pthread_rwlock_t m_activeSocketsRWLock; + + /* Sockets waiting for a thread to read 'em */ + deque m_queue; + pthread_mutex_t m_queueMutex; + pthread_cond_t m_queueCondVar; + + /* for self-write pipe hack */ + int m_pipeRead; + int m_pipeWrite; + + int m_timeToDie; + int m_nThreads; + packet_func m_pFunc; + + static XWThreadPool* g_instance; +}; diff --git a/xwords4/relay/xwrelay.conf b/xwords4/relay/xwrelay.conf new file mode 100644 index 000000000..dd80752cc --- /dev/null +++ b/xwords4/relay/xwrelay.conf @@ -0,0 +1,37 @@ +# -*- mode: Makefile; -*- +# Comments start with #, which must be in first column +# +# Format: var=value. No spaces between var and value at this point. + +# Heartbeat timer. Sent to clients. +HEARTBEAT=60 + +# How long after the first connection on a cookie until we need to +# have heard from all players in the game? After this long passes we +# kill the connection after notifying all that have connected. +ALLCONN=300 + +# How many worker threads in the thread pool? Default is five. +NTHREADS=5 + +# What port do we listen on for incomming connections? +PORT=10997 +PORT=10998 +PORT=10999 +# intentional duplicate for testing +PORT=10999 + +# And the control port is? +CTLPORT=11000 + +# Need a unique name for every instance of the relay so they can +# create game ids guaranteed to be unique +SERVERNAME=eehouse.org + +# Where will the file live that stores the last ID used for a new +# connName. +IDFILE=/home/eehouse/xwrelay_id.txt + +# Initial level of logging. See xwrelay_priv.h for values. Currently +# 0 means errors only, 1 info, 2 verbose and 3 very verbose. +LOGLEVEL=2 diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp new file mode 100644 index 000000000..16dab5223 --- /dev/null +++ b/xwords4/relay/xwrelay.cpp @@ -0,0 +1,737 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ + +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +////////////////////////////////////////////////////////////////////////////// +// +// This program is a *very rough* cut at a message forwarding server that's +// meant to sit somewhere that cellphones can reach and forward packets across +// connections so that they can communicate. It exists to work around the +// fact that many cellular carriers prevent direct incoming connections from +// reaching devices on their networks. It's meant for Crosswords, but might +// be useful for other things. It also needs a lot of work, and I hacked it +// up before making an exhaustive search for other alternatives. +// +////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include /* gethostbyname */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__FreeBSD__) +# if (OSVERSION > 500000) +# include "getopt.h" +# else +# include "unistd.h" +# endif +#else +# include +#endif + +#include + +#include "xwrelay.h" +#include "crefmgr.h" +#include "ctrl.h" +#include "mlock.h" +#include "tpool.h" +#include "configs.h" +#include "timermgr.h" +#include "permid.h" +#include "lstnrmgr.h" + +#define LOG_FILE_PATH "./xwrelay.log" + +void +logf( XW_LogLevel level, const char* format, ... ) +{ + RelayConfigs* rc = RelayConfigs::GetConfigs(); + if ( NULL == rc || level <= rc->GetLogLevel() ) { +#ifdef USE_SYSLOG + char buf[256]; + va_list ap; + va_start( ap, format ); + vsnprintf( buf, sizeof(buf), format, ap ); + syslog( LOG_LOCAL0 | LOG_INFO, buf ); + va_end(ap); +#else + static FILE* where = stderr; + struct tm* timp; + struct timeval tv; + struct timezone tz; + + if ( !where ) { + where = fopen( LOG_FILE_PATH, "a" ); + } + + gettimeofday( &tv, &tz ); + timp = localtime( &tv.tv_sec ); + + pthread_t me = pthread_self(); + + fprintf( where, "<%p>%d:%d:%d: ", (void*)me, timp->tm_hour, + timp->tm_min, timp->tm_sec ); + + va_list ap; + va_start( ap, format ); + vfprintf( where, format, ap ); + va_end(ap); + fprintf( where, "\n" ); +#endif + } +} /* logf */ + +static int +getNetShort( unsigned char** bufpp, unsigned char* end, unsigned short* out ) +{ + int ok = *bufpp + 2 <= end; + if ( ok ) { + unsigned short tmp; + memcpy( &tmp, *bufpp, 2 ); + *bufpp += 2; + *out = ntohs( tmp ); + } + return ok; +} /* getNetShort */ + +static int +getNetByte( unsigned char** bufpp, unsigned char* end, unsigned char* out ) +{ + int ok = *bufpp < end; + if ( ok ) { + *out = **bufpp; + ++*bufpp; + } + return ok; +} /* getNetByte */ + +#ifdef RELAY_HEARTBEAT +static int +processHeartbeat( unsigned char* buf, int bufLen, int socket ) +{ + unsigned char* end = buf + bufLen; + CookieID cookieID; + HostID hostID; + int success = 0; + + if ( getNetShort( &buf, end, &cookieID ) + && getNetByte( &buf, end, &hostID ) ) { + logf( XW_LOGINFO, "processHeartbeat: cookieID 0x%lx, hostID 0x%x", + cookieID, hostID ); + + SafeCref scr( cookieID ); + success = scr.HandleHeartbeat( hostID, socket ); + if ( !success ) { + killSocket( socket, "no cref for socket" ); + } + } + return success; +} /* processHeartbeat */ +#endif + +static int +readStr( unsigned char** bufp, const unsigned char* end, + char* outBuf, int bufLen ) +{ + unsigned char clen = **bufp; + ++*bufp; + if ( ((*bufp + clen) <= end) && (clen < bufLen) ) { + memcpy( outBuf, *bufp, clen ); + outBuf[clen] = '\0'; + *bufp += clen; + return 1; + } + return 0; +} /* readStr */ + +static XWREASON +flagsOK( unsigned char flags ) +{ + return flags == XWRELAY_PROTO_VERSION ? + XWRELAY_ERROR_NONE : XWRELAY_ERROR_OLDFLAGS; +} /* flagsOK */ + +static void +denyConnection( int socket, XWREASON err ) +{ + unsigned char buf[2]; + + buf[0] = XWRELAY_CONNECTDENIED; + buf[1] = err; + + send_with_length_unsafe( socket, buf, sizeof(buf) ); +} + +/* No mutex here. Caller better be ensuring no other thread can access this + * socket. */ +int +send_with_length_unsafe( int socket, unsigned char* buf, int bufLen ) +{ + int ok = 0; + unsigned short len = htons( bufLen ); + ssize_t nSent = send( socket, &len, 2, 0 ); + if ( nSent == 2 ) { + nSent = send( socket, buf, bufLen, 0 ); + if ( nSent == bufLen ) { + logf( XW_LOGINFO, "sent %d bytes on socket %d", nSent, socket ); + ok = 1; + } + } + return ok; +} /* send_with_length_unsafe */ + + +/* A CONNECT message from a device gives us the hostID and socket we'll + * associate with one participant in a relayed session. We'll store this + * information with the cookie where other participants can find it when they + * arrive. + * + * What to do if we already have a game going? In that case the connection ID + * passed in will be non-zero. If the device can be associated with an + * ongoing game, with its new socket, associate it and forward any messages + * outstanding. Otherwise close down the socket. And maybe the others in the + * game? + */ +static int +processConnect( unsigned char* bufp, int bufLen, int socket ) +{ + char cookie[MAX_COOKIE_LEN+1]; + unsigned char* end = bufp + bufLen; + int success = 0; + + logf( XW_LOGINFO, "processConnect" ); + + cookie[0] = '\0'; + + unsigned char flags = *bufp++; + XWREASON err = flagsOK( flags ); + if ( err != XWRELAY_ERROR_NONE ) { + denyConnection( socket, err ); + } else { + HostID srcID; + unsigned char nPlayersH; + unsigned char nPlayersT; + if ( readStr( &bufp, end, cookie, sizeof(cookie) ) + && getNetByte( &bufp, end, &srcID ) + && getNetByte( &bufp, end, &nPlayersH ) + && getNetByte( &bufp, end, &nPlayersT ) ) { + + SafeCref scr( cookie, 1, srcID, nPlayersH, nPlayersT ); + success = scr.Connect( socket, srcID, nPlayersH, nPlayersT ); + } + + if ( !success ) { + denyConnection( socket, XWRELAY_ERROR_BADPROTO ); + } + } + return success; +} /* processConnect */ + +static int +processReconnect( unsigned char* bufp, int bufLen, int socket ) +{ + unsigned char* end = bufp + bufLen; + int success = 0; + + logf( XW_LOGINFO, "processReconnect" ); + + unsigned char flags = *bufp++; + XWREASON err = flagsOK( flags ); + if ( err != XWRELAY_ERROR_NONE ) { + denyConnection( socket, err ); + } else { + char connName[MAX_CONNNAME_LEN+1]; + HostID srcID; + unsigned char nPlayersH; + unsigned char nPlayersT; + + connName[0] = '\0'; + if ( getNetByte( &bufp, end, &srcID ) + && getNetByte( &bufp, end, &nPlayersH ) + && getNetByte( &bufp, end, &nPlayersT ) + && readStr( &bufp, end, connName, sizeof(connName) ) ) { + + SafeCref scr( connName, 0, srcID, nPlayersH, nPlayersT ); + success = scr.Reconnect( socket, srcID, nPlayersH, nPlayersT ); + } + + if ( !success ) { + denyConnection( socket, XWRELAY_ERROR_BADPROTO ); + } + } + return success; +} /* processReconnect */ + +static int +processDisconnect( unsigned char* bufp, int bufLen, int socket ) +{ + unsigned char* end = bufp + bufLen; + CookieID cookieID; + HostID hostID; + int success = 0; + + if ( getNetShort( &bufp, end, &cookieID ) + && getNetByte( &bufp, end, &hostID ) ) { + + SafeCref scr( cookieID ); + scr.Disconnect( socket, hostID ); + success = 1; + } else { + logf( XW_LOGERROR, "dropping XWRELAY_GAME_DISCONNECT; wrong length" ); + } + return success; +} /* processDisconnect */ + +void +killSocket( int socket, const char* why ) +{ + logf( XW_LOGERROR, "killSocket(%d): %s", socket, why ); + CRefMgr::Get()->RemoveSocketRefs( socket ); + /* Might want to kill the thread it belongs to if we're not in it, + e.g. when unable to write to another socket. */ + logf( XW_LOGINFO, "killSocket done" ); + XWThreadPool::GetTPool()->CloseSocket( socket ); +} + +time_t +now() +{ + static time_t startTime = time(NULL); + return time(NULL) - startTime; +} + +/* forward the message. Need only change the command after looking up the + * socket and it's ready to go. */ +static int +forwardMessage( unsigned char* buf, int buflen, int srcSocket ) +{ + int success = 0; + unsigned char* bufp = buf + 1; /* skip cmd */ + unsigned char* end = buf + buflen; + CookieID cookieID; + HostID src; + HostID dest; + + if ( getNetShort( &bufp, end, &cookieID ) + && getNetByte( &bufp, end, &src ) + && getNetByte( &bufp, end, &dest ) ) { + logf( XW_LOGINFO, "cookieID = %d", cookieID ); + + SafeCref scr( cookieID ); + success = scr.Forward( src, dest, buf, buflen ); + } + return success; +} /* forwardMessage */ + +static int +processMessage( unsigned char* buf, int bufLen, int socket ) +{ + int success = 0; /* default is failure */ + XWRELAY_Cmd cmd = *buf; + switch( cmd ) { + case XWRELAY_GAME_CONNECT: + logf( XW_LOGINFO, "processMessage got XWRELAY_CONNECT" ); + success = processConnect( buf+1, bufLen-1, socket ); + break; + case XWRELAY_GAME_RECONNECT: + logf( XW_LOGINFO, "processMessage got XWRELAY_RECONNECT" ); + success = processReconnect( buf+1, bufLen-1, socket ); + break; + case XWRELAY_GAME_DISCONNECT: + success = processDisconnect( buf+1, bufLen-1, socket ); + break; +#ifdef RELAY_HEARTBEAT + case XWRELAY_HEARTBEAT: + logf( XW_LOGINFO, "processMessage got XWRELAY_HEARTBEAT" ); + success = processHeartbeat( buf + 1, bufLen - 1, socket ); + break; +#endif + case XWRELAY_MSG_TORELAY: + logf( XW_LOGINFO, "processMessage got XWRELAY_MSG_TORELAY" ); + success = forwardMessage( buf, bufLen, socket ); + break; + default: + logf( XW_LOGINFO, "processMessage bad: %d", cmd ); + break; + /* just drop it */ + } + + if ( !success ) { + killSocket( socket, "couldn't forward message" ); + } + + return success; /* caller defines non-0 as failure */ +} /* processMessage */ + +int +make_socket( unsigned long addr, unsigned short port ) +{ + int sock = socket( AF_INET, SOCK_STREAM, 0 ); + assert( sock ); + + /* We may be relaunching after crashing with sockets open. SO_REUSEADDR + allows them to be immediately rebound. */ + int t = true; + if ( 0 != setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, &t, sizeof(t) ) ) { + logf( XW_LOGERROR, "setsockopt failed. errno = %s (%d)\n", + strerror(errno), errno ); + return -1; + } + + struct sockaddr_in sockAddr; + sockAddr.sin_family = AF_INET; + sockAddr.sin_addr.s_addr = htonl(addr); + sockAddr.sin_port = htons(port); + + int result = bind( sock, (struct sockaddr*)&sockAddr, sizeof(sockAddr) ); + if ( result != 0 ) { + logf( XW_LOGERROR, "exiting: unable to bind port %d: %d, " + "errno = %s (%d)\n", port, result, strerror(errno), errno ); + return -1; + } + logf( XW_LOGINFO, "bound socket %d on port %d", sock, port ); + + result = listen( sock, 5 ); + if ( result != 0 ) { + logf( XW_LOGERROR, "exiting: unable to listen: %d, " + "errno = %s (%d)\n", result, strerror(errno), errno ); + return -1; + } + return sock; +} /* make_socket */ + +static void +usage( char* arg0 ) +{ + fprintf( stderr, "usage: %s \\\n", arg0 ); + + fprintf( stderr, + "\t-? (print this help)\\\n" + "\t-c (localhost port for control console)\\\n" + "\t-D (don't become daemon)\\\n" + "\t-F (don't fork and wait to respawn child)\\\n" + "\t-f (config file)\\\n" + "\t-h (print this help)\\\n" + "\t-i (file where next global id stored)\\\n" + "\t-n (used in permID generation)\\\n" + "\t-p (port to listen on)\\\n" + "\t-t (how many worker threads to use)\\\n" + ); + fprintf( stderr, "svn rev. %s\n", SVN_REV ); +} + +/* sockets that need to be closable from interrupt handler */ +ListenerMgr g_listeners; +int g_control; + +void +shutdown() +{ + XWThreadPool* tPool = XWThreadPool::GetTPool(); + if ( tPool != NULL ) { + tPool->Stop(); + } + + CRefMgr* cmgr = CRefMgr::Get(); + if ( cmgr != NULL ) { + cmgr->CloseAll(); + delete cmgr; + } + + delete tPool; + + stop_ctrl_threads(); + + g_listeners.RemoveAll(); + close( g_control ); + + exit( 0 ); + logf( XW_LOGINFO, "exit done" ); +} + +static void +SIGINT_handler( int sig ) +{ + logf( XW_LOGERROR, "%s", __func__ ); + shutdown(); +} + +#ifdef SPAWN_SELF +static void +printWhy( int status ) +{ + if ( WIFEXITED(status) ) { + logf( XW_LOGINFO, "why: exited" ); + } else if ( WIFSIGNALED(status) ) { + logf( XW_LOGINFO, "why: signaled" ); + } else if ( WCOREDUMP(status) ) { + logf( XW_LOGINFO, "why: core" ); + } else if ( WIFSTOPPED(status) ) { + logf( XW_LOGINFO, "why: traced" ); + } +} /* printWhy */ +#endif + +static void +parentDied( int sig ) +{ + logf( XW_LOGINFO, "%s", __func__ ); + exit(0); +} + +int main( int argc, char** argv ) +{ + int port = 0; + int ctrlport = 0; + int nWorkerThreads = 0; + char* conffile = NULL; + const char* serverName = NULL; + const char* idFileName = NULL; + bool doDaemon = true; + bool doFork = true; + + /* Verify sizes here... */ + assert( sizeof(CookieID) == 2 ); + + + /* Read options. Options trump config file values when they conflict, but + the name of the config file is an option so we have to get that + first. */ + + for ( ; ; ) { + int opt = getopt(argc, argv, "h?c:p:n:i:f:t:DF" ); + + if ( opt == -1 ) { + break; + } + switch( opt ) { + case '?': + case 'h': + usage( argv[0] ); + exit( 0 ); + case 'c': + ctrlport = atoi( optarg ); + break; + case 'D': + doDaemon = false; + break; + case 'F': + doFork = false; + break; + case 'f': + conffile = optarg; + break; + case 'i': + idFileName = optarg; + break; + case 'n': + serverName = optarg; + break; + case 'p': + port = atoi( optarg ); + break; + case 't': + nWorkerThreads = atoi( optarg ); + break; + default: + usage( argv[0] ); + exit( 1 ); + } + } + + /* Did we consume all the options passed in? */ + if ( optind != argc ) { + usage( argv[0] ); + exit( 1 ); + } + + RelayConfigs::InitConfigs( conffile ); + RelayConfigs* cfg = RelayConfigs::GetConfigs(); + + if ( ctrlport == 0 ) { + ctrlport = cfg->GetCtrlPort(); + } + if ( nWorkerThreads == 0 ) { + nWorkerThreads = cfg->GetNWorkerThreads(); + } + if ( serverName == NULL ) { + serverName = cfg->GetServerName(); + } + if ( idFileName == NULL ) { + idFileName = cfg->GetIdFileName(); + } + + PermID::SetServerName( serverName ); + PermID::SetIDFileName( idFileName ); + + /* add signal handling here */ + + /* + The daemon() function is for programs wishing to detach themselves from + the controlling terminal and run in the background as system daemons. + + Unless the argument nochdir is non-zero, daemon() changes the current + working directory to the root ("/"). + + Unless the argument noclose is non-zero, daemon() will redirect standard + input, standard output and standard error to /dev/null. + + (This function forks, and if the fork() succeeds, the parent does + _exit(0), so that further errors are seen by the child only.) On + success zero will be returned. If an error occurs, daemon() returns -1 + and sets the global variable errno to any of the errors specified for + the library functions fork(2) and setsid(2). + */ + if ( doDaemon ) { + if ( 0 != daemon( true, false ) ) { + logf( XW_LOGERROR, "dev() => %s", strerror(errno) ); + exit( -1 ); + } + } + +#ifdef SPAWN_SELF + /* loop forever, relaunching children as they die. */ + while ( doFork ) { + pid_t pid = fork(); + if ( pid == 0 ) { /* child */ + break; + } else if ( pid > 0 ) { + int status; + logf( XW_LOGINFO, "parent waiting on child pid=%d", pid ); + time_t time_before = time( NULL ); + waitpid( pid, &status, 0 ); + printWhy( status ); + time_t time_after = time( NULL ); + doFork = time_after > time_before; + if ( !doFork ) { + logf( XW_LOGERROR, "exiting b/c respawned too quickly" ); + } + } else { + logf( XW_LOGERROR, "fork() => %s", strerror(errno) ); + } + } +#endif + + /* Arrange to be sent SIGUSR1 on death of parent. */ + prctl( PR_SET_PDEATHSIG, SIGUSR1 ); + + struct sigaction sact; + memset( &sact, 0, sizeof(sact) ); + sact.sa_handler = parentDied; + (void)sigaction( SIGUSR1, &sact, NULL ); + + if ( port != 0 ) { + g_listeners.AddListener( port ); + } + vector::const_iterator iter, end; + cfg->GetPorts( &iter, &end ); + while ( iter != end ) { + int port = *iter; + if ( !g_listeners.PortInUse( port ) ) { + g_listeners.AddListener( port ); + } else { + logf( XW_LOGERROR, "port %d was in use", port ); + } + ++iter; + } + + g_control = make_socket( INADDR_LOOPBACK, ctrlport ); + if ( g_control == -1 ) { + exit( 1 ); + } + + struct sigaction act; + memset( &act, 0, sizeof(act) ); + act.sa_handler = SIGINT_handler; + int err = sigaction( SIGINT, &act, NULL ); + logf( XW_LOGERROR, "sigaction=>%d", err ); + + XWThreadPool* tPool = XWThreadPool::GetTPool(); + tPool->Setup( nWorkerThreads, processMessage ); + + /* set up select call */ + fd_set rfds; + for ( ; ; ) { + FD_ZERO(&rfds); + g_listeners.AddToFDSet( &rfds ); + FD_SET( g_control, &rfds ); + int highest = g_listeners.GetHighest(); + if ( g_control > highest ) { + highest = g_control; + } + ++highest; + + int retval = select( highest, &rfds, NULL, NULL, NULL ); + if ( retval < 0 ) { + if ( errno != 4 ) { /* 4's what we get when signal interrupts */ + logf( XW_LOGINFO, "errno: %s (%d)", strerror(errno), errno ); + } + } else { + logf( XW_LOGINFO, "creating ListenersIter" ); + ListenersIter iter(&g_listeners, true); + while ( retval > 0 ) { + int listener = iter.next(); + if ( listener < 0 ) { + break; + } + + if ( FD_ISSET( listener, &rfds ) ) { + struct sockaddr_in newaddr; + socklen_t siz = sizeof(newaddr); + int newSock = accept( listener, (sockaddr*)&newaddr, &siz ); + + logf( XW_LOGINFO, "accepting connection from %s", + inet_ntoa(newaddr.sin_addr) ); + + tPool->AddSocket( newSock ); + --retval; + } + } + if ( FD_ISSET( g_control, &rfds ) ) { + run_ctrl_thread( g_control ); + --retval; + } + assert( retval == 0 ); + } + } + + g_listeners.RemoveAll(); + close( g_control ); + + delete cfg; + + return 0; +} // main diff --git a/xwords4/relay/xwrelay.h b/xwords4/relay/xwrelay.h new file mode 100644 index 000000000..296e9ea69 --- /dev/null +++ b/xwords4/relay/xwrelay.h @@ -0,0 +1,137 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _XWRELAY_H_ +#define _XWRELAY_H_ + +/* This file is meant to be included by both linux code that doesn't + include xptypes and from Crosswords client code.*/ + +/* Set if device is acting a server; cleared if as client */ +#define FLAGS_SERVER_BIT 0x01 + +#ifndef CANT_DO_TYPEDEF +typedef +#endif +enum { XWRELAY_NONE /* 0 is an illegal value */ + + , XWRELAY_GAME_CONNECT + /* Sent from device to relay to establish connection to relay. Format: + flags: 1; cookieLen: 1; cookie: ; hostID: 1; nPlayers: 1; + nPlayersTotal: 1 */ + + , XWRELAY_GAME_RECONNECT + /* Connect using connName rather than cookie. Used by a device that's + lost its connection to a game in progress. Once a game is locked + this is the only way a host can get (back) in. Format: flags: 1; + hostID: 1; nPlayers: 1; nPlayersTotal: 1; connNameLen: 1; + connName*/ + + , XWRELAY_GAME_DISCONNECT + /* Tell the relay that we're gone for this game. After this message is + sent, the host can reconnect on the same socket for a new game. + Format: cookieID: 2; srcID: 1 */ + + , XWRELAY_CONNECT_RESP + /* Sent from relay to device in response to XWRELAY_CONNECT. Format: + heartbeat_seconds: 2; connectionID: 2; assignedHostID: 1 */ + + , XWRELAY_RECONNECT_RESP + /* Sent from relay to device in response to XWRELAY_RECONNECT. Format: + heartbeat_seconds: 2; connectionID: 2; */ + + , XWRELAY_ALLHERE + /* Sent from relay when it enters the state where all expected devices + are here (at start of new game or after having been gone for a + while). Devices should not attempt to forward messages before this + message is received or after XWRELAY_DISCONNECT_OTHER is received. + Format: hasName: 1; [nameLen: 1; connName: */ + + , XWRELAY_DISCONNECT_YOU + /* Sent from relay when existing connection is terminated. + Format: errorCode: 1 */ + + , XWRELAY_DISCONNECT_OTHER + /* Another device has left the game. + Format: errorCode: 1; lostHostId: 1 */ + + , XWRELAY_CONNECTDENIED + /* The relay says go away. Format: reason code: 1 */ + +#ifdef RELAY_HEARTBEAT + , XWRELAY_HEARTBEAT + /* Sent in either direction. Format: cookieID: 2; srcID: 1 */ +#else + , _XWRELAY_HEARTBEAT /* don't use this */ +#endif + , XWRELAY_MSG_FROMRELAY + /* Sent from relay to device. Format: cookieID: 2; src_hostID: 1; + dest_hostID: 1; data */ + + , XWRELAY_MSG_TORELAY + /* Sent from device to relay. Format: connectionID: 2; src_hostID: + 1; dest_hostID: 1 */ +} +#ifndef CANT_DO_TYPEDEF + XWRelayMsg +#endif +; + +#ifndef CANT_DO_TYPEDEF +typedef unsigned char XWRELAY_Cmd; +#endif + +#define HOST_ID_NONE 0 +#define HOST_ID_SERVER 1 + +#define MAX_COOKIE_LEN 15 +#define MAX_MSG_LEN 256 /* 100 is more like it */ +#define MAX_CONNNAME_LEN 35 /* host id plus a small integer, typically */ + +#define XWRELAY_PROTO_VERSION 0x01 + +/* Errors passed with denied */ +#ifndef CANT_DO_TYPEDEF +typedef +#endif +enum { + XWRELAY_ERROR_NONE + ,XWRELAY_ERROR_OLDFLAGS /* You have the wrong flags */ + ,XWRELAY_ERROR_BADPROTO + ,XWRELAY_ERROR_RELAYBUSY + ,XWRELAY_ERROR_SHUTDOWN /* relay's going down */ + ,XWRELAY_ERROR_TIMEOUT /* Other players didn't show */ + ,XWRELAY_ERROR_HEART_YOU /* Haven't heard from somebody in too long */ + ,XWRELAY_ERROR_HEART_OTHER /* Haven't heard from other in too long */ + ,XWRELAY_ERROR_LOST_OTHER /* Generic other-left-we-dunno-why error */ + + ,XWRELAY_ERROR_LASTERR +} +#ifndef CANT_DO_TYPEDEF +XWREASON +#endif +; + +#ifndef CANT_DO_TYPEDEF +typedef unsigned short CookieID; +#endif + +#define COOKIE_ID_NONE 0L + +#endif diff --git a/xwords4/relay/xwrelay.sh b/xwords4/relay/xwrelay.sh new file mode 100755 index 000000000..5e6a1ec43 --- /dev/null +++ b/xwords4/relay/xwrelay.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +XWRELAY="./xwrelay" +PIDFILE=./xwrelay.pid + +CMD=$1 +shift + +case $CMD in + + stop) + if [ -f $PIDFILE ] && [ -f /proc/$(cat $PIDFILE)/exe ]; then + sync + echo "killing pid=$(cat $PIDFILE)" + kill $(cat $PIDFILE) + else + echo "not running" + fi + rm $PIDFILE + ;; + + restart) + $0 stop + sleep 1 + $0 start $@ + ;; + + start|*) + if [ -f $PIDFILE ] && [ -f /proc/$(cat $PIDFILE)/exe ]; then + echo "already running: pid=$(cat $PIDFILE)" + else + echo "starting..." + $XWRELAY $@ & + NEWPID=$! + echo $NEWPID > $PIDFILE + sleep 1 + echo "running with pid=$(cat $PIDFILE)" + fi + ;; + +esac diff --git a/xwords4/relay/xwrelay_priv.h b/xwords4/relay/xwrelay_priv.h new file mode 100644 index 000000000..4d44d4326 --- /dev/null +++ b/xwords4/relay/xwrelay_priv.h @@ -0,0 +1,30 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ + +#ifndef _XWRELAY_PRIV_H_ +#define _XWRELAY_PRIV_H_ + +#include +#include "lstnrmgr.h" + +typedef unsigned char HostID; + +typedef enum { + XW_LOGERROR + ,XW_LOGINFO + ,XW_LOGVERBOSE0 + ,XW_LOGVERBOSE1 +} XW_LogLevel; + +void logf( XW_LogLevel level, const char* format, ... ); + +void killSocket( int socket, const char* why ); + +int send_with_length_unsafe( int socket, unsigned char* buf, int bufLen ); + +time_t now(); + +int make_socket( unsigned long addr, unsigned short port ); + +extern class ListenerMgr g_listeners; + +#endif diff --git a/xwords4/symbian/aif/lrgicon.bmp b/xwords4/symbian/aif/lrgicon.bmp new file mode 100755 index 0000000000000000000000000000000000000000..ff4c530e6fb499bc8c485e9d43825717e0c07cb4 GIT binary patch literal 6494 zcmeH}!BGP-3`N703ZR3WIHmyZv_NI_3bMcb5t8k!W3hpm-64+rt<`^)JriEv-k(qT zxyX2y&rA6{oSw3Nmi3QEe6k?J=loM&jr&4^?f zsV60%FD#4z<(nd%CD6%by2?UNk6;u`dt<5-!=fF`hGoesIv;V$Sz;+J>e!pk%`}6v z#?o_u-muCGM(6YovT%c218fG_+!%vdU03^g+AP@%#7e*I(+-x@0pFLN((9M*@=QH> zkN)G@o@ktp)q}+z?$H<95qEdT7YkQba`fHxw{t~NXH>&6^gb-yIX16%=rO6p1@i9# ziqV<12MU+QK|0U5$FN9wqnPQ~U$4fo2!iL*R#)Q)7L9OoQ*x2H6RN&ulR)5TRyR(A z`ce@S%|>UR7R17iAn-n0lu#Biyf+J*LKacBG{WR8_eF!CwpWa2ZyLm6$^*xUSEp+% vy%zNJG0p + +RESOURCE AIF_DATA +{ + caption_list = { + CAPTION { code = ELangEnglish; caption = "Crosswords"; } + }; + + app_uid = 0x10206D64; + + num_icons = 1; +} diff --git a/xwords4/symbian/bmps/downarrow_80.bmp b/xwords4/symbian/bmps/downarrow_80.bmp new file mode 100644 index 0000000000000000000000000000000000000000..dba73570233500fcd95c7675e95261aa5b06617b GIT binary patch literal 102 zcmZ?rO=ExnJ0PV2#9UC!$iN7e0Er#pgJ7@#l=%PuKg0h63=E%v_#qG<0OAG*1~34L I!7xY-0Ik*(W&i*H literal 0 HcmV?d00001 diff --git a/xwords4/symbian/bmps/rightarrow_80.bmp b/xwords4/symbian/bmps/rightarrow_80.bmp new file mode 100644 index 0000000000000000000000000000000000000000..2047ced4a84d3730633c0b89af616bef660a3c96 GIT binary patch literal 102 zcmZ?rO=ExnJ0PV2#9UC!$iN7e0Er#pgJ7@#l=%PuKg0I}3=E%v_#+U10OAG*2nMl1 JVjww?8UVsB6;S{H literal 0 HcmV?d00001 diff --git a/xwords4/symbian/bmps/robot_80.bmp b/xwords4/symbian/bmps/robot_80.bmp new file mode 100644 index 0000000000000000000000000000000000000000..969f1b65f96b60c14dddb77eaf935457a5bf69de GIT binary patch literal 774 zcmZ?rWn*Rl12Z700mS@J%*Y@C7Qev6z#zm8!C(O(@gEhygrPL57w#%# t_aG}kV~?r_h5%ZaAakh_J`h8I#$q)Mt1QF~$m%gg&}89UxPfRk0st5eU+Mq= literal 0 HcmV?d00001 diff --git a/xwords4/symbian/bmps/star_80.bmp b/xwords4/symbian/bmps/star_80.bmp new file mode 100644 index 0000000000000000000000000000000000000000..2823e211489ee1614d541da4619e0b40fe6e0641 GIT binary patch literal 102 zcmZ?rO=ExnJ0PV2#9UC!$iN7e0Er#pgJ7@#l=%PuKZ8AxEeFIs4h#$j4lpo0gklgI KBnFZLsQ~~s0uIjr literal 0 HcmV?d00001 diff --git a/xwords4/symbian/bmps/turnicon_80.bmp b/xwords4/symbian/bmps/turnicon_80.bmp new file mode 100755 index 0000000000000000000000000000000000000000..8d965ebfa10735dfab03ba20750897a196666db4 GIT binary patch literal 774 zcmZ?rWn*Rl12Z700mS@J%*Y@C7Qes*rcj9g|No=#adQwVfm(<`aHEJ(jO+|b)I%)A vY64dGjH-u+6>|8%*=P*7Vju$!klAoS2m_aTWN}<9br6*R D&a4ge literal 0 HcmV?d00001 diff --git a/xwords4/symbian/group/.cvsignore b/xwords4/symbian/group/.cvsignore new file mode 100644 index 000000000..7f4de9a8f --- /dev/null +++ b/xwords4/symbian/group/.cvsignore @@ -0,0 +1,24 @@ +XWORDS.DSP +XWORDS.opt +ABLD.BAT +XWORDS.plg +XWORDS.SUP.MAKE +XWORDS.DSW +XWORDS.DSP +XWORDS.UID.CPP +xwords_*.mbg +xwords_*.pkg +xwords_*.aif +xwords_*.aifspec +xwords_*.ex1 +xwords_*.mbm +xwords_*.sis +xwords_*.rsg +xwords_*.app +xwords_*.rsc +icon.series*.mbm +*.def +*.ilk +*.pdb +*.ncb +*.OPT diff --git a/xwords4/symbian/group/Makefile b/xwords4/symbian/group/Makefile new file mode 100644 index 000000000..e5853a5e7 --- /dev/null +++ b/xwords4/symbian/group/Makefile @@ -0,0 +1,63 @@ +# -*- mode: makefile; -*- + +# The braindead build system doesn't remove object files from these +# dirs. So add to the clean target manually +# COMMON_OBJ_DIR = $(EPOC)/BUILD/CYGWIN$(shell pwd)/xwords/symbian/group/common/armi/urel +# XWORDS_OBJ_DIR = $(EPOC)/BUILD/CYGWIN$(shell pwd)/xwords/symbian/group/XWORDS/armi/urel + +ifdef VERB +BLDMAKE_FLAGS = -v +endif + +# There are no rules for building these puppies -- the symbian build +# system does that. But they'd better exist by the time we get to the +# .pkg target. +SISFILES = \ + $(EPOC)/release/armi/urel/xwords.APP \ + $(EPOC)\\data/z/system/apps/xwords/xwords.rsc \ + $(EPOC)\\data/z/system/apps/xwords/xwords.aif \ + $(EPOC)\\data/z/system/apps/xwords/xwords.mbm \ + ../../dawg/English/BasEnglish2to8.xwd \ + +all: wins + +ABLD.BAT: + bldmake $(BLDMAKE_FLAGS) bldfiles + +wins: ABLD.BAT + $< build $(BLDMAKE_FLAGS) wins udeb + +armi: ABLD.BAT + $< build $(BLDMAKE_FLAGS) armi urel + +# depends on wins because that's where the .aif file gets built +sis: wins armi xwords.pkg + makesis -v xwords.pkg + +# If this xwords.pkg target generates errors -- especially the &EN +# part -- you're probably running symbian's make instead of cygwin's. +# Try invoking this Makefile with /usr/bin/make explicitly: +# /usr/bin/make sis +xwords.pkg: Makefile $(SISFILES) + rm -f $@ + @echo "&EN" | tr -d '\r' >> $@ + @echo '#{"Crosswords"},(0x10206D64),1,0,0' | tr -d '\r' >> $@ + @for f in $(SISFILES); do \ + bname=$$(basename "$$f"); \ + echo \"$$f\"-\"C:\\system\\apps\\xwords\\$$bname\" >> $@; \ + done + +# build project files for M$ VC++ version 6, e.g. for source-level +# debugging +vc6: + makmake.exe xwords vc6 + +clean: + rm -f xwords.SIS xwords.pkg + rm -f $(COMMON_OBJ_DIR)/*.o + rm -f $(XWORDS_OBJ_DIR)/*.o + if [ -f abLD.BAT ]; then \ + ./abLD.BAT clean wins udeb; \ + ./abLD.BAT clean armi udeb; \ + fi + bldmake $(BLDMAKE_FLAGS) clean diff --git a/xwords4/symbian/group/bld.inf b/xwords4/symbian/group/bld.inf new file mode 100644 index 000000000..ad40c8577 --- /dev/null +++ b/xwords4/symbian/group/bld.inf @@ -0,0 +1,5 @@ +PRJ_MMPFILES + +common.mmp +xwords.mmp + diff --git a/xwords4/symbian/group/bldlinux.mk b/xwords4/symbian/group/bldlinux.mk new file mode 100644 index 000000000..9d10fe7c0 --- /dev/null +++ b/xwords4/symbian/group/bldlinux.mk @@ -0,0 +1,186 @@ +# -*- mode: Makefile; compile-command: "make -f bldlinux.mk" -*- + +SERIES ?= 80 +DEBUG ?= TRUE + +# User should define EPOC_80 and/or EPOC_60 in the environment +EPOC = $(EPOC_$(SERIES)) + + +PATH = $(EPOC)/bin:/local/bin:/usr/bin:/bin + +BMCONV = bmconv + +include $(EPOC)/lib/makerules/eikon + +COMMONDIR = ../../common +PLATFORM = SYMB_$(SERIES) +XWORDS_DIR = \"xwords_$(SERIES)\" + +include ../../common/config.mk + +#STANDALONE_ONLY ?= -DXWFEATURE_STANDALONE_ONLY +BEYOND_IR = -DBEYOND_IR + +LIBS_ALLSERIES = \ + $(EPOCTRGREL)/euser.lib \ + $(EPOCTRGREL)/apparc.lib \ + $(EPOCTRGREL)/cone.lib \ + $(EPOCTRGREL)/gdi.lib \ + $(EPOCTRGREL)/eikcoctl.lib \ + $(EPOCTRGREL)/eikcore.lib \ + $(EPOCTRGREL)/bafl.lib \ + $(EPOCTRGREL)/egul.lib \ + $(EPOCTRGREL)/estlib.lib \ + $(EPOCTRGREL)/flogger.lib \ + $(EPOCTRGREL)/commonengine.lib \ + $(EPOCTRGREL)/eikdlg.lib \ + $(EPOCTRGREL)/fbscli.lib \ + $(EPOCTRGREL)/efsrv.lib \ + $(EPOCTRGREL)/estor.lib \ + $(EPOCTRGREL)/ws32.lib \ + $(EPOCTRGREL)/insock.lib \ + $(EPOCTRGREL)/esock.lib \ + +LIBS_60 = \ + $(EPOCTRGREL)/eikcore.lib \ + $(EPOCTRGREL)/avkon.lib \ + $(EPOCTRGREL)/eikcdlg.lib \ + +LIBS_80 = \ + $(EPOCTRGREL)/ckndlg.lib \ + $(EPOCTRGREL)/ckncore.lib \ + $(EPOCTRGREL)/eikfile.lib \ + $(EPOCTRGREL)/eikctl.lib \ + $(EPOCTRGREL)/bitgdi.lib \ + +LIBS = $(LIBS_ALLSERIES) $(LIBS_$(SERIES)) + +# fntstr.lib \ +# $(EPOCTRGREL)/bitgdi.lib \ + +NAME = xwords_$(SERIES) +USERNAME = Crosswords +ARCH = series$(SERIES) +SYMARCH = SERIES_$(SERIES) + +SRCDIR = ../src +INCDIR = -I. -I $(EPOC)/include -I $(EPOC)/include/libc -I../inc \ + $(subst ../,../../,$(COMMON_INCS)) +LCLSRC = \ + $(SRCDIR)/xwmain.cpp \ + $(SRCDIR)/xwapp.cpp \ + $(SRCDIR)/symaskdlg.cpp \ + $(SRCDIR)/symdraw.cpp \ + $(SRCDIR)/xwappview.cpp \ + $(SRCDIR)/symdict.cpp \ + $(SRCDIR)/symutil.cpp \ + $(SRCDIR)/xwappui.cpp \ + $(SRCDIR)/xwdoc.cpp \ + $(SRCDIR)/symgmmgr.cpp \ + $(SRCDIR)/symgmdlg.cpp \ + $(SRCDIR)/symblnk.cpp \ + $(SRCDIR)/symgamdl.cpp \ + $(SRCDIR)/symgamed.cpp \ + $(SRCDIR)/symssock.cpp \ + +IMG_SRC = ../bmps/downarrow_80.bmp \ + ../bmps/rightarrow_80.bmp \ + ../bmps/star_80.bmp \ + ../bmps/turnicon_80.bmp \ + ../bmps/turniconmask_80.bmp \ + ../bmps/robot_80.bmp \ + ../bmps/robotmask_80.bmp \ + +AIF = ../aif +ICON_SRC = \ + $(AIF)/lrgicon.bmp \ + $(AIF)/lrgiconmask.bmp \ + +OBJDIR = $(SRCDIR)/$(PLATFORM) + +OBJECTS = $(patsubst $(SRCDIR)/%,$(OBJDIR)/%,$(LCLSRC:.cpp=.o)) $(COMMONOBJ) + +THEAPP = $(NAME).app +MAJOR = 4 +MINOR = 1 +PKGVERS = $(MAJOR),$(MINOR) +SISNAME = $(NAME)-$(MAJOR).$(MINOR)-$(ARCH).sis + +MBG = $(NAME).mbg + +PKGFILES=$(THEAPP) $(NAME).aif $(NAME).rsc $(NAME).mbm BasEnglish2to8.xwd + +U1 = 1000007a +U2 = 100039ce +U3 = 10206D64 + +ifeq ($(DEBUG),TRUE) +DEBUG_FLAGS = -DDEBUG -DMEM_DEBUG +else +OPT = -O2 -fomit-frame-pointer +endif + +CFLAGS += $(OPT) -I. -DUID3=0x$(U3) $(DEBUG_FLAGS) \ + -DXWORDS_DIR=$(XWORDS_DIR) \ + -D__LITTLE_ENDIAN -DKEYBOARD_NAV \ + -DKEY_SUPPORT -DFEATURE_TRAY_EDIT -DNODE_CAN_4 \ + $(STANDALONE_ONLY) -D$(SYMARCH) \ + -DSYM_ARMI -DOS_INITS_DRAW $(BEYOND_IR) \ + $(INCDIR) + +# This violates the no-data rule. Don't allow it for ARMI build. +# It's ok for WINS builds since the rules are relaxed there. + +# CFLAGS += -DSTUBBED_DICT + +CPFLAGS = $(CFLAGS) -DCPLUS + +# Following is used for the resource file +CPPFLAGS += -D_EPOC32_6 -DCPLUS -I../inc -D$(SYMARCH) \ + $(subst ../,../../,$(COMMON_INCS)) + +all: _sanity $(PKGFILES) $(NAME).sis + mv $(NAME).sis $(SISNAME) +ifdef XW_UPLOAD_CMD + $(XW_UPLOAD_CMD) $(SISNAME) +endif + +_sanity: + @if [ "$(EPOC_$(SERIES))" = "" ]; then \ + echo " ---> ERROR: EPOC_$(SERIES) undefined in env"; \ + exit 1; \ + fi + +$(THEAPP): $(NAME).rsc $(MBG) $(OBJECTS) + +icon.$(ARCH).mbm: $(ICON_SRC) + $(BMCONV) $@ $(subst ..,/c8..,$^) + +$(NAME).aifspec: icon.$(ARCH).mbm + @echo "mbmfile=$<" > $@ + @echo "ELangEnglish=$(USERNAME)" >> $@ + +# I'm adding my own rules here because I can't figure out how to use +# the default ones when src and obj live in different directories. +$(COMMONOBJDIR)/%.o: $(COMMONDIR)/%.c + mkdir -p $(COMMONOBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(OBJDIR)/%.o: $(SRCDIR)/%.cpp + mkdir -p $(OBJDIR) + $(CCC) $(CPFLAGS) -c -o $@ $< + +$(NAME).mbg $(NAME).mbm: $(IMG_SRC) + $(BMCONV) /h$(NAME).mbg $(NAME).mbm $(subst ..,/2..,$(IMG_SRC)) + +# temporary hack until I get 'round to breaking the .rss file into +# common, 60 and 80 (with the latter two including the first). +$(NAME).rss: xwords.rss + ln -s $< $@ + +BasEnglish2to8.xwd: ../../dawg/English/BasEnglish2to8.xwd + ln -s $< $@ + +clean: + rm -f $(GENERATED) $(NAME).aifspec $(OBJECTS) $(MBG) *.mbm diff --git a/xwords4/symbian/group/bldwin.mk b/xwords4/symbian/group/bldwin.mk new file mode 100755 index 000000000..a201da672 --- /dev/null +++ b/xwords4/symbian/group/bldwin.mk @@ -0,0 +1,303 @@ +# -*- mode: Makefile; compile-command: "/usr/bin/make -f bldwin.mk" -*- + +SERIES ?= 80 +TARGET ?= WINS + +#U1 = 1000007a +U1 = 10000079 +U2 = 100039ce +U3 = 10206D64 + +# User should define EPOC_80 and/or EPOC_60 in the environment +EPOC = $(EPOC_$(SERIES)) +NAME = xwords_$(SERIES) +DESTDIR = $(EPOC)/wins/c/system/Apps/$(NAME) +DICT = ../../dawg/English/BasEnglish2to8.xwd +EDLL_LIB = $(EPOC)/release/wins/udeb/edll.lib + +#CPP = $(EPOC)/gcc/bin/gcc -E +CPP = $(EPOC)/gcc/arm-epoc-pe/bin/gcc.exe -E - +RC = rcomp +GA = genaif +PT = $(EPOC)/tools/petran +LIB = lib.exe +PERL = c:/activePerl/bin/perl +MAKEDEF = makedef.pl +EPOCRC = epocrc.pl +ECOPYFILE = ecopyfile.pl +LINK = $(MSVC_DIR)/Bin/link.exe +DUMPBIN = $(MSVC_DIR)/Bin/dumpbin.exe +LIB = $(MSVC_DIR)/Bin/lib.exe +ECHO = /usr/bin/echo + +#STANDALONE_ONLY = -DXWFEATURE_STANDALONE_ONLY + +COMMON_FLAGS = \ + -D$(SYMARCH) -D__LITTLE_ENDIAN -DKEYBOARD_NAV \ + -DKEY_SUPPORT -DFEATURE_TRAY_EDIT -DNODE_CAN_4 \ + -DOS_INITS_DRAW $(STANDALONE_ONLY) + +RSS_DEFS = $(COMMON_FLAGS) + +################################################## +# WINS- vs ARMI-specific settings +################################################## +ifeq ($(TARGET),WINS) +######################### +# WINS +######################### +CC = $(VSDIR)/VC98/Bin/CL.EXE +CL_FLAGS = \ + /MDd /Zi /Yd /Od /nologo /Zp4 /GF /QIfist /X /W4 \ + /D _DEBUG /D _UNICODE /D UNICODE /D "__SYMBIAN32__" \ + /D "__VC32__" /D "__WINS__" \ + +CFLAGS += $(CL_FLAGS) -I. -DUID3=0x$(U3) $(DEBUG_FLAGS) \ + -D$(SYMARCH) $(COMMON_FLAGS) \ + -DSYM_WINS \ + $(INCDIR) + +else +ifeq ($(TARGET),ARMI) +######################### +# ARMI (incomplete; build +# with linux. :-) +######################### + +CC = $(EPOC)/gcc/bin/g++.exe + +endif +endif + +BMCONV = bmconv + +COMMONDIR = ../../common +PLATFORM = SYMB_$(SERIES)_$(TARGET) +XWORDS_DIR = \"xwords_$(SERIES)\" + +include $(COMMONDIR)/config.mk + +EPOCTRGREL = $(EPOC)/release/wins/udeb + +LIBS_ALLSERIES = \ + $(EPOCTRGREL)/euser.lib \ + $(EPOCTRGREL)/apparc.lib \ + $(EPOCTRGREL)/cone.lib \ + $(EPOCTRGREL)/gdi.lib \ + $(EPOCTRGREL)/eikcoctl.lib \ + $(EPOCTRGREL)/eikcore.lib \ + $(EPOCTRGREL)/bafl.lib \ + $(EPOCTRGREL)/egul.lib \ + $(EPOCTRGREL)/estlib.lib \ + $(EPOCTRGREL)/flogger.lib \ + $(EPOCTRGREL)/commonengine.lib \ + $(EPOCTRGREL)/eikdlg.lib \ + $(EPOCTRGREL)/fbscli.lib \ + $(EPOCTRGREL)/efsrv.lib \ + $(EPOCTRGREL)/estor.lib \ + $(EPOCTRGREL)/ws32.lib \ + $(EPOCTRGREL)/insock.lib \ + $(EPOCTRGREL)/esock.lib \ + +LIBS_60 = \ + $(EPOCTRGREL)/eikcore.lib \ + $(EPOCTRGREL)/avkon.lib \ + $(EPOCTRGREL)/eikcdlg.lib \ + +LIBS_80 = \ + $(EPOCTRGREL)/ckndlg.lib \ + $(EPOCTRGREL)/ckncore.lib \ + $(EPOCTRGREL)/eikfile.lib \ + $(EPOCTRGREL)/eikctl.lib \ + $(EPOCTRGREL)/bitgdi.lib \ + +LIBS = $(LIBS_ALLSERIES) $(LIBS_$(SERIES)) + +STAGE_BOTH_LINK_FLAGS = \ + $(EDLL_LIB) $(LIBS) /nologo \ + /include:"?_E32Dll@@YGHPAXI0@Z" /nodefaultlib \ + /entry:"_E32Dll" /subsystem:windows /dll \ + /out:$(NAME).app \ + /machine:IX86 \ + +STAGE1_LINK_FLAGS = $(STAGE_BOTH_LINK_FLAGS) \ + /incremental:no \ + +STAGE2_LINK_FLAGS = $(STAGE_BOTH_LINK_FLAGS) \ + $(NAME).exp /debug \ + +ARCH = series$(SERIES) +SYMARCH = SERIES_$(SERIES) + +INC = ../inc +SRCDIR = ../src +UID_CPP = $(SRCDIR)/$(NAME).UID.cpp +INCDIR = -I $(EPOC)/include -I $(EPOC)/include/libc -I$(INC) \ + $(subst ../,../../,$(COMMON_INCS)) + +LCLSRC = \ + $(SRCDIR)/xwmain.cpp \ + $(UID_CPP) \ + $(SRCDIR)/xwapp.cpp \ + $(SRCDIR)/symaskdlg.cpp \ + $(SRCDIR)/symdraw.cpp \ + $(SRCDIR)/xwappview.cpp \ + $(SRCDIR)/symdict.cpp \ + $(SRCDIR)/symutil.cpp \ + $(SRCDIR)/xwappui.cpp \ + $(SRCDIR)/xwdoc.cpp \ + $(SRCDIR)/symgmmgr.cpp \ + $(SRCDIR)/symgmdlg.cpp \ + $(SRCDIR)/symblnk.cpp \ + $(SRCDIR)/symgamdl.cpp \ + $(SRCDIR)/symgamed.cpp \ + $(SRCDIR)/symssock.cpp \ + +IMG_SRC = ../bmps/downarrow_80.bmp \ + ../bmps/rightarrow_80.bmp \ + ../bmps/star_80.bmp \ + ../bmps/turnicon_80.bmp \ + ../bmps/turniconmask_80.bmp \ + ../bmps/robot_80.bmp \ + ../bmps/robotmask_80.bmp \ + +INCLUDES = \ + $(NAME).rsg \ + $(NAME).mbg \ + $(INC)/symaskdlg.h \ + $(INC)/symblnk.h \ + $(INC)/symdict.h \ + $(INC)/symdraw.h \ + $(INC)/symgamdl.h \ + $(INC)/symgamed.h \ + $(INC)/symgmdlg.h \ + $(INC)/symgmmgr.h \ + $(INC)/symutil.h \ + $(INC)/xptypes.h \ + $(INC)/xwapp.h \ + $(INC)/xwappui.h \ + $(INC)/xwappview.h \ + $(INC)/xwdoc.h \ + $(INC)/xwords.hrh \ + $(INC)/LocalizedStrIncludes.h \ + +AIF = ../aif +ICON_SRC = \ + $(AIF)/lrgicon.bmp \ + $(AIF)/lrgiconmask.bmp \ + +OBJDIR = $(SRCDIR)/$(PLATFORM) + +OBJECTS = $(patsubst $(SRCDIR)/%,$(OBJDIR)/%,$(LCLSRC:.cpp=.o)) $(COMMONOBJ) + +MAJOR = 4 +MINOR = 1 +PKGVERS = $(MAJOR),$(MINOR) + +MBG = $(NAME).mbg + +#PKGFILES=$(THEAPP) $(NAME).aif $(NAME).rsc $(NAME).mbm BasEnglish2to8.xwd +PKGFILES = $(NAME).app $(NAME).rsc $(NAME).mbm $(NAME).pdb $(DICT) + +DEBUG_FLAGS = -DDEBUG -DMEM_DEBUG + +CPFLAGS = $(CFLAGS) -DCPLUS + +# Following is used for the resource file +CPPFLAGS = -I$(EPOC)/include -I../inc + +ifdef VERBOSE +else +AMP = @ +endif + +ifeq ($(TARGET),WINS) +all: wins +else +ifeq ($(TARGET),ARMI) +all: armi +else +all: define_TARGET_please +endif +endif + +wins: _sanity $(PKGFILES) + @mkdir -p $(DESTDIR) + cp $(PKGFILES) $(DESTDIR) + +_sanity: + @if [ "$(EPOC_$(SERIES))" = "" ]; then \ + $(ECHO) " ---> ERROR: EPOC_$(SERIES) undefined in env"; \ + exit 1; \ + fi + +icon.$(ARCH).mbm: $(ICON_SRC) + $(BMCONV) $@ $(subst ..,/c8..,$^) + +$(NAME).aifspec: icon.$(ARCH).mbm + @$(ECHO) "mbmfile=$<" > $@ + @$(ECHO) "ELangEnglish=$(NAME)" >> $@ + +# I'm adding my own rules here because I can't figure out how to use +# the default ones when src and obj live in different directories. +$(COMMONOBJDIR)/%.o: $(COMMONDIR)/%.c + $(AMP)mkdir -p $(COMMONOBJDIR) + $(AMP)$(CC) $(CFLAGS) /c /Fo$@ $< + +$(OBJDIR)/%.o: $(SRCDIR)/%.cpp $(INCLUDES) + $(AMP)mkdir -p $(OBJDIR) + $(AMP)$(CC) $(CPFLAGS) /c /Fo$@ $< + +$(NAME).mbg $(NAME).mbm: $(IMG_SRC) + $(BMCONV) /h$(NAME).mbg $(NAME).mbm $(subst ..,/2..,$(IMG_SRC)) + +$(NAME).rss: xwords.rss + cp $< $@ + +clean: + rm -rf $(GENERATED) $(NAME).aifspec $(OBJECTS) $(MBG) *.mbm *.rpp *.rsc \ + *.rsg *.app $(UID_CPP) + +# remove saved games and data file +clean_state: + rm -rf $(EPOC)/wins/c/system/Apps/$(NAME)/* + rm -rf $(EPOC)/wins/c/system/Apps/$(NAME) + rm -rf $(EPOC)/release/wins/udeb/z/system/apps/$(NAME)/* + rm -rf $(EPOC)/release/wins/udeb/z/system/apps/$(NAME) + +############################################################################# +# from here down added from the linux build system or stolen from +# makefiles generated by the symbian system +############################################################################# + +$(UID_CPP): ./bldwin.mk + rm -f $@ + $(ECHO) "// Make-generated uid source file" >> $@ + $(ECHO) "#include " >> $@ + $(ECHO) "#pragma data_seg(\".E32_UID\")" >> $@ + $(ECHO) "__WINS_UID(0x$(U1),0x$(U2),0x$(U3))" >> $@ + $(ECHO) "#pragma data_seg()" >> $@ + +%.rsc %.rsg: %.rss + $(PERL) -S $(EPOCRC) $(RSS_DEFS) -I "." -I "..\inc" \ + $(subst ../,../../,$(COMMON_INCS)) -I- -I $(EPOC)/include \ + -DLANGUAGE_SC -u $< -o$*.rsc -h$*.rsg -l./ + +%.aif: %.aifspec + @$(ECHO) "[GENAIF] $*" + $(AMP)$(GA) -u 0x$(U3) $< $@ + +$(NAME).app : $(OBJECTS) $(EDLL_LIB) $(LIBS) + @$(ECHO) building $@ + $(AMP)$(LINK) $(STAGE1_LINK_FLAGS) $(OBJECTS) + $(AMP)rm -f $@ $(NAME).exp + $(AMP)$(DUMPBIN) /exports /out:$(NAME).inf $(NAME).lib + $(AMP)rm -f $(NAME).lib + $(AMP)$(PERL) -S $(MAKEDEF) -Inffile $(NAME).inf \ + -1 ?NewApplication@@YAPAVCApaApplication@@XZ $(NAME).def + $(AMP)rm $(NAME).inf + $(AMP)$(LIB) /nologo /machine:i386 /nodefaultlib \ + /name:$@ /def:$(NAME).def /out:$(NAME).lib + $(AMP)rm -f $(NAME).lib + $(AMP)$(LINK) $(STAGE2_LINK_FLAGS) $(OBJECTS) + $(AMP)rm -f $(NAME).exp diff --git a/xwords4/symbian/group/common.mmp b/xwords4/symbian/group/common.mmp new file mode 100644 index 000000000..7c257bc87 --- /dev/null +++ b/xwords4/symbian/group/common.mmp @@ -0,0 +1,31 @@ +TARGET common.lib +TARGETTYPE lib + +MACRO DEBUG MEM_DEBUG +MACRO __LITTLE_ENDIAN KEYBOARD_NAV OS_INITS_DRAW +MACRO KEY_SUPPORT FEATURE_TRAY_EDIT NODE_CAN_4 +MACRO XWFEATURE_STANDALONE_ONLY + +SOURCEPATH ..\..\common +SOURCE board.c +SOURCE tray.c +SOURCE draw.c +SOURCE model.c +SOURCE mscore.c +SOURCE server.c +SOURCE pool.c +SOURCE game.c +SOURCE dictnry.c +SOURCE engine.c +SOURCE memstream.c +SOURCE comms.c +SOURCE mempool.c +SOURCE movestak.c +SOURCE strutils.c +SOURCE vtabmgr.c + + +USERINCLUDE ..\inc +SYSTEMINCLUDE \epoc32\include +SYSTEMINCLUDE \epoc32\include\libc + diff --git a/xwords4/symbian/group/xwords.mmp b/xwords4/symbian/group/xwords.mmp new file mode 100644 index 000000000..11d463fb1 --- /dev/null +++ b/xwords4/symbian/group/xwords.mmp @@ -0,0 +1,88 @@ +TARGET xwords.app +TARGETTYPE app + +UID 0x100039CE 0x10206D64 + +// Comment the next line out for a non-debug build. Isn't this build +// system advanced? +MACRO DEBUG MEM_DEBUG _DEBUG +MACRO CPLUS +MACRO __LITTLE_ENDIAN KEYBOARD_NAV OS_INITS_DRAW +MACRO KEY_SUPPORT FEATURE_TRAY_EDIT NODE_CAN_4 +MACRO XWFEATURE_STANDALONE_ONLY +MACRO SERIES_80 SYMBIAN + + +//FOOBAR + +TARGETPATH \system\apps\xwords +LANG SC + +SOURCEPATH ..\src +SOURCE xwmain.cpp +SOURCE xwapp.cpp +SOURCE xwappview.cpp +SOURCE xwappui.cpp +SOURCE xwdoc.cpp +SOURCE symdraw.cpp +SOURCE symutil.cpp +SOURCE symaskdlg.cpp +SOURCE symgamdl.cpp +SOURCE symdict.cpp +SOURCE symblnk.cpp +SOURCE symgmmgr.cpp +SOURCE symgmdlg.cpp +SOURCE symgamed.cpp + +SOURCEPATH ..\group +RESOURCE xwords.rss + +USERINCLUDE ..\inc +USERINCLUDE ..\..\common + +SYSTEMINCLUDE \epoc32\include +SYSTEMINCLUDE \epoc32\include\libc + +START BITMAP xwords.mbm +HEADER +SOURCEPATH ..\bmps +SOURCE 2 downarrow_80.bmp +SOURCE 2 rightarrow_80.bmp +SOURCE 2 star_80.bmp +SOURCE C12 turnicon_80.bmp +SOURCE 2 turniconmask_80.bmp +SOURCE C12 robot_80.bmp +SOURCE 2 robotmask_80.bmp +END + +LIBRARY common.lib + +LIBRARY gdi.lib +LIBRARY euser.lib +LIBRARY apparc.lib +LIBRARY cone.lib +LIBRARY eikcore.lib +LIBRARY eikcoctl.lib +LIBRARY bafl.lib +LIBRARY egul.lib +LIBRARY ckndlg.lib +LIBRARY ckncore.lib +LIBRARY estlib.lib +LIBRARY flogger.lib +LIBRARY commonengine.lib +LIBRARY eikdlg.lib +LIBRARY EIKFILE.lib +LIBRARY eikctl.lib +// Bitmaps +LIBRARY bitgdi.lib +LIBRARY fbscli.lib +// FindWildByDir etc. +LIBRARY EFSRV.lib +// convert to unicode +// LIBRARY CHARCONV.lib +// RFileReadStream +LIBRARY ESTOR.lib +// Window.Invalidate +LIBRARY WS32.lib + +AIF xwords.aif ..\aif xwaif.rss c8 lrgicon.bmp lrgiconmask.bmp diff --git a/xwords4/symbian/group/xwords.rss b/xwords4/symbian/group/xwords.rss new file mode 100644 index 000000000..b4632fdfd --- /dev/null +++ b/xwords4/symbian/group/xwords.rss @@ -0,0 +1,620 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). (based on sample + * app helloworldbasic "Copyright (c) 2002, Nokia. All rights + * reserved.") + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +NAME XWRD + +/* I can't get macros in MMC to be honored here; though the compiler complains + about double-definition it doesn't honor the one in the mmp file. */ + +#include +#include + +#if defined SERIES_60 +# include +# include +#endif + +#include "xwords.hrh" + +#define CANT_DO_TYPEDEF +#include "xwrelay.h" +#undef CANT_DO_TYPEDEF + +// --------------------------------------------------------- +// +// Define the resource file signature +// This resource should be empty. +// +// --------------------------------------------------------- +// +RESOURCE RSS_SIGNATURE { } + +// --------------------------------------------------------- +// +// Default Document Name +// +// --------------------------------------------------------- +// +RESOURCE TBUF r_default_document_name { buf=""; } + +// --------------------------------------------------------- +// +// Define default menu, hotkeys and CBA keys. +// +// --------------------------------------------------------- +// +RESOURCE EIK_APP_INFO +{ + menubar = r_xwords_menubar; + cba = r_xwords_cba; + hotkeys = r_xwords_hotkeys; +} + +// --------------------------------------------------------- +// +// r_xwords_hotkeys +// +// --------------------------------------------------------- +// +RESOURCE HOTKEYS r_xwords_hotkeys +{ + control = + { + HOTKEY + { + command = EEikCmdExit; + key = 'e'; + } + }; +} + +// --------------------------------------------------------- +// +// r_xwords_cba +// +// --------------------------------------------------------- +// +RESOURCE CBA r_xwords_cba +{ + buttons= + { + CBA_BUTTON + { + id=XW_JUGGLE_COMMAND; + txt="Juggle"; + }, + CBA_BUTTON + { + id=XW_NEXTHINT_COMMAND; + txt="Next Hint"; + }, + CBA_BUTTON + { + id=XW_HIDETRAY_COMMAND; + txt="Hide tray"; + }, + CBA_BUTTON + { + id=XW_DONE_COMMAND; + txt="Done"; + } + }; +} + +// --------------------------------------------------------- +// +// r_xwords_menubar +// +// --------------------------------------------------------- +// +RESOURCE MENU_BAR r_xwords_menubar + { + titles = + { + MENU_TITLE {menu_pane = r_xwords_file_menu; txt = "File";} + ,MENU_TITLE {menu_pane = r_xwords_game_menu; txt = "Game";} + ,MENU_TITLE {menu_pane = r_xwords_move_menu; txt = "Move";} + }; + } + + +// --------------------------------------------------------- +// +// r_xwords_file_menu +// Menu for "Options" +// +// --------------------------------------------------------- +// +RESOURCE MENU_PANE r_xwords_file_menu +{ + items = + { + MENU_ITEM {command = XW_NEWGAME_COMMAND; txt = "New game";} + ,MENU_ITEM {command = XW_SAVEDGAMES_COMMAND; txt = "Saved games";} + ,MENU_ITEM {command = XW_PREFS_COMMAND; txt = "Preferences";} + ,MENU_ITEM {command = XW_ABOUT_COMMAND; txt = "About";} + + ,MENU_ITEM {command = EEikCmdExit; txt = "Exit";} + }; +} + +RESOURCE MENU_PANE r_xwords_game_menu +{ + items = + { + MENU_ITEM {command = XW_VALUES_COMMAND; txt = "Tile values";} + ,MENU_ITEM {command = XW_REMAIN_COMMAND; txt = "Remaining tiles";} + ,MENU_ITEM {command = XW_CURINFO_COMMAND; txt = "Game info";} + ,MENU_ITEM {command = XW_HISTORY_COMMAND; txt = "History";} + ,MENU_ITEM {command = XW_FINALSCORES_COMMAND; txt = "Final scores";} + ,MENU_ITEM {command = XW_FLIP_COMMAND; txt = "Flip board"; } + ,MENU_ITEM {command = XW_TOGGLEVALS_COMMAND; txt = "Toggle values"; } +#ifndef XWFEATURE_STANDALONE_ONLY + ,MENU_ITEM {command = XW_RESEND_COMMAND; txt = "Resend messages"; } +#endif + }; +} + +RESOURCE MENU_PANE r_xwords_move_menu +{ + items = + { + MENU_ITEM {command = XW_HINT_COMMAND; txt = "Hint";} +/* #ifdef XWFEATURE_SEARCHLIMIT */ +/* ,MENU_ITEM {command = XW_LIMHINT_COMMAND; txt = "Limited hint";} */ +/* #endif */ + ,MENU_ITEM {command = XW_NEXTHINT_COMMAND; txt = "Next hint";} + ,MENU_ITEM {command = XW_UNDOCUR_COMMAND; txt = "Undo current";} + ,MENU_ITEM {command = XW_UNDOLAST_COMMAND; txt = "Undo last";} + ,MENU_ITEM {command = XW_DONE_COMMAND; txt = "Turn done";} +/* ,MENU_ITEM {command = XW_JUGGLE_COMMAND; txt = "Juggle";} */ + ,MENU_ITEM {command = XW_TRADE_COMMAND; txt = "Trade";} +/* ,MENU_ITEM {command = XW_HIDETRAY_COMMAND; txt = "[Un]hide tray";} */ + }; +} + +/* Apparently you have to use a resource type large enough to hold the + string. There are TBUF16, TBUF32, TBUF40, TBUF48,TBUF64, 128 and + 256. Why a system that can generate constants from the resource + IDs can't match resource types to strings is beyond me. +*/ + +RESOURCE TBUF32 r_no_peek_alert { + buf="No peeking at the robot's tiles."; } + +RESOURCE TBUF40 r_tiles_in_line_alert { + buf ="All tiles played must be in a line."; +} + +RESOURCE TBUF64 r_no_empties_sep_alert { + buf ="Empty squares cannot separate pieces played."; +} + +RESOURCE TBUF64 r_two_tiles_first_move_alert { + buf = "Must play two or more pieces on the first move."; +} + +RESOURCE TBUF128 r_placed_must_contact_alert { + buf = "New tiles must contact others already in place (or the middle square on the first move)."; +} + +RESOURCE TBUF32 r_too_few_to_trade_alert { + buf = "Too few tiles left to trade."; +} + +RESOURCE TBUF64 r_not_your_turn_alert { + buf = "You can't do that; it's not your turn!"; +} + +RESOURCE TBUF40 r_remove_first_alert { + buf = "Remove played tiles before trading."; +} + +RESOURCE TBUF64 r_nothing_to_undo_alert { + buf = "Nothing to undo. (Initial tile picking cannot be undone.)"; +} + +RESOURCE TBUF64 r_confirm_end_game { + buf = "Are you sure you want to end the game now?"; +} + +RESOURCE TBUF64 r_alert_no_dicts { + buf = "Crosswords requires at least one dictionary."; +} + +RESOURCE TBUF32 r_alert_feature_pending { + buf = "Feature not yet implemented."; +} + +RESOURCE TBUF64 r_alert_no_delete_open_game { + buf = "You can't delete the current game."; +} + +RESOURCE TBUF64 r_confirm_trade { + buf = "Are you sure you want to use your turn trading tiles?"; +} + +RESOURCE TBUF64 r_confirm_delgame { + buf = "Are you sure you want delete the selected game?"; +} + +RESOURCE TBUF64 r_alert_no_rename_open_game { + buf = "You can't rename the current game."; +} + +RESOURCE TBUF32 r_alert_rename_target_exists { + buf = "Name already in use."; +} + +RESOURCE TBUF32 r_alert_rename_target_badname { + buf = "Illegal game name"; +} + + +#define XW_EFLAGS EEikEdwinNoAutoSelection | EEikEdwinReadOnly | EEikEdwinResizable +// EEikEdwinDisplayOnly | + +RESOURCE DIALOG r_xwords_confirmation_query +{ + title = "Query"; +#if ! defined SERIES_60 + buttons = R_EIK_BUTTONS_NO_YES; +#endif + flags = EEikDialogFlagWait | EEikDialogFlagNotifyEsc; + items = + { + DLG_LINE + { + control = EDWIN { flags = XW_EFLAGS; + width = 50; + lines = 4; + }; + type = EEikCtEdwin; + id = EAskContents; + } + }; +} + +RESOURCE DIALOG r_xwords_info_only +{ + title = "Info"; +#if ! defined SERIES_60 + buttons = R_EIK_BUTTONS_CONTINUE; +#endif + flags = EEikDialogFlagWait | EEikDialogFlagNotifyEsc; + items = + { + DLG_LINE + { + control = EDWIN { flags = XW_EFLAGS; + width = 50; + lines = 4; + }; + type = EEikCtEdwin; + id = EAskContents; + } + }; +} + +#ifndef XWFEATURE_STANDALONE_ONLY +RESOURCE ARRAY r_conn_roles +{ + items = { + LBUF { txt = "Standalone"; } + ,LBUF { txt = "Host"; } + ,LBUF { txt = "Guest"; } + }; +} + +RESOURCE ARRAY r_conn_types +{ + items = { + LBUF { txt = "Cellular internet"; } + ,LBUF { txt = "Bluetooth"; } + ,LBUF { txt = "IR"; } + }; +} + +RESOURCE ARRAY r_xwords_newgame_page_conn +{ + items = + { + DLG_LINE + { + prompt = "Role"; + type = EEikCtChoiceList; +#if defined SERIES_80 + control = CHOICELIST { array_id = r_conn_roles; }; +#endif + id = EConnectionRole; + // itemflags = + }, + DLG_LINE + { + prompt = "Connect via"; + type = EEikCtChoiceList; +#if defined SERIES_80 + control = CHOICELIST { array_id = r_conn_types; }; +#elif defined SERIES_60 + control = AVKON_LIST_QUERY_CONTROL { + listtype = EAknCtSingleHeadingPopupMenuListBox; + listbox = LISTBOX { + flags = EAknListBoxMenuList; + height = 5; + width = 20; + array_id = r_conn_types; + }; +/* heading = qtn_aknexquery_list_title; */ + }; +#endif + id = EConnectionType; + // itemflags = + }, + DLG_LINE { + prompt = "Game name"; + type = EEikCtEdwin; + id = ECookie; + control = EDWIN { width = 10; maxlength = MAX_COOKIE_LEN; }; + }, + DLG_LINE { + prompt = "Relay name"; + type = EEikCtEdwin; + id = ERelayName; + control = EDWIN { width = 10; maxlength = 32; }; + }, + DLG_LINE { + prompt = "Port number"; + id = ERelayPort; + type = EEikCtNumberEditor; + control = NUMBER_EDITOR { min = 0; max = 0xFFFF; }; + } + }; +} +#endif + +RESOURCE ARRAY r_four_nums +{ + items = { + LBUF { txt = "1"; } + ,LBUF { txt = "2"; } + ,LBUF { txt = "3"; } + ,LBUF { txt = "4"; } + }; +} + +RESOURCE ARRAY r_xwords_newgame_page_players +{ + items = + { + DLG_LINE { + + prompt = "Number of players"; + type = EEikCtChoiceList; +#if defined SERIES_80 + control = CHOICELIST { array_id = r_four_nums; }; +#endif + id = ENPlayersList; + + }, DLG_LINE { + + prompt = "Show player"; + type = EEikCtChoiceList; +#if defined SERIES_80 + control = CHOICELIST { maxdisplaychar = 2; }; +#endif + id = ENPlayersWhichList; + + }, DLG_LINE { + + prompt = "Location"; + type = EEikCtChoiceList; +#if defined SERIES_80 + control = CHOICELIST { array_id = r_location_choices; }; +#endif + id = EPlayerLocationChoice; + + } ,DLG_LINE { + + prompt = "Name"; + control = EDWIN { width = 20; maxlength = 32; }; + type = EEikCtEdwin; + id = EPlayerName; + + } ,DLG_LINE { + + prompt = "Species"; + type = EEikCtChoiceList; +#if defined SERIES_80 + control = CHOICELIST { array_id = r_player_species; }; +#endif + id = EPlayerSpeciesChoice; + + } ,DLG_LINE { + + type = EEikCtSecretEd; + prompt = "Password"; + id = EDecryptPassword; + control = SECRETED { num_letters = 5; }; + } + }; +} + +RESOURCE ARRAY r_player_species +{ + items = { + LBUF { txt = "Human"; } + ,LBUF { txt = "Robot"; } + }; +} + +RESOURCE ARRAY r_location_choices +{ + items = { + LBUF { txt = "Local"; } + ,LBUF { txt = "Remote"; } + }; +} + +/* This flag goes on editors in pages on series 60 */ +/* avkon_flags = EAknEditorFlagNoEditIndicators; \ */ + +#ifdef SERIES_60 +RESOURCE DLG_BUTTONS r_dict_browse_button +{ + buttons = { + DLG_BUTTON + { + id = EDictBrowseButton; + button = CMBUT { txt = "Browse"; }; +/* hotkey = "B"; */ + } + }; +} +#endif + +RESOURCE ARRAY r_xwords_newgame_page_dict +{ + items = { + DLG_LINE { + prompt = "Game dictionary"; + type = EEikCtChoiceList; +#if defined SERIES_80 + control = CHOICELIST { flags = EEikChlistArrayOwnedExternally; }; +#endif + id = ESelDictChoice; + } +#ifdef SERIES_60 +/* ,DLG_LINE { */ +/* buttons = r_dict_browse_button; */ +/* } */ +#endif + }; +} + +RESOURCE ARRAY r_xwords_newgame_pages +{ + items = + { +#ifndef XWFEATURE_STANDALONE_ONLY + PAGE{ text="Connection"; id=EPageConn; lines=r_xwords_newgame_page_conn;} + , +#endif + PAGE{ text="Players"; id=EPagePlayers; + lines=r_xwords_newgame_page_players;} + ,PAGE{ text="Dictionary"; id=EPageDict; + lines=r_xwords_newgame_page_dict;} + }; +} + +RESOURCE DIALOG r_xwords_newgame_dlg +{ + title = "Game setup"; + /* This doesn't work in series 60. How do I differentiate? Sep .rss + files? */ +#if defined SERIES_80 + buttons = R_EIK_BUTTONS_CANCEL_OK; +#endif + flags = EEikDialogFlagWait | EEikDialogFlagNotifyEsc; + pages = r_xwords_newgame_pages; +} + +RESOURCE DIALOG r_xwords_blank_picker +{ + title = "Tile picker"; +#if defined SERIES_80 + buttons = R_EIK_BUTTONS_CONTINUE; +#endif + flags = EEikDialogFlagWait | EEikDialogFlagNotifyEsc; + items = { + DLG_LINE { + prompt = "Choose one"; + type = EEikCtChoiceList; +#if defined SERIES_80 + control = CHOICELIST { flags = EEikChlistArrayOwnedExternally; }; +#elif defined SERIES_60 +#endif + id = ESelBlankChoice; + } + }; +} + +RESOURCE DLG_BUTTONS r_savedgames_buttons +{ + buttons = { + DLG_BUTTON { + id = XW_SGAMES_RENAME_COMMAND; + buttontype = EEikCtCommandButton; + button = CMBUT { txt = "Rename"; }; + }, DLG_BUTTON { + id = XW_SGAMES_OPEN_COMMAND; + buttontype = EEikCtCommandButton; +/* flags = EEikLabeledButtonIsDefault; */ + button = CMBUT { txt = "Open"; }; + }, DLG_BUTTON { + id = XW_SGAMES_DELETE_COMMAND; + buttontype = EEikCtCommandButton; + button = CMBUT { txt = "Delete"; }; + }, DLG_BUTTON { + id = EEikBidCancel; + buttontype = EEikCtCommandButton; + button = CMBUT { txt = "Cancel"; }; + } + }; +} + +RESOURCE DIALOG r_xwords_savedgames_dlg +{ + title = "Saved games"; + buttons = r_savedgames_buttons; + flags = EEikDialogFlagWait | EEikDialogFlagNotifyEsc; + items = { + DLG_LINE { + prompt = "Open:"; + id = ESelGameChoice; +#if 0 + type = EEikCtChoiceList; + control = CHOICELIST { flags = EEikChlistArrayOwnedExternally; }; +#else + type = EEikCtListBox; + control = LISTBOX { flags = EEikListBoxMultipleSelection; + width = 20; + }; +#endif + } + }; +} + +RESOURCE DIALOG r_xwords_editname_dlg +{ + title = "Change name"; +#if defined SERIES_80 + buttons = R_EIK_BUTTONS_CANCEL_OK; +#endif + flags = EEikDialogFlagWait | EEikDialogFlagNotifyEsc; + items = { + DLG_LINE { + prompt = "New name"; + id = EEditNameEdwin; + type = EEikCtFileNameEd; + control = FILENAMEEDITOR { width = 32; }; + } + }; +} diff --git a/xwords4/symbian/inc/LocalizedStrIncludes.h b/xwords4/symbian/inc/LocalizedStrIncludes.h new file mode 100644 index 000000000..5a2a8291b --- /dev/null +++ b/xwords4/symbian/inc/LocalizedStrIncludes.h @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* This is the symbian version of what's always been a palm file. + * There's probably a better way of doing this, but this is it for + * now. + */ + +#ifndef _LOCALIZEDSTRINCLUDES_H_ +#define _LOCALIZEDSTRINCLUDES_H_ + +enum { + STRD_REMAINING_TILES_ADD, + STRD_UNUSED_TILES_SUB, + STR_COMMIT_CONFIRM, + STR_BONUS_ALL, + STRD_TURN_SCORE, + STR_LOCAL_NAME, + STR_NONLOCAL_NAME, + STRD_TIME_PENALTY_SUB, + + STRD_CUMULATIVE_SCORE, + STRS_TRAY_AT_START, + STRS_MOVE_DOWN, + STRS_MOVE_ACROSS, + STRS_NEW_TILES, + STRSS_TRADED_FOR, + STR_PASS, + STR_PHONY_REJECTED, + + STRD_ROBOT_TRADED, + STR_ROBOT_MOVED, + STR_REMOTE_MOVED, + + STR_PASSED, + STRSD_SUMMARYSCORED, + STRD_TRADED, + STR_LOSTTURN, + + STRS_VALUES_HEADER, + + STR_LOCALPLAYERS, + STR_TOTALPLAYERS, + + STR_NOT_USED +}; + +#endif diff --git a/xwords4/symbian/inc/symaskdlg.h b/xwords4/symbian/inc/symaskdlg.h new file mode 100644 index 000000000..eb1150f60 --- /dev/null +++ b/xwords4/symbian/inc/symaskdlg.h @@ -0,0 +1,58 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4;-*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _XWASKDLG_H_ +#define _XWASKDLG_H_ + +extern "C" { +#include "comtypes.h" +#include "xwstream.h" +#include "mempool.h" +} + +#include +#include + +class CXWAskDlg : public CEikDialog +{ + public: + static TBool DoAskDlg( MPFORMAL XWStreamCtxt* aStream, TBool aKillStream ); + static TBool DoAskDlg( MPFORMAL TBuf16<128>* aMessage ); + + static void DoInfoDlg( MPFORMAL XWStreamCtxt* aStream, TBool aKillStream ); + + ~CXWAskDlg(); + + private: + CXWAskDlg( MPFORMAL XWStreamCtxt* aStream, TBool aKillStream ); + CXWAskDlg( MPFORMAL TBuf16<128>* aMessage); + + private: + TBool OkToExitL( TInt aKeyCode ); + void PreLayoutDynInitL(); + + /* One or the other of these will be set/used */ + XWStreamCtxt* iStream; + TBuf16<128>* iMessage; + + TBool iKillStream; + MPSLOT +}; + +#endif diff --git a/xwords4/symbian/inc/symblnk.h b/xwords4/symbian/inc/symblnk.h new file mode 100644 index 000000000..ea4963d62 --- /dev/null +++ b/xwords4/symbian/inc/symblnk.h @@ -0,0 +1,50 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). (based on sample + * app helloworldbasic "Copyright (c) 2002, Nokia. All rights + * reserved.") + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _SYMBLNK_H_ +#define _SYMBLNK_H_ + +extern "C" { +#include "comtypes.h" +} + +#include +#include + +class CXWBlankSelDlg : public CEikDialog +{ + public: + static void UsePickTileDialogL( const XP_UCHAR4* texts, TInt aNTiles, + TInt* result ); + + CXWBlankSelDlg( const XP_UCHAR4* texts, TInt aNTiles, TInt* aResultP ); + /* no need for destructor; we give the control ownership of iFacesList */ + + void PreLayoutDynInitL(); + TBool OkToExitL( TInt aKeyCode ); + + private: + const XP_UCHAR4* iTexts; + TInt iNTiles; + TInt* iResultP; + CDesC16ArrayFlat* iFacesList; +}; +#endif diff --git a/xwords4/symbian/inc/symdict.h b/xwords4/symbian/inc/symdict.h new file mode 100644 index 000000000..8696d8850 --- /dev/null +++ b/xwords4/symbian/inc/symdict.h @@ -0,0 +1,37 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4;-*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _SYMDICT_H_ +#define _SYMDICT_H_ + +#if defined SERIES_60 +#elif defined SERIES_80 +# include +#endif + +extern "C" { +#include "comtypes.h" +#include "mempool.h" +} + +DictionaryCtxt* sym_dictionary_makeL( MPFORMAL TFileName* path, + const XP_UCHAR* aDictName ); + + +#endif diff --git a/xwords4/symbian/inc/symdraw.h b/xwords4/symbian/inc/symdraw.h new file mode 100644 index 000000000..ce0088550 --- /dev/null +++ b/xwords4/symbian/inc/symdraw.h @@ -0,0 +1,47 @@ +// -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _SYMDRAW_H_ +#define _SYMDRAW_H_ + +extern "C" { + +#include "draw.h" +#include "board.h" + +} /* extern "C" */ + +#if defined SERIES_60 +# include +# include +#elif defined SERIES_80 +# include +#endif + +#define scaleBoardV 13 +#define scaleBoardH 15 +#define scaleTrayV 37 +#define scaleTrayH 32 + +#define CUR_PREFS_VERS 0x0405 + +DrawCtx* sym_drawctxt_make( MPFORMAL CWindowGc* gc, CCoeEnv* aCoeEnv, + CEikonEnv* aEikonEnv, CEikApplication* aApp ); + +#endif diff --git a/xwords4/symbian/inc/symgamdl.h b/xwords4/symbian/inc/symgamdl.h new file mode 100644 index 000000000..caf4ff0a7 --- /dev/null +++ b/xwords4/symbian/inc/symgamdl.h @@ -0,0 +1,95 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4;-*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _SYMGAMDL_H_ +#define _SYMGAMDL_H_ + +extern "C" { +#include "comtypes.h" +#include "xwstream.h" +#include "mempool.h" +#include "game.h" +#include "comms.h" +} + +#include +#include + +class TGameInfoBuf +{ + public: + TGameInfoBuf::TGameInfoBuf( const CurGameInfo* aGi, +#ifndef XWFEATURE_STANDALONE_ONLY + const CommsAddrRec* aCommsAddr, +#endif + CDesC16ArrayFlat* aDictList ); + CDesC16ArrayFlat* GetDictList() { return iDictList; } + void CopyToL( MPFORMAL CurGameInfo* aGi +#ifndef XWFEATURE_STANDALONE_ONLY + , CommsAddrRec* aCommsAddr +#endif + ); + + TBool iIsRobot[MAX_NUM_PLAYERS]; + TBool iIsLocal[MAX_NUM_PLAYERS]; + + TBuf16<32> iPlayerNames[MAX_NUM_PLAYERS]; + + CDesC16ArrayFlat* iDictList; /* owned externally! */ + TInt iDictIndex; + TInt iNPlayers; + +#ifndef XWFEATURE_STANDALONE_ONLY + Connectedness iServerRole; + CommsAddrRec iCommsAddr; +#endif +}; + +class CXWGameInfoDlg : public CEikDialog /* CEikForm instead? */ +{ + public: + static TBool DoGameInfoDlgL( MPFORMAL TGameInfoBuf* aGib, + TBool aNewGame ); + + ~CXWGameInfoDlg(); + + private: + CXWGameInfoDlg( MPFORMAL TGameInfoBuf* aGib, TBool aNewGame ); + + private: + void PreLayoutDynInitL(); + void HandleControlStateChangeL( TInt aControlId ); + TBool OkToExitL( TInt aKeyCode ); + + void LoadPlayerInfo( TInt aWhich ); + void SavePlayerInfo( TInt aWhich ); + void SetPlayerShown( TInt aPlayer ); + void HideAndShow(); + + CDesC16ArrayFlat* MakeNumListL( TInt aFirst, TInt aLast ); + + TBool iIsNewGame; + TGameInfoBuf* iGib; + + TInt iCurPlayerShown; + + MPSLOT +}; + +#endif diff --git a/xwords4/symbian/inc/symgamed.h b/xwords4/symbian/inc/symgamed.h new file mode 100755 index 000000000..b74adf9a0 --- /dev/null +++ b/xwords4/symbian/inc/symgamed.h @@ -0,0 +1,48 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4;-*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _SYMGAMED_H_ +#define _SYMGAMED_H_ + +/* extern "C" { */ +/* #include "comtypes.h" */ +/* #include "mempool.h" */ +/* #include "util.h" */ +/* } */ + +#include +#include + +#include "symgmmgr.h" + +class CNameEditDlg : public CEikDialog +{ + public: + static TBool EditName( TGameName* aGameName ); + + private: + CNameEditDlg( TGameName* aGameName ); + + TBool OkToExitL( TInt aKeyCode ); + void PreLayoutDynInitL(); + + TGameName* iGameName; +}; + +#endif diff --git a/xwords4/symbian/inc/symgmdlg.h b/xwords4/symbian/inc/symgmdlg.h new file mode 100755 index 000000000..977303676 --- /dev/null +++ b/xwords4/symbian/inc/symgmdlg.h @@ -0,0 +1,65 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4;-*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _SYMGMDLG_H_ +#define _SYMGMDLG_H_ + +extern "C" { +#include "comtypes.h" +#include "mempool.h" +#include "util.h" +} + +#include +#include + +#include "symgmmgr.h" + +class CXWordsAppView; + +enum { + SYM_QUERY_CONFIRM_DELGAME = QUERY_LAST_COMMON +}; + +class CXSavedGamesDlg : public CEikDialog +{ + public: + static TBool DoGamesPicker( MPFORMAL CXWordsAppView* aOwner, + CXWGamesMgr* aGameMgr, + const TGameName* aCurName, TGameName* result ); + + private: + CXSavedGamesDlg( MPFORMAL CXWordsAppView* aOwner, CXWGamesMgr* aGameMgr, + const TGameName* aCurName, TGameName* result ); + + TBool OkToExitL( TInt aKeyCode ); + void PreLayoutDynInitL(); + + void ResetNames( TInt aPrefIndex, const TGameName* aSelName ); + TBool EditSelName( const TGameName* aSelName, TGameName* aNewName ); + + CXWordsAppView* iOwner;/* uses: don't own this!!! */ + CXWGamesMgr* iGameMgr; /* I don't own this */ + const TGameName* iCurName; + TGameName* iResultP; /* ditto */ + MPSLOT +}; + + +#endif diff --git a/xwords4/symbian/inc/symgmmgr.h b/xwords4/symbian/inc/symgmmgr.h new file mode 100755 index 000000000..dbfbd4091 --- /dev/null +++ b/xwords4/symbian/inc/symgmmgr.h @@ -0,0 +1,76 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef _SYMGMMGR_H_ +#define _SYMGMMGR_H_ + +extern "C" { +#include "comtypes.h" +#include "memstream.h" +#include "mempool.h" +} + +#include +#include +#include +#include + +typedef TBuf16<32> TGameName; + +/* This class tracks games, each of which is (currently) saved in a + * file in a hidden directory. + */ +class CXWGamesMgr : public CBase +{ + private: + CXWGamesMgr( MPFORMAL CCoeEnv* aCoeEnv, TFileName* aBasePath ); + + void BuildListL(); + TBool DeleteFileFor( TPtrC16* aName ); + void GameNameToPath( TFileName* path, const TDesC16* name ); + + public: + static CXWGamesMgr* NewL( MPFORMAL CCoeEnv* aCoeEnv, TFileName* aBasePath ); + + TInt GetNGames() const; + /* Will be used by dialog */ + CDesC16Array* GetNames() const; + + /* Come up with some unique name */ + void MakeDefaultName( TGameName* aName ); + + void StoreGameL( const TGameName* aName, XWStreamCtxt* stream ); + void LoadGameL( const TGameName* aName, XWStreamCtxt* stream ); + + TBool DeleteSelected( TInt aIndex ); + TBool Exists( TGameName* aName ); + TBool IsLegalName( const TGameName* aName ); + void Rename( const TDesC16* aCurName, const TDesC16* aNewName ); + + private: + + CDesC16ArrayFlat* iNamesList; + TFileName iDir; + CCoeEnv* iCoeEnv; + TInt iGameCount; + MPSLOT +}; + +#endif diff --git a/xwords4/symbian/inc/symrsock.h b/xwords4/symbian/inc/symrsock.h new file mode 100644 index 000000000..2b0da3b2b --- /dev/null +++ b/xwords4/symbian/inc/symrsock.h @@ -0,0 +1,75 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). (based on sample + * app helloworldbasic "Copyright (c) 2002, Nokia. All rights + * reserved.") + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef XWFEATURE_STANDALONE_ONLY + +#ifndef _SYMRSOCK_H_ +#define _SYMRSOCK_H_ + +#include +#include +#include "comms.h" + +const TInt KMaxRcvMsgLen = 512; + +class CReadSocket : public CActive { + + public: + typedef void (*ReadNotifyCallback)( const TDesC8* aBuf, + void *closure ); + + static CReadSocket* NewL( ReadNotifyCallback aGotData, + void* aCallbackClosure ); + ~CReadSocket(); + + void SetListenPort( TInt aPort ); + void Start(); + void Stop(); + + protected: + void RunL(); /* from CActive */ + void DoCancel(); /* from CActive */ + + private: + CReadSocket( ReadNotifyCallback aGotData, void* aCallbackClosure ); + void ConstructL(); + void RequestRecv(); + void Bind(); + + enum TReadState { + ENotReading + ,EReading + }; + + TReadState iReadState; + ReadNotifyCallback iGotData; + void* iCallbackClosure; + RSocket iListenSocket; + TInt iPort; + TBuf8 iInBuf; + TSockXfrLength iReadLength; + RSocketServ iSocketServer; + TInetAddr iRecvAddr; +}; + +#endif + +#endif /* XWFEATURE_STANDALONE_ONLY */ diff --git a/xwords4/symbian/inc/symssock.h b/xwords4/symbian/inc/symssock.h new file mode 100644 index 000000000..579a4ebfe --- /dev/null +++ b/xwords4/symbian/inc/symssock.h @@ -0,0 +1,92 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). (based on sample + * app helloworldbasic "Copyright (c) 2002, Nokia. All rights + * reserved.") + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _SYMSSOCK_H_ +#define _SYMSSOCK_H_ + +#ifndef XWFEATURE_STANDALONE_ONLY + +#include +#include +#include "comms.h" + +const TInt KMaxMsgLen = 512; + +class CSendSocket : public CActive { + + public: + + typedef void (*ReadNotifyCallback)( const TDesC8* aBuf, + void *aClosure ); + static CSendSocket* NewL( ReadNotifyCallback aCallback, void* aClosure ); + + ~CSendSocket(); + + TBool SendL( const XP_U8* aBuf, XP_U16 aLen, const CommsAddrRec* aAddr ); + TBool Listen(); + + void ConnectL( const CommsAddrRec* aAddr ); + void Disconnect(); + + protected: + void RunL(); /* from CActive */ + void DoCancel(); /* from CActive */ + + private: + CSendSocket( ReadNotifyCallback aCallback, void* aClosure ); + void ConstructL(); + void DoActualSend(); + + void ConnectL(); + void ConnectL( TUint32 aIpAddr ); + + TBool CancelListen(); + void ResetState(); + + enum TSSockState { ENotConnected + ,ELookingUp + ,EConnecting + ,EConnected + ,ESending + ,EListening + }; + + TSSockState iSSockState; + RSocket iSendSocket; + RSocketServ iSocketServer; + RHostResolver iResolver; + TInetAddr iAddress; + TBuf8 iSendBuf; + TInt iDataLen; /* How big should next packet be */ + TUint8 iInBuf[KMaxMsgLen]; + TPtr8* iInBufDesc; /* points to above buffer */ + CommsAddrRec iCurAddr; + TNameEntry iNameEntry; + TNameRecord iNameRecord; + TBool iAddrSet; + TBool iListenPending; + + ReadNotifyCallback iCallback; + void* iClosure; +}; + +#endif +#endif diff --git a/xwords4/symbian/inc/symutil.h b/xwords4/symbian/inc/symutil.h new file mode 100644 index 000000000..921a6adc4 --- /dev/null +++ b/xwords4/symbian/inc/symutil.h @@ -0,0 +1,36 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _SYMUTIL_H_ +#define _SYMUTIL_H_ + +/* Functions in this file can use C++ types etc since they're not + * extern "C" + */ + +void symReplaceStrIfDiff( MPFORMAL XP_UCHAR** loc, const TDesC16& desc ); +void symReplaceStrIfDiff( MPFORMAL XP_UCHAR** loc, const XP_UCHAR* str ); + +#ifdef DEBUG +void XP_LOGDESC16( const TDesC16* desc ); +#else +# define XP_LOGDESC16(d) +#endif + +#endif diff --git a/xwords4/symbian/inc/xptypes.h b/xwords4/symbian/inc/xptypes.h new file mode 100644 index 000000000..cab6c10d1 --- /dev/null +++ b/xwords4/symbian/inc/xptypes.h @@ -0,0 +1,137 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1999-2004 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _XPTYPES_H_ +#define _XPTYPES_H_ + +#include +#include + +/* #include */ + +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ + +/* #include */ + +#ifdef CPLUS +extern "C" { +#endif + +#define XP_TRUE ((XP_Bool)(1==1)) +#define XP_FALSE ((XP_Bool)(1==0)) + +typedef TUint16 XP_U16; +typedef TInt16 XP_S16; + +typedef TUint32 XP_U32; +typedef TInt32 XP_S32; + +typedef TUint8 XP_U8; +typedef TInt8 XP_S8; +typedef unsigned char XP_UCHAR; +//typedef TText XP_UCHAR; /* native two-byte char */ + +typedef signed short XP_FontCode; /* not sure how I'm using this yet */ +typedef TBool XP_Bool; +typedef XP_U32 XP_Time; + +#define SC(t,val) static_cast(val) + +#define XP_CR "\n" + +#ifndef DEBUG +void p_ignore( char* fmt, ... ); +#endif + +void sym_debugf( char* aFmt, ...); +int sym_snprintf( XP_UCHAR* buf, XP_U16 len, const XP_UCHAR* format, ... ); + +XP_U32 sym_flip_long( unsigned long l ); +XP_U16 sym_flip_short(unsigned short s); +void* sym_malloc(XP_U32 nbytes ); +void* sym_realloc(void* p, XP_U32 nbytes); +void sym_free( void* p ); +void sym_assert(XP_Bool b, XP_U32 line, const char* file ); +void sym_memset( void* dest, XP_UCHAR val, XP_U32 nBytes ); +XP_S16 sym_strcmp( XP_UCHAR* str1, XP_UCHAR* str2 ); +XP_U32 sym_strlen( XP_UCHAR* str ); +XP_S16 sym_strncmp( XP_UCHAR* str1, XP_UCHAR* str2, XP_U32 len ); +void sym_memcpy( void* dest, const void* src, XP_U32 nbytes ); +XP_S16 sym_memcmp( void* m1, void* m2, XP_U32 nbytes ); +char* sym_strcat( XP_UCHAR* dest, const XP_UCHAR* src ); + +#define XP_RANDOM() rand() + +#ifdef MEM_DEBUG +# define XP_PLATMALLOC(nbytes) sym_malloc(nbytes) +# define XP_PLATREALLOC(p,s) sym_realloc((p), (s)) +# define XP_PLATFREE(p) sym_free(p) +#else +# define XP_MALLOC(pool, nbytes) sym_malloc(nbytes) +# define XP_REALLOC(pool, p, bytes) sym_realloc((p), (bytes)) +# define XP_FREE(pool, p) sym_free(p) +#endif + +#define XP_MEMSET(src, val, nbytes) \ + sym_memset( (src), (val), (nbytes) ) +#define XP_MEMCPY(d,s,l) sym_memcpy((d),(s),(l)) +#define XP_MEMCMP( a1, a2, l ) sym_memcmp( (a1),(a2),(l)) +#define XP_STRLEN(s) sym_strlen((unsigned char*)(s)) +#define XP_STRCMP(s1,s2) sym_strcmp((XP_UCHAR*)(s1),(XP_UCHAR*)(s2)) +#define XP_STRNCMP(s1,s2,l) sym_strncmp((char*)(s1),(char*)(s2),(l)) +#define XP_STRCAT(d,s) sym_strcat((d),(s)) + +#define XP_SNPRINTF sym_snprintf + +#define XP_MIN(a,b) ((a)<(b)?(a):(b)) +#define XP_MAX(a,b) ((a)>(b)?(a):(b)) + +#ifdef DEBUG +# define XP_ASSERT(b) sym_assert((XP_Bool)(b), __LINE__, __FILE__ ) +#else +# define XP_ASSERT(b) +#endif + +#define XP_STATUSF XP_DEBUGF +#define XP_WARNF XP_DEBUGF + +#ifdef DEBUG +# define XP_LOGF sym_debugf +# define XP_DEBUGF sym_debugf +#else +# define XP_LOGF if(0)p_ignore +# define XP_DEBUGF if(0)p_ignore +#endif + +#define XP_NTOHL(l) sym_flip_long(l) +#define XP_NTOHS(s) sym_flip_short(s) +#define XP_HTONL(l) sym_flip_long(l) +#define XP_HTONS(s) sym_flip_short(s) + +#define XP_LD "%d" + +#ifdef CPLUS +} +#endif + +#endif diff --git a/xwords4/symbian/inc/xwapp.h b/xwords4/symbian/inc/xwapp.h new file mode 100644 index 000000000..c37627b06 --- /dev/null +++ b/xwords4/symbian/inc/xwapp.h @@ -0,0 +1,46 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). (based on sample + * app helloworldbasic "Copyright (c) 2002, Nokia. All rights + * reserved.") + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __XWAPP_H__ +#define __XWAPP_H__ + +#include + +/*! + @class CXWordsApplication +*/ +class CXWordsApplication : public CEikApplication +{ + public: // from CEikApplication + + /*! + @function AppDllUid + */ + TUid AppDllUid() const; + + protected: // from CEikApplication + /*! + @function CreateDocumentL + */ + CApaDocument* CreateDocumentL(); +}; + +#endif // __XWAPP_H__ diff --git a/xwords4/symbian/inc/xwappui.h b/xwords4/symbian/inc/xwappui.h new file mode 100644 index 000000000..00ece83ee --- /dev/null +++ b/xwords4/symbian/inc/xwappui.h @@ -0,0 +1,72 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). (based on sample + * app helloworldbasic "Copyright (c) 2002, Nokia. All rights + * reserved.") + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _XWAPPUI_H_ +#define _XWAPPUI_H_ + +#include + +// Forward reference +class CXWordsAppView; + +/*! + @class CXWordsAppUi + + @discussion An instance of class CXWordsAppUi is the UserInterface + part of the Eikon application framework for XWords. + */ +class CXWordsAppUi : public CEikAppUi +{ + public: + /*! + @function ConstructL + */ + void ConstructL(); + + /*! + @function CXWordsAppUi + */ + CXWordsAppUi(); + + + /*! + @function ~CXWordsAppUi + + @discussion Destroy the object and release all memory objects + */ + ~CXWordsAppUi(); + + + public: // from CEikAppUi + /*! + @function HandleCommandL + */ + void HandleCommandL( TInt aCommand ); + TKeyResponse HandleKeyEventL( const TKeyEvent& aKeyEvent, + TEventCode aType ); + + private: + /*! @var iAppView The application view */ + CXWordsAppView* iAppView; +}; + + +#endif // _XWAPPUI_H_ diff --git a/xwords4/symbian/inc/xwappview.h b/xwords4/symbian/inc/xwappview.h new file mode 100644 index 000000000..8c64e8d82 --- /dev/null +++ b/xwords4/symbian/inc/xwappview.h @@ -0,0 +1,232 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). (based on sample + * app helloworldbasic "Copyright (c) 2002, Nokia. All rights + * reserved.") + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _XWORDSAPPVIEW_H_ +#define _XWORDSAPPVIEW_H_ + + +#include + +#include "game.h" +#include "comms.h" +#include "memstream.h" +#include "symdraw.h" +#include "symgmmgr.h" +#include "symssock.h" +#include "symrsock.h" +#include "xwrelay.h" + +typedef enum { + EGamesLoc + ,EDictsLoc + ,EPrefsLoc +} TDriveReason; + +/*! + @class CXWordsAppView + + @discussion This is the main view for Crosswords. Owns all the + common-code-created objects, passes events to them, etc. Should be + the only sample code file modified to any significant degree. + */ +//class CXWordsAppView : public CEikBorderedControl // ( which : CCoeControl ) +class CXWordsAppView : public CCoeControl +{ + public: + + /*! + @function NewL + + @discussion Create a CXWordsAppView object, which will draw itself to aRect + @param aRect the rectangle this view will be drawn to + @result a pointer to the created instance of CXWordsAppView + */ + static CXWordsAppView* NewL(const TRect& aRect, CEikApplication* aApp ); + + /*! + @function NewLC + + @discussion Create a CXWordsAppView object, which will draw itself to aRect + @param aRect the rectangle this view will be drawn to + @result a pointer to the created instance of CXWordsAppView + */ + static CXWordsAppView* NewLC(const TRect& aRect, CEikApplication* aApp ); + + + /*! + @function ~CXWordsAppView + + @discussion Destroy the object and release all memory objects + */ + ~CXWordsAppView(); + + + public: // from CEikBorderedControl + /*! + @function Draw + + @discussion Draw this CXWordsAppView to the screen + @param aRect the rectangle of this view that needs updating + */ + void Draw(const TRect& aRect) const; + + + private: + /*! + @function ConstructL + + @discussion Perform the second phase construction of a CXWordsAppView object + @param aRect the rectangle this view will be drawn to + */ + void ConstructL(const TRect& aRect); + + /*! + @function CXWordsAppView + + @discussion Perform the first phase of two phase construction + */ + CXWordsAppView( CEikApplication* aApp ); + + /* Added by eeh */ + public: + int HandleCommand( TInt aCommand ); + void Exiting(); + TBool HandleKeyEvent( const TKeyEvent& aKeyEvent ); + void UserErrorFromID( TInt aResource ); + XP_Bool UserQuery( UtilQueryID aId, XWStreamCtxt* aStream ); + + private: + typedef enum { + EUtilRequest + , EProcessPacket + , ENumReasons + } XWTimerReason_symb ; + + /* open game from prefs or start a new one. */ + void MakeOrLoadGameL(); + void DeleteGame(); + void SetUpUtil(); + void PositionBoard(); + void DisplayFinalScoresL(); + XWStreamCtxt* MakeSimpleStream( MemStreamCloseCallback cb, + XP_U16 channelNo = CHANNEL_NONE ); + TBool AskFromResId( TInt aResource ); + TBool FindAllDicts(); + TBool LoadPrefs(); + TBool AskSaveGame() { return ETrue; } + void SaveCurrentGame() {} + void NotImpl(); + void GetXwordsRWDir( TFileName* aPathRef, TDriveReason aWhy ); + void InitPrefs(); + void WritePrefs(); + void SaveCurGame(); + + void LoadOneGameL( TGameName* aGameName ); + void StoreOneGameL( TGameName* aGameName ); + TBool DoSavedGames(); + TBool DoNewGame(); + void DoImmediateDraw(); + void DrawGameName() const; + void StartIdleTimer( XWTimerReason_symb aWhy ); + + static void sym_util_requestTime( XW_UtilCtxt* uc ); + static VTableMgr* sym_util_getVTManager( XW_UtilCtxt* uc ); + static XP_U32 sym_util_getCurSeconds( XW_UtilCtxt* uc ); + static void sym_util_notifyGameOverL( XW_UtilCtxt* uc ); + static void sym_util_userError( XW_UtilCtxt* uc, UtilErrID id ); + static DictionaryCtxt* sym_util_makeEmptyDict( XW_UtilCtxt* uc ); + static XWStreamCtxt* sym_util_makeStreamFromAddr( XW_UtilCtxt* uc, + XP_U16 channelNo ); + + static void sym_util_setTimer( XW_UtilCtxt* uc, + XWTimerReason why, XP_U16 when, + TimerProc proc, void* closure ); +#ifdef BEYOND_IR + static void sym_util_listenPortChange( XW_UtilCtxt* uc, + XP_U16 listenPort ); + static void sym_util_addrChange( XW_UtilCtxt* uc, + const CommsAddrRec* aOld, + const CommsAddrRec* aNew ); +#endif + +#ifdef XWFEATURE_STANDALONE_ONLY +# define SYM_SEND (TransportSend)NULL +#else +# define SYM_SEND sym_send + + static XP_S16 sym_send( XP_U8* buf, XP_U16 len, + const CommsAddrRec* addr, void* closure ); + static void sym_send_on_close( XWStreamCtxt* stream, + void* closure ); + static void PacketReceived( const TDesC8* aBuf, void* aClosure ); + +#endif + + static TInt TimerCallback( TAny* aThis ); + static TInt HeartbeatTimerCallback( TAny* closure ); + + void SetHeartbeatCB( TimerProc aHBP, void* aHBC) { + iHeartbeatProc =aHBP; iHeartbeatClosure = aHBC; + } + void GetHeartbeatCB( TimerProc* aHBP, void** aHBC) { + *aHBP = iHeartbeatProc; *aHBC = iHeartbeatClosure; + } + + CEikApplication* iApp; /* remove if there's some way to get from + env */ + CurGameInfo iGi; + CommonPrefs iCp; +#ifndef XWFEATURE_STANDALONE_ONLY + CommsAddrRec iCommsAddr; /* for default settings */ +#endif + XW_UtilCtxt iUtil; + XWGame iGame; + DrawCtx* iDraw; + TGameName iCurGameName; + TRect iTitleBox; + + VTableMgr* iVtMgr; + TTime iStartTime; + TInt iTimerRunCount; + CIdle* iRequestTimer; + TInt iTimerReasons[ENumReasons]; + + CXWGamesMgr* iGamesMgr; + + CDesC16ArrayFlat* iDictList; /* to pass into the dialog */ + + CDeltaTimer* iDeltaTimer; + + TimerProc iHeartbeatProc; + void* iHeartbeatClosure; + TCallBack iHeartbeatCB; + TDeltaTimerEntry iHeartbeatDTE; + XP_Bool iHBQueued; + +#ifndef XWFEATURE_STANDALONE_ONLY + CSendSocket* iSendSock; + CDesC8ArrayFlat* iNewPacketQueue; +#endif + + MPSLOT +}; + +#endif // _XWORDSAPPVIEW_H_ diff --git a/xwords4/symbian/inc/xwdoc.h b/xwords4/symbian/inc/xwdoc.h new file mode 100644 index 000000000..1736232f3 --- /dev/null +++ b/xwords4/symbian/inc/xwdoc.h @@ -0,0 +1,100 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). (based on sample + * app helloworldbasic "Copyright (c) 2002, Nokia. All rights + * reserved.") + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __XWDOC_H__ +#define __XWDOC_H__ + +#include + +// Forward references +class CXWordsAppUi; +class CEikApplication; + +/*! + @class CXWordsDocument + + @discussion An instance of class CXWordsDocument is the Document part of the + Eikon application framework for the XWords example application + */ +class CXWordsDocument : public CEikDocument +{ + public: + + /*! + @function NewL + + @discussion Construct a CXWordsDocument for the Eikon + application aApp using two phase construction, and return a + pointer to the created object @param aApp application creating + this document @result a pointer to the created instance of + CXWordsDocument + */ + static CXWordsDocument* NewL(CEikApplication& aApp); + + /*! + @function NewLC + + @discussion Construct a CXWordsDocument for the Eikon + application aApp using two phase construction, and return a + pointer to the created object @param aApp application creating + this document @result a pointer to the created instance of + CXWordsDocument + */ + static CXWordsDocument* NewLC(CEikApplication& aApp); + + /*! + @function ~CXWordsDocument + + @discussion Destroy the object and release all memory objects + */ + ~CXWordsDocument(); + + public: // from CEikDocument + /*! + @function CreateAppUiL + + @discussion Create a CXWordsAppUi object and return a + pointer to it @result a pointer to the created instance of the + AppUi created + */ + CEikAppUi* CreateAppUiL(); + + private: + + /*! + @function ConstructL + + @discussion Perform the second phase construction of a + CXWordsDocument object + */ + void ConstructL(); + + /*! + @function CXWordsDocument + + @discussion Perform the first phase of two phase construction + @param aApp application creating this document + */ + CXWordsDocument(CEikApplication& aApp); + +}; + +#endif //__XWDOC_H__ diff --git a/xwords4/symbian/inc/xwords.hrh b/xwords4/symbian/inc/xwords.hrh new file mode 100644 index 000000000..e61d81192 --- /dev/null +++ b/xwords4/symbian/inc/xwords.hrh @@ -0,0 +1,96 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). (based on sample + * app helloworldbasic "Copyright (c) 2002, Nokia. All rights + * reserved.") + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __XWORDS_HRH__ +#define __XWORDS_HRH__ + +// HelloWorldBasic enumerate command codes +enum TXWordsIds { + XW_TIMEREQ_COMMAND = 0x6000 // convention is for >= 0x6000 -- they say + ,XW_NEWGAME_COMMAND + ,XW_SAVEDGAMES_COMMAND + ,XW_PREFS_COMMAND + ,XW_ABOUT_COMMAND + + ,XW_VALUES_COMMAND + ,XW_REMAIN_COMMAND + ,XW_CURINFO_COMMAND + ,XW_HISTORY_COMMAND + ,XW_FINALSCORES_COMMAND + + ,XW_HINT_COMMAND + /* #ifdef XWFEATURE_SEARCHLIMIT */ + /* ,XW_LIMHINT_COMMAND */ + /* #endif */ + ,XW_NEXTHINT_COMMAND + ,XW_UNDOCUR_COMMAND + ,XW_UNDOLAST_COMMAND + ,XW_DONE_COMMAND + ,XW_JUGGLE_COMMAND + ,XW_TRADE_COMMAND + ,XW_HIDETRAY_COMMAND + ,XW_FLIP_COMMAND + ,XW_TOGGLEVALS_COMMAND + + ,XW_SGAMES_DELETE_COMMAND + ,XW_SGAMES_RENAME_COMMAND + ,XW_SGAMES_OPEN_COMMAND + + ,EAskContents /* edit control in generic ask dlg */ + + ,EPlayerLocationChoice + ,EPlayerName + ,EPlayerSpeciesChoice + ,EDecryptPassword + +#ifndef XWFEATURE_STANDALONE_ONLY + ,XW_RESEND_COMMAND /* out of place for a menu command... */ + ,EConnectionRole + ,EConnectionType + ,ECookie + ,ERelayName + ,ERelayPort + ,EPageConn +#endif + ,EPagePlayers + ,EPageDict + + ,ESelDictName + ,ESelDictChoice + ,EPlayerSpecies + + ,ENPlayersList + ,ENPlayersWhichList + + ,ESelBlankChoice + ,ESelGameChoice + + ,EEditNameEdwin + +#if defined SERIES_60 + ,EDictBrowseButton +#endif +}; + +#define XW_UID3 0x10206D64 + +#endif // __XWORDS_HRH__ + diff --git a/xwords4/symbian/inc/xwords.pan b/xwords4/symbian/inc/xwords.pan new file mode 100644 index 000000000..1327fd208 --- /dev/null +++ b/xwords4/symbian/inc/xwords.pan @@ -0,0 +1,37 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). (based on sample + * app helloworldbasic "Copyright (c) 2002, Nokia. All rights + * reserved.") + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __XWORDS_PAN__ +#define __XWORDS_PAN__ + +/** XWords application panic codes */ +enum TXwordsPanics { + EXWordsUi = 1 + // add further panics here +}; + +inline void Panic(TXwordsPanics aReason) +{ + _LIT(applicationName,"Crosswords"); + User::Panic(applicationName, aReason); +} + +#endif // __XWORDS_PAN__ diff --git a/xwords4/symbian/src/.cvsignore b/xwords4/symbian/src/.cvsignore new file mode 100644 index 000000000..f0357870c --- /dev/null +++ b/xwords4/symbian/src/.cvsignore @@ -0,0 +1,2 @@ +SYMB_80_WINS +xwords*.UID.cpp diff --git a/xwords4/symbian/src/symaskdlg.cpp b/xwords4/symbian/src/symaskdlg.cpp new file mode 100644 index 000000000..d7511fab3 --- /dev/null +++ b/xwords4/symbian/src/symaskdlg.cpp @@ -0,0 +1,154 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4;-*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include + +#include "symaskdlg.h" +# include "xwords.hrh" +#ifdef SERIES_60 +# include "xwords_60.rsg" +#elif defined SERIES_80 +# include "xwords_80.rsg" +#endif + +CXWAskDlg::CXWAskDlg( MPFORMAL XWStreamCtxt* aStream, TBool aKillStream ) : + iStream(aStream), iMessage(NULL), iKillStream(aKillStream) +{ + MPASSIGN( this->mpool, mpool ); +} + +CXWAskDlg::CXWAskDlg( MPFORMAL TBuf16<128>* aMessage) + : iStream(NULL), iMessage(aMessage) +{ + MPASSIGN( this->mpool, mpool ); +} + +CXWAskDlg::~CXWAskDlg() +{ + if ( iKillStream && iStream != NULL ) { + stream_destroy( iStream ); + } +} + +static void +SwapInSymbLinefeed( TDes16& buf16 ) +{ + TBuf16<1> lfDescNew; + lfDescNew.Append( CEditableText::ELineBreak ); + TBuf8<4> tmp( (unsigned char*)XP_CR ); + TBuf16<4> lfDescOld; + lfDescOld.Copy( tmp ); + + XP_LOGF( "starting search-and-replace" ); +#if 0 + TInt len = buf16.Length(); + TPtrC16 rightPart = buf16.Right(len); + TInt leftLen = 0; + for ( ; ; ) { + TInt pos = rightPart.Find( lfDescOld ); + if ( pos == KErrNotFound ) { + break; + } + buf16.Replace( leftLen + pos, 1, lfDescNew ); + leftLen += pos; + len -= pos; + /* This won't compile. Need to figure out how to replace without + starting the search at the beginning each time */ + rightPart = buf16.Right(len); + } +#else + for ( ; ; ) { + TInt pos = buf16.Find( lfDescOld ); + if ( pos == KErrNotFound ) { + break; + } + buf16.Replace( pos, 1, lfDescNew ); + } +#endif + XP_LOGF( "search-and-replace done" ); +} + +void +CXWAskDlg::PreLayoutDynInitL() +{ + CEikEdwin* contents = (CEikEdwin*)Control( EAskContents ); + + // Load the stream's contents into a read-only edit control. + if ( iStream ) { + TInt size = stream_getSize( iStream ); + XP_U16* buf16 = new(ELeave) XP_U16[size]; + CleanupStack::PushL( buf16 ); + + unsigned char* buf8 = (unsigned char*)XP_MALLOC( mpool, size ); + /* PENDING This belongs on the leave stack */ + User::LeaveIfNull( buf8 ); + stream_getBytes( iStream, buf8, SC(XP_U16,size) ); + + TPtrC8 desc8( buf8, size ); + TPtr16 desc16( buf16, size ); +#if 0 + if ( ConvertToDblByteL( desc16, desc8 ) ) { + contents->SetTextL( &desc16 ); + } +#else + desc16.Copy( desc8 ); + SwapInSymbLinefeed( desc16 ); + contents->SetTextL( &desc16 ); +#endif + XP_FREE( mpool, buf8 ); + CleanupStack::PopAndDestroy( buf16 ); + } else { + contents->SetTextL( iMessage ); + } +} /* PreLayoutDynInitL */ + +TBool CXWAskDlg::OkToExitL( TInt aButtonID /* pressed button */ ) +{ + /* The function should return ETrue if it is OK to exit, and EFalse to + keep the dialog active. It should always return ETrue if the button + with ID EEikBidOK was activated. */ + + XP_LOGF( "CXWAskDlg::OkToExitL passed %d", aButtonID ); + + return ETrue; +} + +/* static */ TBool +CXWAskDlg::DoAskDlg( MPFORMAL XWStreamCtxt* aStream, TBool aKillStream ) +{ + CXWAskDlg* me = new(ELeave)CXWAskDlg( MPPARM(mpool) aStream, aKillStream ); + return me->ExecuteLD( R_XWORDS_CONFIRMATION_QUERY ); +} + +/* static */ TBool +CXWAskDlg::DoAskDlg( MPFORMAL TBuf16<128>* aMessage ) +{ + CXWAskDlg* me = new(ELeave)CXWAskDlg( MPPARM(mpool) aMessage ); + return me->ExecuteLD( R_XWORDS_CONFIRMATION_QUERY ); +} + +/* static */ void +CXWAskDlg::DoInfoDlg( MPFORMAL XWStreamCtxt* aStream, TBool aKillStream ) +{ + CXWAskDlg* me = new(ELeave)CXWAskDlg( MPPARM(mpool) aStream, aKillStream ); + (void)me->ExecuteLD( R_XWORDS_INFO_ONLY ); +} + diff --git a/xwords4/symbian/src/symblnk.cpp b/xwords4/symbian/src/symblnk.cpp new file mode 100644 index 000000000..adbac6af9 --- /dev/null +++ b/xwords4/symbian/src/symblnk.cpp @@ -0,0 +1,91 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). (based on sample + * app helloworldbasic "Copyright (c) 2002, Nokia. All rights + * reserved.") + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#if defined SERIES_60 +# include +# include "xwords_60.rsg" +#elif defined SERIES_80 +# include +# include "xwords_80.rsg" +#endif + +#include "xwords.hrh" +#include "symblnk.h" + + +CXWBlankSelDlg::CXWBlankSelDlg( const XP_UCHAR4* aTexts, + TInt aNTiles, TInt* aResultP ) + :iTexts(aTexts), + iNTiles(aNTiles), + iResultP(aResultP), + iFacesList(NULL) +{ + // nothing else +} + +void +CXWBlankSelDlg::PreLayoutDynInitL() +{ + // stuff the array +#if defined SERIES_80 + CEikChoiceList* list; +#elif defined SERIES_60 + CAknListQueryControl* list; +#endif + CDesC16ArrayFlat* facesList = new (ELeave)CDesC16ArrayFlat( iNTiles ); + + TInt i; + for ( i = 0; i < iNTiles; ++i ) { + TBuf16<4> buf16; + buf16.Copy( TPtrC8(iTexts[i]) ); + facesList->AppendL( buf16 ); + } + +#if defined SERIES_80 + list = static_cast(Control(ESelBlankChoice)); + list->SetArrayExternalOwnership( EFalse ); + list->SetArrayL( facesList ); +#elif defined SERIES_60 + list = static_cast(Control(ESelBlankChoice)); +#endif +} + +TBool +CXWBlankSelDlg::OkToExitL( TInt /*aKeyCode*/ ) +{ +#if defined SERIES_80 + CEikChoiceList* list = static_cast + (Control(ESelBlankChoice)); + *iResultP = list->CurrentItem(); +#endif + return ETrue; +} // OkToExitL + +/* static */ void +CXWBlankSelDlg::UsePickTileDialogL( const XP_UCHAR4* texts, TInt aNTiles, + TInt* resultP ) +{ + CXWBlankSelDlg* dlg = new (ELeave) CXWBlankSelDlg( texts, aNTiles, + resultP ); + (void)dlg->ExecuteLD( R_XWORDS_BLANK_PICKER ); +} // UsePickTileDialogL diff --git a/xwords4/symbian/src/symdict.cpp b/xwords4/symbian/src/symdict.cpp new file mode 100644 index 000000000..d76d69d3b --- /dev/null +++ b/xwords4/symbian/src/symdict.cpp @@ -0,0 +1,382 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4;-*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +extern "C" { +#include "dictnryp.h" +#include "mempool.h" +#include "xptypes.h" +} + +#include +#include "symdict.h" +#include "symutil.h" + + +typedef struct SymDictCtxt { + DictionaryCtxt super; + +} SymDictCtxt; + +static void sym_dictionary_destroy( DictionaryCtxt* dict ); + + + +static XP_U8 +readXP_U8( RFile* file ) +{ + TBuf8<1> buf; + TInt err = file->Read( buf, 1 ); + XP_ASSERT( err == KErrNone ); + return *buf.Ptr(); +} // readXP_U8 + +static XP_U16 +readXP_U16( RFile* file ) +{ + TBuf8<2> buf; + TInt err = file->Read( buf, 2 ); + XP_ASSERT( err == KErrNone ); + return XP_NTOHS( *(XP_U16*)buf.Ptr() ); +} // readXP_U16 + +static XP_U32 +readXP_U32( RFile* file ) +{ + TBuf8<4> buf; + TInt err = file->Read( buf, 4 ); + XP_ASSERT( err == KErrNone ); + return XP_NTOHL( *(XP_U32*)buf.Ptr() ); +} // readXP_U32 + +static XP_U16 +symCountSpecials( SymDictCtxt* ctxt ) +{ + XP_U16 result = 0; + XP_U16 i; + + for ( i = 0; i < ctxt->super.nFaces; ++i ) { + if ( IS_SPECIAL(ctxt->super.faces16[i] ) ) { + ++result; + } + } + + return result; +} /* symCountSpecials */ + +#ifdef DEBUG +static void +printBitmap( CFbsBitmap* bmp ) +{ + TSize bmpSize = bmp->SizeInPixels(); + TBitmapUtil butil( bmp ); + butil.Begin( TPoint(0,0) ); + TInt row, col; + + for ( row = 0; row < bmpSize.iHeight; ++row ) { + char buf[64]; + for ( col = 0; col < bmpSize.iWidth; ++col ) { + butil.SetPos( TPoint(col, row) ); + if ( butil.GetPixel() ) { + buf[col] = '*'; + } else { + buf[col] = '_'; + } + } + buf[col] = '\0'; + XP_LOGF( "row %d: %s", row, buf ); + } + + butil.End(); +} +#else +#define printBitmap(b) +#endif + +static XP_Bitmap* +symMakeBitmap( SymDictCtxt* /*ctxt*/, RFile* file ) +{ + XP_U8 nCols = readXP_U8( file ); + CFbsBitmap* bitmap = (CFbsBitmap*)NULL; + const TDisplayMode dispMode = EGray2; + + if ( nCols > 0 ) { + + XP_U8 nRows = readXP_U8( file ); + XP_U8 srcByte = 0; + XP_U16 nBits; + bitmap = new (ELeave) CFbsBitmap(); + bitmap->Create( TSize(nCols, nRows), dispMode ); + + TBitmapUtil butil( bitmap ); + butil.Begin( TPoint(0,0) ); + + TInt col, row; + nBits = nRows * nCols; + TInt curBit = 0; + for ( row = 0; curBit < nBits; ++row ) { + for ( col = 0; col < nCols; ++col ) { + TUint32 value; + TInt index = curBit % 8; + + if ( index == 0 ) { + srcByte = readXP_U8( file ); + } + + value = ((srcByte & 0x80) == 0) ? 1L : 0L; + + butil.SetPos( TPoint(col, row) ); + butil.SetPixel( value ); + + srcByte <<= 1; + ++curBit; + } + } + butil.End(); + + printBitmap( bitmap ); + } + + return (XP_Bitmap*)bitmap; +} /* symMakeBitmap */ + +static void +symLoadSpecialData( SymDictCtxt* ctxt, RFile* file ) +{ + TInt i; + TInt nSpecials = symCountSpecials( ctxt ); + XP_UCHAR** texts; + SpecialBitmaps* bitmaps; + + XP_DEBUGF( "loadSpecialData: there are %d specials", nSpecials ); + + texts = (XP_UCHAR**)XP_MALLOC( ctxt->super.mpool, + nSpecials * sizeof(*texts) ); + bitmaps = (SpecialBitmaps*) + XP_MALLOC( ctxt->super.mpool, nSpecials * sizeof(*bitmaps) ); + + for ( i = 0; i < ctxt->super.nFaces; ++i ) { + + XP_CHAR16 face = ctxt->super.faces16[(short)i]; + if ( IS_SPECIAL(face) ) { + + /* get the string */ + XP_U8 txtlen = readXP_U8( file ); + XP_UCHAR* text = (XP_UCHAR*)XP_MALLOC(ctxt->super.mpool, txtlen+1); + TPtr8 desc( text, txtlen ); + file->Read( desc, txtlen ); + text[txtlen] = '\0'; + XP_ASSERT( face < nSpecials ); + texts[face] = text; + + XP_DEBUGF( "making bitmaps for %s", texts[face] ); + bitmaps[face].largeBM = symMakeBitmap( ctxt, file ); + bitmaps[face].smallBM = symMakeBitmap( ctxt, file ); + } + } + + ctxt->super.chars = texts; + ctxt->super.bitmaps = bitmaps; + XP_LOGF( "returning from symLoadSpecialData" ); +} // symLoadSpecialData + +static void +readFileToBuf( XP_UCHAR* dictBuf, const RFile* file ) +{ + XP_U32 offset = 0; + for ( ; ; ) { + TBuf8<1024> buf; + TInt err = file->Read( buf, buf.MaxLength() ); + XP_ASSERT( err == KErrNone ); + TInt nRead = buf.Size(); + if ( nRead <= 0 ) { + break; + } + XP_MEMCPY( (void*)(dictBuf + offset), (void*)buf.Ptr(), nRead ); + offset += nRead; + } +} // readFileToBuf + +DictionaryCtxt* +sym_dictionary_makeL( MPFORMAL TFileName* base, const XP_UCHAR* aDictName ) +{ + if ( !aDictName ) { + SymDictCtxt* ctxt = (SymDictCtxt*)XP_MALLOC( mpool, sizeof( *ctxt ) ); + XP_MEMSET( ctxt, 0, sizeof(*ctxt) ); + MPASSIGN( ctxt->super.mpool, mpool ); + return &ctxt->super; + } else { + + TBuf16<32> dname16; + dname16.Copy( TPtrC8(aDictName) ); + base->Append( dname16 ); + base->Append( _L(".xwd") ); + SymDictCtxt* ctxt = NULL; + + RFs fileSession; + User::LeaveIfError(fileSession.Connect()); + CleanupClosePushL(fileSession); + + RFile file; + TInt err = file.Open( fileSession, *base, EFileRead ); + if ( err != KErrNone ) { + XP_LOGDESC16( base ); + XP_LOGF( "file.Open => %d", err ); + } + User::LeaveIfError( err ); + CleanupClosePushL(file); + + ctxt = (SymDictCtxt*)XP_MALLOC( mpool, sizeof(*ctxt) ); + User::LeaveIfNull( ctxt ); + XP_MEMSET( ctxt, 0, sizeof( *ctxt ) ); + MPASSIGN( ctxt->super.mpool, mpool ); + + dict_super_init( (DictionaryCtxt*)ctxt ); + + ctxt->super.destructor = sym_dictionary_destroy; + XP_ASSERT( ctxt->super.name == NULL ); + symReplaceStrIfDiff( MPPARM(mpool) &ctxt->super.name, aDictName ); + + XP_U16 flags = readXP_U16( &file ); + XP_LOGF( "read flags are: 0x%x", (TInt)flags ); + + TInt numFaces = readXP_U8( &file ); + ctxt->super.nFaces = (XP_U8)numFaces; + XP_DEBUGF( "read %d faces from dict", (TInt)numFaces ); + + ctxt->super.faces16 = (XP_U16*) + XP_MALLOC( mpool, numFaces * sizeof(ctxt->super.faces16[0]) ); +#ifdef NODE_CAN_4 + if ( flags == 0x0002 ) { + ctxt->super.nodeSize = 3; + } else if ( flags == 0x0003 ) { + ctxt->super.nodeSize = 4; + } else { + XP_DEBUGF( "flags=0x%x", flags ); + XP_ASSERT( 0 ); + } + + ctxt->super.is_4_byte = ctxt->super.nodeSize == 4; + TInt i; + for ( i = 0; i < numFaces; ++i ) { + ctxt->super.faces16[i] = readXP_U16( &file ); + } +#else + error will robinson....; +#endif + + ctxt->super.countsAndValues = + (XP_U8*)XP_MALLOC( mpool, numFaces*2 ); + (void)readXP_U16( &file ); // skip xloc header + + for ( i = 0; i < numFaces*2; i += 2 ) { + ctxt->super.countsAndValues[i] = readXP_U8( &file ); + ctxt->super.countsAndValues[i+1] = readXP_U8( &file ); + } + + symLoadSpecialData( ctxt, &file ); + + // Now, until we figure out how/whether Symbian does memory + // mapping of files, we need to allocate a buffer to hold the + // entire freaking DAWG... :-( + TInt dawgSize; + (void)file.Size( dawgSize ); + TInt pos = 0; + file.Seek( ESeekCurrent, pos ); + dawgSize -= pos; + + if ( dawgSize > SC(TInt, sizeof(XP_U32)) ) { + XP_U32 offset = readXP_U32( &file ); + dawgSize -= sizeof(XP_U32); + + XP_ASSERT( dawgSize % ctxt->super.nodeSize == 0 ); +# ifdef DEBUG + ctxt->super.numEdges = dawgSize / ctxt->super.nodeSize; +# endif + + if ( dawgSize > 0 ) { + XP_DEBUGF( "setting topEdge; offset = %d", offset ); + + XP_U8* dictBuf = (XP_U8*)XP_MALLOC( mpool, dawgSize ); + User::LeaveIfNull( dictBuf ); // will leak ctxt (PENDING...) + + readFileToBuf( dictBuf, &file ); + + ctxt->super.base = (array_edge*)dictBuf; + + ctxt->super.topEdge = ctxt->super.base + + (offset * ctxt->super.nodeSize); + ctxt->super.topEdge = ctxt->super.base + + (offset * ctxt->super.nodeSize); + } else { + ctxt->super.topEdge = (array_edge*)NULL; + ctxt->super.base = (array_edge*)NULL; + } + } + + CleanupStack::PopAndDestroy( &file ); // file + CleanupStack::PopAndDestroy( &fileSession ); // fileSession + + return &ctxt->super; + } +} // sym_dictionary_make + +static void +sym_dictionary_destroy( DictionaryCtxt* dict ) +{ + SymDictCtxt* sctx = (SymDictCtxt*)dict; + XP_U16 nSpecials = symCountSpecials( sctx ); + XP_U16 i; + + if ( dict->countsAndValues != NULL ) { + XP_FREE( sctx->super.mpool, dict->countsAndValues ); + } + if ( dict->faces16 != NULL ) { + XP_FREE( sctx->super.mpool, dict->faces16 ); + } + + if ( !!sctx->super.chars ) { + for ( i = 0; i < nSpecials; ++i ) { + XP_UCHAR* text = sctx->super.chars[i]; + if ( !!text ) { + XP_FREE( sctx->super.mpool, text ); + } + } + XP_FREE( dict->mpool, dict->chars ); + } + + if ( !!sctx->super.bitmaps ) { + for ( i = 0; i < nSpecials; ++i ) { + CFbsBitmap* bitmap = (CFbsBitmap*)dict->bitmaps[i].smallBM; + delete bitmap; + bitmap = (CFbsBitmap*)dict->bitmaps[i].largeBM; + delete bitmap; + } + XP_FREE( dict->mpool, dict->bitmaps ); + } + + if ( dict->base != NULL ) { + XP_FREE( dict->mpool, dict->base ); + } + + if ( dict->name ) { + XP_FREE( dict->mpool, dict->name ); + } + + XP_FREE( dict->mpool, dict ); +} diff --git a/xwords4/symbian/src/symdraw.cpp b/xwords4/symbian/src/symdraw.cpp new file mode 100644 index 000000000..a9d8440ef --- /dev/null +++ b/xwords4/symbian/src/symdraw.cpp @@ -0,0 +1,872 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4;-*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +extern "C" { +#include "comtypes.h" +#include "board.h" +#include "draw.h" +#include "mempool.h" + +} // extern "C" + +#include + +#if defined SERIES_60 + +# include +# include +# include "xwords_60.mbg" + +# define BMNAME( file, bm ) file ## _60 ## bm + +#elif defined SERIES_80 +# include +# include +# include +# include "xwords_80.mbg" + +# define BMNAME( file, bm ) file ## _80 ## bm + +#else +# error define a series platform!!!! +#endif + +#include + +#include "symdraw.h" + +#define TRAY_CURSOR_HT 2 + +enum { + COLOR_BLACK, + COLOR_WHITE, + COLOR_CURSOR, + + COLOR_PLAYER1, + COLOR_PLAYER2, + COLOR_PLAYER3, + COLOR_PLAYER4, + + COLOR_DBL_LTTR, + COLOR_DBL_WORD, + COLOR_TRPL_LTTR, + COLOR_TRPL_WORD, + + COLOR_EMPTY, + COLOR_TILE, + + COLOR_NCOLORS /* 12 */ +}; + +#ifdef SERIES_60 +# define CONST_60 const +#else +# define CONST_60 +#endif + +typedef struct SymDrawCtxt { + /* Must be first */ + DrawCtxVTable* vtable; + + CWindowGc* iGC; + + CEikonEnv* iiEikonEnv; /* iEikonEnv is a macro in Symbian headers!!! */ + CCoeEnv* iCoeEnv; + + CFbsBitmap* iRightArrow; + CFbsBitmap* iDownArrow; + CFbsBitmap* iStar; + CFbsBitmap* iTurnIcon; + CFbsBitmap* iTurnIconMask; + CFbsBitmap* iRobotIcon; + CFbsBitmap* iRobotIconMask; + + CONST_60 CFont* iTileFaceFont; + CONST_60 CFont* iTileValueFont; + CONST_60 CFont* iBoardFont; + CONST_60 CFont* iScoreFont; + + XP_U16 iTrayOwner; + XP_Bool iAllFontsSame; + TRgb colors[COLOR_NCOLORS]; + + MPSLOT +} SymDrawCtxt; + +static void +symLocalRect( TRect* dest, const XP_Rect* src ) +{ + dest->Move( src->left, src->top ); + dest->SetWidth( src->width + 1 ); + dest->SetHeight( src->height + 1 ); +} // symLocalRect + +static void +symClearRect( SymDrawCtxt* sctx, const TRect* rect, TInt clearTo ) +{ + sctx->iGC->SetBrushColor( sctx->colors[clearTo] ); + sctx->iGC->SetBrushStyle( CGraphicsContext::ESolidBrush ); + sctx->iGC->SetPenStyle( CGraphicsContext::ENullPen ); + sctx->iGC->DrawRect( *rect ); +} // symClearRect + +static void +drawFocusRect( SymDrawCtxt* sctx, const XP_Rect* rect, XP_Bool hasfocus ) +{ + TRect lRect; + symLocalRect( &lRect, rect ); + + lRect.Grow( 2, 2 ); // This is space board.c doesn't know about + + sctx->iGC->SetBrushStyle( CGraphicsContext::ENullBrush ); + sctx->iGC->SetPenStyle( CGraphicsContext::ESolidPen ); + XP_U16 index = SC(XP_U16,(hasfocus? COLOR_CURSOR : COLOR_WHITE)); + sctx->iGC->SetPenColor( sctx->colors[index] ); + TInt i; + for ( i = 0; i < 2; ++i ) { + sctx->iGC->DrawRect( lRect ); + lRect.Shrink( 1, 1 ); + } +} // drawFocusRect + +static void +getBonusColor( SymDrawCtxt* sctx, XWBonusType bonus, TRgb* rgb ) +{ + TInt index; + if ( bonus == BONUS_NONE ) { + index = COLOR_WHITE; + } else { + index = COLOR_DBL_LTTR + bonus - 1; + } + *rgb = sctx->colors[index]; +} // getBonusColor + +static void +drawBitmap( SymDrawCtxt* sctx, CFbsBitmap* bmp, CFbsBitmap* mask, + const TRect* aRect ) +{ + TRect rect( *aRect ); + TSize bmpSize = bmp->SizeInPixels(); + if ( bmpSize.iWidth <= rect.Width() + && bmpSize.iHeight <= rect.Height() ) { + + rect.Move( (rect.Width() - bmpSize.iWidth) / 2, + (rect.Height() - bmpSize.iHeight) / 2 ); + rect.SetSize( bmpSize ); + + TRect imgRect( TPoint(0,0), bmpSize ); + sctx->iGC->BitBltMasked( rect.iTl, bmp, imgRect, mask, ETrue ); + } else { + XP_LOGF( "bitmap too big" ); + } +} /* drawBitmap */ + +static void +sym_draw_destroyCtxt( DrawCtx* p_dctx ) +{ + SymDrawCtxt* sctx = (SymDrawCtxt*)p_dctx; + XP_LOGF( "freeing draw ctxt" ); + + CWsScreenDevice* sdev = sctx->iCoeEnv->ScreenDevice(); + sdev->ReleaseFont( sctx->iTileFaceFont ); + if ( ! sctx->iAllFontsSame ) { + sdev->ReleaseFont( sctx->iTileValueFont ); + sdev->ReleaseFont( sctx->iBoardFont ); + sdev->ReleaseFont( sctx->iScoreFont ); + } + + delete sctx->iRightArrow; + delete sctx->iDownArrow; + delete sctx->iStar; + delete sctx->iTurnIcon; + delete sctx->iTurnIconMask; + delete sctx->iRobotIcon; + delete sctx->iRobotIconMask; + + XP_ASSERT( sctx ); + XP_ASSERT( sctx->vtable ); + XP_FREE( sctx->mpool, sctx->vtable ); + XP_FREE( sctx->mpool, sctx ); +} + +static XP_Bool +sym_draw_boardBegin( DrawCtx* p_dctx, const DictionaryCtxt* dict, + const XP_Rect* rect, XP_Bool hasfocus ) +{ + XP_LOGF( "sym_draw_boardBegin" ); + SymDrawCtxt* sctx = (SymDrawCtxt*)p_dctx; + drawFocusRect( sctx, rect, hasfocus ); + return XP_TRUE; +} + +static void +sym_draw_boardFinished( DrawCtx* /*p_dctx*/ ) +{ +} + +#ifdef DEBUG +static XP_Bool +sym_draw_vertScrollBoard( DrawCtx* /*p_dctx*/, XP_Rect* /*rect*/, + XP_S16 /*dist*/ ) +{ + XP_ASSERT(0); + return XP_FALSE; +} +#endif + +static XP_Bool +sym_draw_trayBegin( DrawCtx* p_dctx, const XP_Rect* rect, + XP_U16 owner, XP_Bool hasfocus ) +{ + SymDrawCtxt* sctx = (SymDrawCtxt*)p_dctx; + sctx->iTrayOwner = owner; + + drawFocusRect( sctx, rect, hasfocus ); + + return XP_TRUE; +} + +static void +sym_draw_trayFinished( DrawCtx* /*dctx*/ ) +{ +} + +static void +makeRemText( TBuf16<64>* buf, XP_S16 nLeft ) +{ + if ( nLeft < 0 ) { + nLeft = 0; + } + buf->Num( nLeft ); + buf->Insert( 0, _L("Tiles left in pool: ") ); +} // makeRemText + +static void +sym_draw_measureRemText( DrawCtx* p_dctx, const XP_Rect* /*r*/, + XP_S16 nTilesLeft, + XP_U16* widthP, XP_U16* heightP ) +{ + SymDrawCtxt* sctx = (SymDrawCtxt*)p_dctx; + TBuf16<64> tbuf; + makeRemText( &tbuf, nTilesLeft ); + + CONST_60 CFont* font = sctx->iScoreFont; + *widthP = (XP_S16)font->TextWidthInPixels( tbuf ); + *heightP = (XP_S16)font->HeightInPixels(); +} // sym_draw_measureRemText + +static void +sym_draw_drawRemText(DrawCtx* p_dctx, const XP_Rect* rInner, + const XP_Rect* /*rOuter*/, XP_S16 nTilesLeft) +{ + SymDrawCtxt* sctx = (SymDrawCtxt*)p_dctx; + TBuf16<64> tbuf; + makeRemText( &tbuf, nTilesLeft ); + + TRect lRect; + symLocalRect( &lRect, rInner ); + + sctx->iGC->SetPenStyle( CGraphicsContext::ESolidPen ); + sctx->iGC->SetPenColor( KRgbBlack ); + sctx->iGC->SetBrushColor( KRgbGray ); + sctx->iGC->SetBrushStyle( CGraphicsContext::ESolidBrush ); + + CONST_60 CFont* font = sctx->iScoreFont; + sctx->iGC->UseFont( font ); + sctx->iGC->DrawText( tbuf, lRect, lRect.Height() - 2 ); + sctx->iGC->DiscardFont(); +} // sym_draw_drawRemText + +static void +sym_draw_scoreBegin( DrawCtx* p_dctx, const XP_Rect* rect, + XP_U16 /*numPlayers*/, XP_Bool hasfocus ) +{ + SymDrawCtxt* sctx = (SymDrawCtxt*)p_dctx; + drawFocusRect( sctx, rect, hasfocus ); +} + +static void +sym_draw_measureScoreText( DrawCtx* p_dctx, const XP_Rect* /*r*/, + const DrawScoreInfo* /*dsi*/, + XP_U16* widthP, XP_U16* heightP ) +{ + SymDrawCtxt* sctx = (SymDrawCtxt*)p_dctx; + CONST_60 CFont* font = sctx->iScoreFont; + TInt height = font->HeightInPixels(); + + *widthP = 10; /* whatever; we're only using rOuter */ + *heightP = SC( XP_U16, height ); +} /* sym_draw_measureScoreText */ + +/* We want the elements of a scoreboard to line up in columns. So draw them + * one at a time. NAME SCORE TILE_LEFT ?LAST_SCORE? Might be better to show + * robot-ness and local/remoteness with colors? Turn is with an icon. + */ +static void +sym_draw_score_drawPlayer( DrawCtx* p_dctx, const XP_Rect* /*rInner*/, + const XP_Rect* rOuter, const DrawScoreInfo* dsi ) +{ + const TInt KTurnIconWidth = 16; + const TInt KNameColumnWidth = 90; + const TInt KScoreColumnWidth = 25; +/* const TInt KTilesLeftColumnWidth = 15; */ + const TInt KLastMoveColumnWidth = 100; /* will be clipped down */ + + SymDrawCtxt* sctx = (SymDrawCtxt*)p_dctx; + CONST_60 CFont* font = sctx->iScoreFont; + + TRect lRect; + symLocalRect( &lRect, rOuter ); + TInt rightEdge = lRect.iBr.iX; + symClearRect( sctx, &lRect, dsi->selected? COLOR_BLACK:COLOR_WHITE ); + + TInt fontHeight = font->AscentInPixels(); + TInt baseline = fontHeight + ((lRect.Height() - fontHeight) / 2); + + /* The y coords of the rect stay the same, but the x coords change as we + do each column. The first time, turn-icon column, the left edge is + already where we want it. */ + lRect.iBr.iX = lRect.iTl.iX + KTurnIconWidth; + symClearRect( sctx, &lRect, COLOR_WHITE ); + if ( dsi->isTurn ) { + drawBitmap( sctx, sctx->iTurnIcon, sctx->iTurnIconMask, &lRect ); + } else if ( dsi->isRobot ) { + drawBitmap( sctx, sctx->iRobotIcon, sctx->iRobotIconMask, &lRect ); + } + + XP_U16 playerNum = dsi->playerNum; + if ( dsi->selected ) { + sctx->iGC->SetPenColor( sctx->colors[COLOR_WHITE] ); + } else { + sctx->iGC->SetPenColor( sctx->colors[playerNum + COLOR_PLAYER1] ); + } + sctx->iGC->SetBrushStyle( CGraphicsContext::ENullBrush ); + + TBuf16<64> tbuf; + sctx->iGC->UseFont( font ); + + /* Draw name */ + lRect.iTl.iX = lRect.iBr.iX + 1; /* add one to get name away from edge */ + lRect.iBr.iX += KNameColumnWidth; + tbuf.Copy( TBuf8<32>(dsi->name) ); + if ( dsi->isRemote ) { + tbuf.Insert( 0, _L("[") ); + tbuf.Append( _L("]") ); + } + sctx->iGC->DrawText( tbuf, lRect, baseline ); + + /* Draw score, right-justified */ + lRect.iTl.iX = lRect.iBr.iX; + lRect.iBr.iX += KScoreColumnWidth; + tbuf.Num( dsi->score ); + sctx->iGC->DrawText( tbuf, lRect, baseline, CGraphicsContext::ERight ); + + /* Draw last move */ + lRect.iTl.iX = lRect.iBr.iX + 6; /* 6 gives it some spacing from + r-justified score */ + lRect.iBr.iX += KLastMoveColumnWidth; + XP_UCHAR buf[32]; + XP_U16 len = sizeof(buf); + if ( (*dsi->lsc)( dsi->lscClosure, playerNum, buf, &len ) ) { + tbuf.Copy( TBuf8<32>(buf) ); + if ( lRect.iBr.iX > rightEdge ) { + lRect.iBr.iX = rightEdge; + } + sctx->iGC->DrawText( tbuf, lRect, baseline ); + } + + sctx->iGC->DiscardFont(); +} /* sym_draw_score_drawPlayer */ + +static void +sym_draw_score_pendingScore( DrawCtx* p_dctx, const XP_Rect* rect, + XP_S16 score, XP_U16 /*playerNum*/ ) +{ + SymDrawCtxt* sctx = (SymDrawCtxt*)p_dctx; + TRect lRect; + symLocalRect( &lRect, rect ); + lRect.Shrink( 1, 1 ); + lRect.SetHeight( lRect.Height() - TRAY_CURSOR_HT ); + sctx->iGC->SetPenColor( sctx->colors[COLOR_BLACK] ); + sctx->iGC->SetBrushStyle( CGraphicsContext::ESolidBrush ); + sctx->iGC->SetBrushColor( sctx->colors[COLOR_WHITE] ); + + sctx->iGC->UseFont( sctx->iTileValueFont ); + + TInt halfHeight = lRect.Height() / 2; + lRect.iBr.iY -= halfHeight; + sctx->iGC->DrawText( _L("Pts:"), lRect, halfHeight-2, + CGraphicsContext::ERight ); + + TBuf16<8> buf; + if ( score >= 0 ) { + buf.Num( score ); + } else { + buf.Copy( _L("???") ); + } + + lRect.iTl.iY += halfHeight; + lRect.iBr.iY += halfHeight; + sctx->iGC->DrawText( buf, lRect, halfHeight-2, CGraphicsContext::ERight ); + + sctx->iGC->DiscardFont(); +} + +static void +sym_draw_scoreFinished( DrawCtx* /*dctx*/ ) +{ +} + +static void +sym_draw_drawTimer( DrawCtx* /*p_dctx*/, const XP_Rect* /*rInner*/, + const XP_Rect* /*rOuter*/, + XP_U16 /*player*/, XP_S16 /*secondsLeft*/ ) +{ +} + +static void +textInCell( SymDrawCtxt* sctx, const XP_UCHAR* text, const TRect* lRect, + TBool highlight ) +{ + if ( highlight ) { + sctx->iGC->SetPenColor( sctx->colors[COLOR_WHITE] ); + } else { + sctx->iGC->SetPenColor( sctx->colors[COLOR_BLACK] ); + } + sctx->iGC->SetBrushStyle( CGraphicsContext::ENullBrush ); + CONST_60 CFont* font = sctx->iBoardFont; + + TBuf16<64> tbuf; + tbuf.Copy( TPtrC8(text) ); + + TInt ht = font->AscentInPixels(); + TInt baseOffset = ht + ((lRect->Height() - ht) / 2); + + sctx->iGC->UseFont( font ); + sctx->iGC->DrawText( tbuf, *lRect, baseOffset, CGraphicsContext::ECenter ); + sctx->iGC->DiscardFont(); +} /* textInCell */ + +static XP_Bool +sym_draw_drawCell( DrawCtx* p_dctx, const XP_Rect* rect, + /* at least one of these two will be null */ + const XP_UCHAR* text, XP_Bitmap bitmap, + Tile tile, XP_S16 /*owner*/, /* -1 means don't use */ + XWBonusType bonus, HintAtts /*hintAtts*/, + XP_Bool isBlank, XP_Bool highlight, + XP_Bool isStar ) +{ + TRect lRect; + SymDrawCtxt* sctx = (SymDrawCtxt*)p_dctx; + + symLocalRect( &lRect, rect ); + + TRgb rgb; + if ( highlight ) { + rgb = sctx->colors[COLOR_BLACK]; + } else if ( !!bitmap || (!!text && XP_STRLEN((const char*)text) > 0)) { + rgb = sctx->colors[COLOR_TILE]; + } else { + getBonusColor( sctx, bonus, &rgb ); + } + + sctx->iGC->SetPenColor( sctx->colors[COLOR_BLACK] ); + sctx->iGC->SetPenStyle( CGraphicsContext::ESolidPen ); + sctx->iGC->SetBrushColor( rgb ); + sctx->iGC->SetBrushStyle( CGraphicsContext::ESolidBrush ); + sctx->iGC->DrawRect( lRect ); + + if ( !!bitmap ) { + drawBitmap( sctx, (CFbsBitmap*)bitmap, (CFbsBitmap*)bitmap, &lRect ); + } else if ( !!text && (*text != '\0') ) { + TRect r2(lRect); + textInCell( sctx, text, &r2, highlight ); + } else if ( isStar ) { + drawBitmap( sctx, sctx->iStar, sctx->iStar, &lRect ); + } + + if ( isBlank ) { + lRect.Shrink( 1, 0 ); + sctx->iGC->DrawLine( lRect.iTl, TPoint(lRect.iTl.iX, lRect.iBr.iY ) ); + lRect.Shrink( 1, 0 ); + /* draws to right of points; second Shrink is easier than subbing 1 + from x coords */ + sctx->iGC->DrawLine( TPoint(lRect.iBr.iX, lRect.iTl.iY), lRect.iBr ); + } + + return XP_TRUE; +} /* sym_draw_drawCell */ + +static void +sym_draw_invertCell( DrawCtx* /*p_dctx*/, const XP_Rect* /*rect*/ ) +{ +} + +static void +sym_draw_drawTile( DrawCtx* p_dctx, const XP_Rect* rect, + /* at least 1 of these two will be null*/ + const XP_UCHAR* text, XP_Bitmap bitmap, + XP_S16 val, XP_Bool highlighted ) +{ + SymDrawCtxt* sctx = (SymDrawCtxt*)p_dctx; + XP_U16 index = SC(XP_U16,COLOR_PLAYER1 + sctx->iTrayOwner); + + TRect lRect; + symLocalRect( &lRect, rect ); + symClearRect( sctx, &lRect, COLOR_WHITE ); + + lRect.Shrink( 1, 1 ); + lRect.SetHeight( lRect.Height() - TRAY_CURSOR_HT ); + + sctx->iGC->SetPenColor( sctx->colors[index] ); + sctx->iGC->SetPenStyle( CGraphicsContext::ESolidPen ); + sctx->iGC->SetBrushColor( sctx->colors[COLOR_TILE] ); + sctx->iGC->SetBrushStyle( CGraphicsContext::ESolidBrush ); + sctx->iGC->DrawRect( lRect ); + + lRect.Shrink( 1, 1 ); + if ( highlighted ) { + sctx->iGC->DrawRect( lRect ); + } + + lRect.Shrink( 2, 2 ); + + // now put the text in the thing + if ( !!bitmap ) { + drawBitmap( sctx, (CFbsBitmap*)bitmap, (CFbsBitmap*)bitmap, &lRect ); + } else if ( !!text ) { + CONST_60 CFont* font = sctx->iTileFaceFont; + + TBuf16<4> txtbuf; + txtbuf.Copy( TBuf8<4>(text) ); + TInt ht = font->AscentInPixels(); + TPoint point( lRect.iTl.iX, lRect.iTl.iY + ht ); + + sctx->iGC->UseFont( font ); + sctx->iGC->DrawText( txtbuf, point ); + sctx->iGC->DiscardFont(); + } + + if ( val >= 0 ) { + CONST_60 CFont* font = sctx->iTileValueFont; + + TBuf16<5> txtbuf; + txtbuf.Num( val ); + TInt width = font->TextWidthInPixels( txtbuf ); + TPoint point( lRect.iBr.iX - width, lRect.iBr.iY ); + + sctx->iGC->UseFont( font ); + sctx->iGC->DrawText( txtbuf, point ); + sctx->iGC->DiscardFont(); + } +} // sym_draw_drawTile + +static void +sym_draw_drawTileBack( DrawCtx* p_dctx, const XP_Rect* rect ) +{ + sym_draw_drawTile( p_dctx, rect, (XP_UCHAR*)"?", NULL, -1, XP_FALSE ); +} + +static void +sym_draw_drawTrayDivider( DrawCtx* p_dctx, const XP_Rect* rect, + XP_Bool /*selected*/ ) +{ + SymDrawCtxt* sctx = (SymDrawCtxt*)p_dctx; + TRect lRect; + symLocalRect( &lRect, rect ); + symClearRect( sctx, &lRect, COLOR_WHITE ); + + lRect.Shrink( 1, 1 ); + lRect.SetHeight( lRect.Height() - TRAY_CURSOR_HT ); + + sctx->iGC->SetBrushStyle( CGraphicsContext::ESolidBrush ); + sctx->iGC->SetBrushColor( sctx->colors[COLOR_PLAYER1 + sctx->iTrayOwner] ); + + sctx->iGC->DrawRect( lRect ); +} + +static void +sym_draw_clearRect( DrawCtx* p_dctx, const XP_Rect* rect ) +{ + SymDrawCtxt* sctx = (SymDrawCtxt*)p_dctx; + TRect lRect; + symLocalRect( &lRect, rect ); + symClearRect( sctx, &lRect, COLOR_WHITE ); +} + +static void +sym_draw_drawBoardArrow( DrawCtx* p_dctx, const XP_Rect* rect, + XWBonusType bonus, XP_Bool vert, + HintAtts /*hintAtts*/ ) +{ + SymDrawCtxt* sctx = (SymDrawCtxt*)p_dctx; + + TRect lRect; + symLocalRect( &lRect, rect ); + + TRgb rgb; + getBonusColor( sctx, bonus, &rgb ); + sctx->iGC->SetBrushColor( rgb ); + sctx->iGC->SetBrushStyle( CGraphicsContext::ESolidBrush ); + + CFbsBitmap* arrow = vert? sctx->iDownArrow : sctx->iRightArrow; + drawBitmap( sctx, arrow, arrow, &lRect ); +} /* sym_draw_drawBoardArrow */ + +#ifdef KEY_SUPPORT +static void +sym_draw_drawTrayCursor( DrawCtx* p_dctx, const XP_Rect* rect ) +{ + SymDrawCtxt* sctx = (SymDrawCtxt*)p_dctx; + TRect lRect; + symLocalRect( &lRect, rect ); + lRect.iTl.iY += lRect.Height() - TRAY_CURSOR_HT; + symClearRect( sctx, &lRect, COLOR_WHITE ); + + sctx->iGC->SetBrushColor( sctx->colors[COLOR_CURSOR] ); + sctx->iGC->SetBrushStyle( CGraphicsContext::ESolidBrush ); + sctx->iGC->SetPenStyle( CGraphicsContext::ENullPen ); + sctx->iGC->DrawRect( lRect ); +} + +static void +sym_draw_drawBoardCursor( DrawCtx* p_dctx, const XP_Rect* rect ) +{ + SymDrawCtxt* sctx = (SymDrawCtxt*)p_dctx; + TRect lRect; + symLocalRect( &lRect, rect ); + + + sctx->iGC->SetPenColor( sctx->colors[COLOR_CURSOR] ); + sctx->iGC->SetPenStyle( CGraphicsContext::ESolidPen ); + sctx->iGC->SetBrushStyle( CGraphicsContext::ENullBrush ); + TInt i; + for ( i = 0; i <= 1; ++i ) { + sctx->iGC->DrawRect( lRect ); + lRect.Shrink( 1, 1 ); + } +} +#endif + +static XP_UCHAR* +sym_draw_getMiniWText( DrawCtx* /*p_dctx*/, + XWMiniTextType /*textHint*/ ) +{ + return (XP_UCHAR*)""; +} + +static void +sym_draw_measureMiniWText( DrawCtx* /*p_dctx*/, const XP_UCHAR* /*textP*/, + XP_U16* /*width*/, XP_U16* /*height*/ ) +{ +} + +static void +sym_draw_drawMiniWindow( DrawCtx* /*p_dctx*/, const XP_UCHAR* /*text*/, + const XP_Rect* /*rect*/, void** /*closure*/ ) +{ +} + +static void +sym_draw_eraseMiniWindow( DrawCtx* /*p_dctx*/, const XP_Rect* /*rect*/, + XP_Bool /*lastTime*/, void** /*closure*/, + XP_Bool* /*invalUnder*/ ) +{ +} + +static void +figureFonts( SymDrawCtxt* sctx ) +{ + /* Look at FontUtils class for info on fonts. */ +#if defined SERIES_80 + XP_LOGF( "figureFonts" ); + TBuf<128> fontName; + CWsScreenDevice* sdev = sctx->iCoeEnv->ScreenDevice(); + TInt nTypes = sdev->NumTypefaces(); + XP_LOGF( "count = %d", nTypes ); + + TTypefaceSupport tfSupport; + TInt smallIndex = -1; + TInt smallSize = 0x7FFF; + + for ( TInt i = 0; i < nTypes; ++i ) { + sdev->TypefaceSupport( tfSupport, i ); + if ( +#ifdef SYM_ARMI + tfSupport.iIsScalable && +#endif + tfSupport.iMinHeightInTwips < smallSize ) { + smallIndex = i; + smallSize = tfSupport.iMinHeightInTwips; + } + } + + // Now use the smallest guy + if ( smallIndex != -1 ) { + sdev->TypefaceSupport( tfSupport, smallIndex ); + fontName = tfSupport.iTypeface.iName.Des(); + + TFontSpec fontSpecBoard( fontName, scaleBoardV ); + sdev->GetNearestFontInPixels( sctx->iBoardFont, fontSpecBoard ); + + TInt tileHt = scaleTrayV - TRAY_CURSOR_HT; + TFontSpec fontSpecTray( fontName, tileHt * 3 / 4 ); + sdev->GetNearestFontInPixels( sctx->iTileFaceFont, fontSpecTray ); + + TFontSpec fontSpecVal( fontName, tileHt / 3 ); + sdev->GetNearestFontInPixels( sctx->iTileValueFont, fontSpecVal ); + + TFontSpec fontSpecScore( fontName, scaleBoardV ); + sdev->GetNearestFontInPixels( sctx->iScoreFont, fontSpecScore ); + + } else { + sctx->iTileFaceFont = (CFont*)sctx->iCoeEnv->NormalFont(); + sctx->iTileValueFont = sctx->iTileFaceFont; + sctx->iBoardFont = sctx->iTileFaceFont; + sctx->iScoreFont = sctx->iTileFaceFont; + sctx->iAllFontsSame = XP_TRUE; + } + +#elif defined SERIES_60 + CCoeEnv* ce = sctx->iCoeEnv; + sctx->iTileFaceFont = ce->NormalFont(); + sctx->iTileValueFont = sctx->iiEikonEnv->DenseFont(); + sctx->iBoardFont = sctx->iiEikonEnv->LegendFont(); + sctx->iScoreFont = sctx->iiEikonEnv->TitleFont(); +#endif + + XP_LOGF( "figureFonts done" ); +} // figureFonts + +DrawCtx* +sym_drawctxt_make( MPFORMAL CWindowGc* aGC, CCoeEnv* aCoeEnv, + CEikonEnv* aEikonEnv, CEikApplication* aApp ) +{ + XP_LOGF( "in sym_drawctxt_make" ); + SymDrawCtxt* sctx = (SymDrawCtxt*)XP_MALLOC( mpool, sizeof( *sctx ) ); + + XP_ASSERT( aGC != NULL ); + + if ( sctx != NULL ) { + XP_MEMSET( sctx, 0, sizeof( *sctx ) ); + MPASSIGN( sctx->mpool, mpool ); + sctx->iGC = aGC; + sctx->iCoeEnv = aCoeEnv; + sctx->iiEikonEnv = aEikonEnv; + + sctx->vtable = (DrawCtxVTable*)XP_MALLOC( mpool, sizeof(*sctx->vtable) ); + if ( sctx->vtable != NULL ) { + + SET_VTABLE_ENTRY( sctx->vtable, draw_destroyCtxt, sym ); + SET_VTABLE_ENTRY( sctx->vtable, draw_boardBegin, sym ); + SET_VTABLE_ENTRY( sctx->vtable, draw_boardFinished, sym ); +#ifdef DEBUG + /* Shouldn't get called as thing stand now */ + SET_VTABLE_ENTRY( sctx->vtable, draw_vertScrollBoard, sym ); +#endif + SET_VTABLE_ENTRY( sctx->vtable, draw_trayBegin, sym ); + SET_VTABLE_ENTRY( sctx->vtable, draw_trayFinished, sym ); + SET_VTABLE_ENTRY( sctx->vtable, draw_measureRemText, sym ); + SET_VTABLE_ENTRY( sctx->vtable, draw_drawRemText, sym ); + SET_VTABLE_ENTRY( sctx->vtable, draw_scoreBegin, sym ); + SET_VTABLE_ENTRY( sctx->vtable, draw_measureScoreText, sym ); + SET_VTABLE_ENTRY( sctx->vtable, draw_score_drawPlayer, sym ); + SET_VTABLE_ENTRY( sctx->vtable, draw_score_pendingScore, sym ); + SET_VTABLE_ENTRY( sctx->vtable, draw_scoreFinished, sym ); + SET_VTABLE_ENTRY( sctx->vtable, draw_drawTimer, sym ); + SET_VTABLE_ENTRY( sctx->vtable, draw_drawCell, sym ); + SET_VTABLE_ENTRY( sctx->vtable, draw_invertCell, sym ); + SET_VTABLE_ENTRY( sctx->vtable, draw_drawTile, sym ); + SET_VTABLE_ENTRY( sctx->vtable, draw_drawTileBack, sym ); + SET_VTABLE_ENTRY( sctx->vtable, draw_drawTrayDivider, sym ); + SET_VTABLE_ENTRY( sctx->vtable, draw_clearRect, sym ); + SET_VTABLE_ENTRY( sctx->vtable, draw_drawBoardArrow, sym ); +#ifdef KEY_SUPPORT + SET_VTABLE_ENTRY( sctx->vtable, draw_drawTrayCursor, sym ); + SET_VTABLE_ENTRY( sctx->vtable, draw_drawBoardCursor, sym ); +#endif + SET_VTABLE_ENTRY( sctx->vtable, draw_getMiniWText, sym ); + SET_VTABLE_ENTRY( sctx->vtable, draw_measureMiniWText, sym ); + SET_VTABLE_ENTRY( sctx->vtable, draw_drawMiniWindow, sym ); + SET_VTABLE_ENTRY( sctx->vtable, draw_eraseMiniWindow, sym ); + + sctx->colors[COLOR_BLACK] = KRgbBlack; + sctx->colors[COLOR_WHITE] = KRgbWhite; + sctx->colors[COLOR_CURSOR] = TRgb(0x0000FF); + sctx->colors[COLOR_TILE] = TRgb(0x80ffff); // light yellow + //sctx->colors[COLOR_TILE] = KRgbYellow; + + sctx->colors[COLOR_PLAYER1] = KRgbBlack; + sctx->colors[COLOR_PLAYER2] = KRgbDarkRed; + sctx->colors[COLOR_PLAYER4] = KRgbDarkBlue; + sctx->colors[COLOR_PLAYER3] = KRgbDarkGreen; + + sctx->colors[COLOR_DBL_LTTR] = KRgbYellow; + sctx->colors[COLOR_DBL_WORD] = KRgbBlue; + sctx->colors[COLOR_TRPL_LTTR] = KRgbMagenta; + sctx->colors[COLOR_TRPL_WORD] = KRgbCyan; + + figureFonts( sctx ); + + TFileName bitmapFile = aApp->BitmapStoreName(); + + XP_LOGF( "loading bitmaps0" ); + sctx->iDownArrow = new (ELeave) CFbsBitmap(); + User::LeaveIfError( sctx->iDownArrow-> + Load(bitmapFile, + BMNAME(EMbmXwords,Downarrow_80) ) ); + sctx->iRightArrow = new (ELeave) CFbsBitmap(); + User::LeaveIfError( sctx->iRightArrow-> + Load(bitmapFile, + BMNAME(EMbmXwords,Rightarrow_80) ) ); + sctx->iStar = new (ELeave) CFbsBitmap(); + User::LeaveIfError( sctx->iStar-> + Load(bitmapFile, + BMNAME(EMbmXwords,Star_80) ) ); + + sctx->iTurnIcon = new (ELeave) CFbsBitmap(); + User::LeaveIfError( sctx->iTurnIcon-> + Load(bitmapFile, + BMNAME(EMbmXwords,Turnicon_80) ) ); + sctx->iTurnIconMask = new (ELeave) CFbsBitmap(); + User::LeaveIfError( sctx->iTurnIconMask-> + Load(bitmapFile, + BMNAME(EMbmXwords,Turniconmask_80) ) ); + + sctx->iRobotIcon = new (ELeave) CFbsBitmap(); + User::LeaveIfError( sctx->iRobotIcon-> + Load(bitmapFile, + BMNAME(EMbmXwords,Robot_80) ) ); + sctx->iRobotIconMask = new (ELeave) CFbsBitmap(); + User::LeaveIfError( sctx->iRobotIconMask-> + Load(bitmapFile, + BMNAME(EMbmXwords,Robotmask_80) ) ); + + XP_LOGF( "done loading bitmaps" ); + } else { + XP_FREE( mpool, sctx ); + sctx = NULL; + } + } + + XP_LOGF( "leaving sym_drawctxt_make" ); + + return (DrawCtx*)sctx; +} diff --git a/xwords4/symbian/src/symgamdl.cpp b/xwords4/symbian/src/symgamdl.cpp new file mode 100644 index 000000000..f17301057 --- /dev/null +++ b/xwords4/symbian/src/symgamdl.cpp @@ -0,0 +1,435 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4;-*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#if defined SERIES_60 +# include "xwords_60.rsg" +#elif defined SERIES_80 +# include +# include "xwords_80.rsg" +#endif + +#include "symgamdl.h" +#include "symutil.h" +#include "xwords.hrh" + +/*************************************************************************** + * TGameInfoBuf + ***************************************************************************/ +TGameInfoBuf::TGameInfoBuf( const CurGameInfo* aGi, +#ifndef XWFEATURE_STANDALONE_ONLY + const CommsAddrRec* aCommsAddr, +#endif + CDesC16ArrayFlat* aDictList ) + : iDictList(aDictList), + iDictIndex(0) +{ + TInt i; + + for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { + iIsRobot[i] = aGi->players[i].isRobot; + iIsLocal[i] = aGi->players[i].isLocal; + if ( aGi->players[i].name != NULL ) { + XP_LOGF( "name[%d] = %s", i, aGi->players[i].name ); + TBuf8<32> tmp( aGi->players[i].name ); + iPlayerNames[i].Copy( tmp ); + } else { + iPlayerNames[i].Copy( _L("") ); + } + } + iNPlayers = aGi->nPlayers; + + if ( aGi->dictName != NULL ) { + TBuf8<32> tmp( aGi->dictName ); + TBuf16<32> dictName; + dictName.Copy( tmp ); + (void)iDictList->Find( dictName, iDictIndex ); /*iDictIndex ref passed*/ + } + +#ifndef XWFEATURE_STANDALONE_ONLY + iServerRole = aGi->serverRole; + iCommsAddr = *aCommsAddr; +#endif +} /* TGameInfoBuf::TGameInfoBuf */ + +void +TGameInfoBuf::CopyToL( MPFORMAL CurGameInfo* aGi +#ifndef XWFEATURE_STANDALONE_ONLY + , CommsAddrRec* aCommsAddr +#endif + ) +{ + TInt i; + for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { + aGi->players[i].isRobot = iIsRobot[i]; + aGi->players[i].isLocal = iIsLocal[i]; + + symReplaceStrIfDiff( MPPARM(mpool) &aGi->players[i].name, + iPlayerNames[i] ); + } + + aGi->nPlayers = SC( XP_U8, iNPlayers ); + + TPtrC16 dictName = (*iDictList)[iDictIndex]; + symReplaceStrIfDiff( MPPARM(mpool) &aGi->dictName, dictName ); + +#ifndef XWFEATURE_STANDALONE_ONLY + aGi->serverRole = iServerRole; + *aCommsAddr = iCommsAddr; +#endif +} + +/*************************************************************************** + * CXWGameInfoDlg + ***************************************************************************/ +CXWGameInfoDlg::CXWGameInfoDlg( MPFORMAL TGameInfoBuf* aGib, TBool aNewGame ) + : iIsNewGame(aNewGame), iGib(aGib) +{ +/* XP_LOGF( "CXWGameInfoDlg::CXWGameInfoDlg" ); */ + MPASSIGN( this->mpool, mpool ); +} + +CXWGameInfoDlg::~CXWGameInfoDlg() +{ +/* XP_LOGF( "CXWGameInfoDlg::~CXWGameInfoDlg" ); */ +} + +void +CXWGameInfoDlg::PreLayoutDynInitL() +{ +/* XP_LOGF( "CXWGameInfoDlg::PreLayoutDynInitL" ); */ +#if defined SERIES_80 + + /* This likely belongs in its own method */ +#ifndef XWFEATURE_STANDALONE_ONLY + const TInt deps[] = { + EConnectionRole + }; + TInt i; + for ( i = 0; i < sizeof(deps)/sizeof(deps[0]); ++i ) { + HandleControlStateChangeL( deps[i] ); + } +#endif + + CEikChoiceList* list; + list = static_cast(Control(ENPlayersList)); + XP_ASSERT( list != NULL ); + list->SetCurrentItem( iGib->iNPlayers - 1 ); + + list = static_cast(Control(ENPlayersWhichList)); + XP_ASSERT( list != NULL ); + list->SetArrayExternalOwnership( EFalse ); + list->SetArrayL( MakeNumListL( 1, iGib->iNPlayers ) ); + + list = static_cast(Control(ESelDictChoice)); + XP_ASSERT( list != NULL ); + list->SetArrayL( iGib->iDictList ); + list->SetCurrentItem( iGib->iDictIndex ); + + iCurPlayerShown = 0; + LoadPlayerInfo( iCurPlayerShown ); + +#ifndef XWFEATURE_STANDALONE_ONLY + list = static_cast(Control(EConnectionRole)); + XP_ASSERT( list != NULL ); + TInt sel = (TInt)iGib->iServerRole; + list->SetCurrentItem( sel ); + list->DrawDeferred(); + + list = static_cast(Control(EConnectionType)); + XP_ASSERT( list != NULL ); + sel = (TInt)(iGib->iCommsAddr.conType) - 2; /* first 2 unused */ + XP_ASSERT( sel >= 0 ); + list->SetCurrentItem( sel ); + list->DrawDeferred(); + + CEikEdwin* edwin = static_cast(Control(ECookie)); + TBuf16 cookieBuf; + cookieBuf.Copy( TBuf8 + (iGib->iCommsAddr.u.ip_relay.cookie) ); + edwin->SetTextL( &cookieBuf ); + edwin->DrawDeferred(); + + edwin = static_cast(Control(ERelayName)); + TBuf16 nameBuf; + nameBuf.Copy( TBuf8 + (iGib->iCommsAddr.u.ip_relay.hostName) ); + edwin->SetTextL( &nameBuf ); + edwin->DrawDeferred(); + + TInt num = iGib->iCommsAddr.u.ip_relay.port; + CEikNumberEditor* hostPort + = static_cast(Control(ERelayPort)); + hostPort->SetNumber( num ); + hostPort->DrawDeferred(); + + HideAndShow(); +#endif +#endif +} /* PreLayoutDynInitL */ + +void +CXWGameInfoDlg::HideAndShow() +{ +/* XP_LOGF( "HideAndShow" ); */ +#if defined SERIES_80 + /* if it's standalone, hide all else. Then if it's not IP, hide all + below. */ + +#ifndef XWFEATURE_STANDALONE_ONLY + CEikChoiceList* list; + TBool showConnect; + list = static_cast(Control(EConnectionRole)); + XP_ASSERT( list != NULL ); + showConnect = list->CurrentItem() != 0; + + TBool showIP = showConnect; + if ( showIP ) { + list = static_cast(Control(EConnectionType)); + XP_ASSERT( list != NULL ); + showIP = list->CurrentItem() == 0; + } + + MakeLineVisible( EConnectionType, showConnect ); + MakeLineVisible( ECookie, showIP ); + MakeLineVisible( ERelayName, showIP ); + MakeLineVisible( ERelayPort, showIP ); +#endif +#endif +} /* HideAndShow */ + +void +CXWGameInfoDlg::HandleControlStateChangeL( TInt aControlId ) +{ +/* XP_LOGF( "HandleControlStateChangeL got %d", aControlId ); */ +#if defined SERIES_80 + CEikChoiceList* list; + CEikChoiceList* whichList; + TInt item; + TBool show; + + switch ( aControlId ) { +#ifndef XWFEATURE_STANDALONE_ONLY + case EConnectionRole: + case EConnectionType: + HideAndShow(); + break; +#endif + case ENPlayersList: + /* The ENPlayersWhichList must match the number of players available, + and we need to display a different player if we're currently + showing one who no longer exists. */ + list = static_cast + (Control( ENPlayersList )); + item = list->CurrentItem(); + + whichList = static_cast + (Control( ENPlayersWhichList )); + whichList->SetArrayL( MakeNumListL( 1, item + 1 ) ); + + if ( item < iCurPlayerShown ) { + /* HandleControlStateChangeL seems not to get called for this + change. But the DrawDeferred()s below make it ok */ + whichList->SetCurrentItem( item ); + SetPlayerShown( item ); + } else { + whichList->SetCurrentItem( iCurPlayerShown ); + } + whichList->DrawDeferred(); + + break; + + case ENPlayersWhichList: + list = static_cast + (Control( ENPlayersWhichList )); + SetPlayerShown( list->CurrentItem() ); + HandleControlStateChangeL( EPlayerLocationChoice ); + break; + + case EPlayerLocationChoice: + list = static_cast + (Control(EPlayerLocationChoice )); + show = list->CurrentItem() == 0; + + MakeLineVisible( EPlayerName, show ); + MakeLineVisible( EPlayerSpeciesChoice, show ); + MakeLineVisible( EDecryptPassword, show ); + break; + + default: + break; + } +#endif +} + +TBool +CXWGameInfoDlg::OkToExitL( TInt /*aKeyCode*/ ) +{ +#if defined SERIES_80 + CEikChoiceList* list; + + /* Dictionary */ + list = static_cast(Control(ESelDictChoice)); + iGib->iDictIndex = list->CurrentItem(); + + /* Player data (all but displayed already saved) */ + SavePlayerInfo( iCurPlayerShown ); + + /* number of players */ + list = static_cast(Control(ENPlayersList)); + iGib->iNPlayers = list->CurrentItem() + 1; +#endif + +#ifndef XWFEATURE_STANDALONE_ONLY + list = static_cast(Control(EConnectionRole)); + TInt sel = list->CurrentItem(); + iGib->iServerRole = (Connectedness)sel; + + if ( iGib->iServerRole != SERVER_STANDALONE ) { + + list = static_cast(Control(EConnectionType)); + iGib->iCommsAddr.conType = SC(CommsConnType, list->CurrentItem() + 2 ); + + if ( iGib->iCommsAddr.conType == COMMS_CONN_RELAY ) { + /* cookie */ + CEikEdwin* edwin = static_cast(Control(ECookie)); + TBuf16 cookieBuf; + edwin->GetText( cookieBuf ); + TBuf8 buf8cookie; + buf8cookie.Copy( cookieBuf ); + TInt len = buf8cookie.Length(); + XP_MEMCPY( iGib->iCommsAddr.u.ip_relay.cookie, + (void*)buf8cookie.Ptr(), len ); + iGib->iCommsAddr.u.ip_relay.cookie[len] = '\0'; + + /* hostname */ + edwin = static_cast(Control(ERelayName)); + TBuf16 nameBuf; + edwin->GetText( nameBuf ); + TBuf8 buf8; + buf8.Copy( nameBuf ); + len = buf8.Length(); + XP_MEMCPY( iGib->iCommsAddr.u.ip_relay.hostName, + (void*)buf8.Ptr(), len ); + iGib->iCommsAddr.u.ip_relay.hostName[len] = '\0'; + + /* port */ + CEikNumberEditor* hostPort + = static_cast(Control(ERelayPort)); + iGib->iCommsAddr.u.ip_relay.port = SC( XP_U16, hostPort->Number() ); + } + } +#endif + return ETrue; +} /* OkToExitL */ + +void +CXWGameInfoDlg::LoadPlayerInfo( TInt aWhich ) +{ + /* location */ +#ifndef XWFEATURE_STANDALONE_ONLY +#endif + + /* name */ + XP_LOGF( "setting name" ); + CEikEdwin* nameEditor = static_cast(Control(EPlayerName)); + nameEditor->SetTextL( &iGib->iPlayerNames[aWhich] ); + nameEditor->DrawDeferred(); + + XP_LOGF( "done setting name" ); + +#if defined SERIES_80 + /* species */ + CEikChoiceList* list = static_cast + (Control(EPlayerSpeciesChoice )); + TInt oldVal = list->CurrentItem(); + TInt newVal = iGib->iIsRobot[aWhich]? 1:0; + if ( oldVal != newVal ) { + list->SetCurrentItem( newVal ); + list->DrawDeferred(); + } + + /* remoteness */ + list = static_cast(Control(EPlayerLocationChoice )); + oldVal = list->CurrentItem(); + newVal = iGib->iIsLocal[aWhich]? 0:1; + if ( oldVal != newVal ) { + list->SetCurrentItem( newVal ); + list->DrawDeferred(); + } +#endif + + /* password */ +} + +void +CXWGameInfoDlg::SavePlayerInfo( TInt aWhich ) +{ + + /* name */ + CEikEdwin* nameEditor = static_cast(Control(EPlayerName)); + nameEditor->GetText( iGib->iPlayerNames[aWhich] ); + + /* species */ +#if defined SERIES_80 + CEikChoiceList* list = static_cast + (Control(EPlayerSpeciesChoice )); + iGib->iIsRobot[aWhich] = (list->CurrentItem() == 1); + + list = static_cast(Control(EPlayerLocationChoice )); + iGib->iIsLocal[aWhich] = (list->CurrentItem() == 0); +#endif +} + +void +CXWGameInfoDlg::SetPlayerShown( TInt aPlayer ) +{ + if ( aPlayer != iCurPlayerShown ) { + SavePlayerInfo( iCurPlayerShown ); + LoadPlayerInfo( aPlayer ); + iCurPlayerShown = aPlayer; + } +} + +CDesC16ArrayFlat* +CXWGameInfoDlg::MakeNumListL( TInt aFirst, TInt aLast ) +{ + XP_ASSERT( aFirst <= aLast ); + + CDesC16ArrayFlat* list = new (ELeave)CDesC16ArrayFlat(aLast - aFirst + 1); + TInt i; + for ( i = aFirst; i <= aLast; ++i ) { + TBuf16<4> num; + num.Num( i ); + list->AppendL( num ); + } + + return list; +} /* MakeNumListL */ + +/* static*/ TBool +CXWGameInfoDlg::DoGameInfoDlgL( MPFORMAL TGameInfoBuf* aGib, TBool aNewGame ) +{ + XP_LOGF( "CXWGameInfoDlg::DoGameInfoDlgL called" ); + CXWGameInfoDlg* me = + new(ELeave)CXWGameInfoDlg( MPPARM(mpool) aGib, aNewGame ); + XP_LOGF( "calling ExecuteLD" ); + return me->ExecuteLD( R_XWORDS_NEWGAME_DLG ); +} diff --git a/xwords4/symbian/src/symgamed.cpp b/xwords4/symbian/src/symgamed.cpp new file mode 100755 index 000000000..5bf008578 --- /dev/null +++ b/xwords4/symbian/src/symgamed.cpp @@ -0,0 +1,71 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#if defined SERIES_80 +# include +# include "xwords_80.rsg" +#elif defined SERIES_60 +# include "xwords_60.rsg" +#endif +#include "xwords.hrh" +#include "symgamed.h" + + +CNameEditDlg::CNameEditDlg( TGameName* aGameName ) + : iGameName( aGameName ) +{ +} + +TBool +CNameEditDlg::OkToExitL( TInt aKeyCode ) +{ +#ifdef SERIES_80 + CEikFileNameEditor* ed; + ed = static_cast(Control(EEditNameEdwin)); + + if ( aKeyCode != EEikBidCancel ) { + TInt len = ed->TextLength(); + XP_ASSERT( len <= iGameName->MaxLength() ); + TGameName tmp; + ed->GetText( tmp ); + iGameName->Copy( tmp ); + } +#endif + return ETrue; +} // OkToExitL + +void +CNameEditDlg::PreLayoutDynInitL() +{ +#ifdef SERIES_80 + CEikFileNameEditor* ed; + ed = static_cast(Control(EEditNameEdwin)); + ed->SetTextL( iGameName ); + ed->SetTextLimit( iGameName->MaxLength() ); +#endif +} + +/* static */ TBool +CNameEditDlg::EditName( TGameName* aGameName ) +{ + CNameEditDlg* me = new CNameEditDlg( aGameName ); + User::LeaveIfNull( me ); + + return me->ExecuteLD( R_XWORDS_EDITNAME_DLG ); +} diff --git a/xwords4/symbian/src/symgmdlg.cpp b/xwords4/symbian/src/symgmdlg.cpp new file mode 100755 index 000000000..d4b674c7e --- /dev/null +++ b/xwords4/symbian/src/symgmdlg.cpp @@ -0,0 +1,181 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#if defined SERIES_60 +# include "xwords_60.rsg" +#elif defined SERIES_80 +# include +# include "xwords_80.rsg" +#endif +#include + +#include "symgmdlg.h" +#include "symutil.h" +#include "symgamed.h" +#include "xwords.hrh" +#include "xwappview.h" + + +CXSavedGamesDlg::CXSavedGamesDlg( MPFORMAL CXWordsAppView* aOwner, + CXWGamesMgr* aGameMgr, + const TGameName* aCurName, + TGameName* result ) + : iOwner(aOwner), iGameMgr(aGameMgr), iCurName(aCurName), iResultP(result) +{ + MPASSIGN( this->mpool, mpool ); +} + +void +CXSavedGamesDlg::PreLayoutDynInitL() +{ + ResetNames( -1, iCurName ); +} + +TBool +CXSavedGamesDlg::OkToExitL( TInt aKeyCode ) +{ +#if defined SERIES_60 + return ETrue; +#elif defined SERIES_80 + TBool canReturn = EFalse; + CEikTextListBox* box; + const CListBoxView::CSelectionIndexArray* indices; + box = static_cast(Control(ESelGameChoice)); + TInt index = box->CurrentItemIndex(); + TDesC16 selName = (*iGameMgr->GetNames())[index]; + + XP_LOGF( "CXSavedGamesDlg::OkToExitL(%d) called", aKeyCode ); + + switch( aKeyCode ) { + case XW_SGAMES_OPEN_COMMAND: + indices = box->SelectionIndexes(); + if ( indices->Count() > 1 ) { + XP_LOGF( "too many selected" ); + } else { + /* Don't use indices: invalid when multi-select isn't on? */ + // TInt index = indices->At(0); + XP_LOGF( "the %dth is selected:", index ); + *iResultP = (*iGameMgr->GetNames())[index]; + XP_LOGDESC16( iResultP ); + canReturn = ETrue; + } + break; + + case XW_SGAMES_RENAME_COMMAND: + if ( 0 == selName.Compare(*iCurName) ) { + iOwner->UserErrorFromID( R_ALERT_NO_RENAME_OPEN_GAME ); + } else { + TGameName newName; + if ( EditSelName( static_cast(&selName), &newName ) ) { + ResetNames( -1, &newName ); + box->DrawDeferred(); + } + } + break; + + case XW_SGAMES_DELETE_COMMAND: { + XP_LOGF( "delete" ); + if ( 0 == selName.Compare(*iCurName) ) { + iOwner->UserErrorFromID( R_ALERT_NO_DELETE_OPEN_GAME ); + } else if ( iOwner->UserQuery( (UtilQueryID)SYM_QUERY_CONFIRM_DELGAME, + NULL ) ) { + if ( iGameMgr->DeleteSelected( index ) ) { + ResetNames( index, NULL ); + box->DrawDeferred(); + } + } + } + break; + case EEikBidCancel: + canReturn = ETrue; + } + + return canReturn; +#endif +} /* OkToExitL */ + +void +CXSavedGamesDlg::ResetNames( TInt aPrefIndex, + const TGameName* aSelName ) +{ + /* PENDING aPrefIndex is a hint what to select next */ +#if defined SERIES_60 +#elif defined SERIES_80 + CDesC16Array* names = iGameMgr->GetNames(); + TInt index = 0; /* make compiler happy */ + const TGameName* seekName = NULL; + + if ( aPrefIndex >= 0 ) { + index = aPrefIndex; + } else if ( aSelName != NULL ) { + seekName = aSelName; + } else { + seekName = iCurName; + } + + if ( seekName != NULL && ( 0 != names->Find( *seekName, index ) ) ) { + XP_LOGF( "Unable to find" ); + XP_LOGDESC16( seekName ); + XP_ASSERT( 0 ); + index = 0; /* safe default if not found */ + } + + CEikTextListBox* box; + box = static_cast(Control(ESelGameChoice)); + + box->Model()->SetItemTextArray( names ); + box->Model()->SetOwnershipType( ELbmDoesNotOwnItemArray ); + box->HandleItemAdditionL(); + + box->SetCurrentItemIndex( index ); +#endif +} /* ResetNames */ + +TBool +CXSavedGamesDlg::EditSelName( const TGameName* aSelName, TGameName* aNewName ) +{ + aNewName->Copy( *aSelName ); + TBool renamed = EFalse; + + if ( CNameEditDlg::EditName( aNewName ) ) { + if ( iGameMgr->Exists( aNewName ) ) { + iOwner->UserErrorFromID( R_ALERT_RENAME_TARGET_EXISTS ); + } else if ( !iGameMgr->IsLegalName( aNewName ) ) { + iOwner->UserErrorFromID( R_ALERT_RENAME_TARGET_BADNAME ); + } else { + iGameMgr->Rename( aSelName, aNewName ); + renamed = ETrue; + } + } + return renamed; +} + +/* static */ TBool +CXSavedGamesDlg::DoGamesPicker( MPFORMAL CXWordsAppView* aOwner, + CXWGamesMgr* aGameMgr, + const TGameName* aCurName, TGameName* result ) +{ + CXSavedGamesDlg* me = new CXSavedGamesDlg( MPPARM(mpool) aOwner, + aGameMgr, aCurName, result ); + User::LeaveIfNull( me ); + + return me->ExecuteLD( R_XWORDS_SAVEDGAMES_DLG ); +} // DoGamesPicker diff --git a/xwords4/symbian/src/symgmmgr.cpp b/xwords4/symbian/src/symgmmgr.cpp new file mode 100755 index 000000000..318c3926f --- /dev/null +++ b/xwords4/symbian/src/symgmmgr.cpp @@ -0,0 +1,257 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "symgmmgr.h" + +extern "C" { +#include "xwstream.h" +} +#include "symutil.h" + +#if defined SERIES_60 +# include +#endif +#include + +_LIT( kGameTypeExt, ".xwg" ); + + +// private +CXWGamesMgr::CXWGamesMgr( MPFORMAL CCoeEnv* aCoeEnv, TFileName* aBasePath ) + : iCoeEnv(aCoeEnv), iGameCount(0) +{ + MPASSIGN( this->mpool, mpool ); + + iDir.Copy( *aBasePath ); + iDir.Append( _L("xwgames" ) ); + iDir.Append( _L("\\" ) ); +} // CXWGamesMgr + +/* static */ CXWGamesMgr* +CXWGamesMgr::NewL( MPFORMAL CCoeEnv* aCoeEnv, TFileName* aBasePath ) +{ + CXWGamesMgr* self = new CXWGamesMgr( MPPARM(mpool) aCoeEnv, aBasePath ); + User::LeaveIfNull( self ); + + // Create the games directory if it doesn't already exist +#if defined SERIES_80 + RFs fs = aCoeEnv->FsSession(); +#elif defined SERIES_60 + RFs fs = CEikonEnv::Static()->FsSession(); +#endif + TInt err = fs.MkDirAll( self->iDir ); + + if ( err != KErrNone && err != KErrAlreadyExists ) { + XP_LOGF( "MkDir failed: %d", err ); + User::Leave( err ); + } + + err = fs.SetAtt( self->iDir, KEntryAttHidden, KEntryAttNormal ); + + self->BuildListL(); + + return self; +} // NewL + +void +CXWGamesMgr::BuildListL() +{ + delete iNamesList; + + RFs fs = iCoeEnv->FsSession(); + + CDir* file_list; + TFindFile file_finder( fs ); + TBuf16<6> gameNamePat( _L("*") ); + gameNamePat.Append( kGameTypeExt ); + TInt err = file_finder.FindWildByDir( gameNamePat, iDir, file_list ); + // Leave it empty (null) if nothing in the list. + if ( err == KErrNone ) { + CleanupStack::PushL( file_list ); + + TInt gameCount = file_list->Count(); + iNamesList = new (ELeave)CDesC16ArrayFlat( gameCount ); + + TInt i; + for ( i = 0; i < file_list->Count(); i++ ) { + TParse fullentry; + fullentry.Set( (*file_list)[i].iName, &file_finder.File(), NULL ); + iNamesList->AppendL( fullentry.Name() ); + } + CleanupStack::PopAndDestroy(file_list); // file_list + } +} // BuildListL + +TInt +CXWGamesMgr::GetNGames() const +{ + if ( !iNamesList ) { + return 0; + } else { + return iNamesList->MdcaCount(); + } +} // GetNGames + +CDesC16Array* +CXWGamesMgr::GetNames() const +{ + return iNamesList; +} // GetNames + +TBool +CXWGamesMgr::Exists( TGameName* aName ) +{ + RFs fs = iCoeEnv->FsSession(); + + TFileName nameD; + GameNameToPath( &nameD, aName ); + + TUint attValue; + TInt err = fs.Att( nameD, attValue ); + XP_LOGF( "fs.Att(%S) => %d", aName, err ); + return err == KErrNone; +} + +TBool +CXWGamesMgr::IsLegalName( const TGameName* aName ) +{ + RFs fs = iCoeEnv->FsSession(); + return fs.IsValidName( *aName ); +} /* IsLegalName */ + +void +CXWGamesMgr::GameNameToPath( TFileName* aPath, const TDesC16* aName ) +{ + aPath->Copy( iDir ); + aPath->Append( *aName ); + aPath->Append( kGameTypeExt ); +} + +void +CXWGamesMgr::MakeDefaultName( TGameName* aName ) +{ + RFs fs = iCoeEnv->FsSession(); + TFileName nameD; + TGameName tmpName( _L("game") ); + GameNameToPath( &nameD, &tmpName ); + + TInt err = CApaApplication::GenerateFileName( fs, nameD ); + User::LeaveIfError( err ); + + TParse nameParser; + nameParser.Set( nameD, NULL, NULL ); + + aName->Copy( nameParser.Name() ); +} // MakeDefaultName + +void +CXWGamesMgr::StoreGameL( const TGameName* aName, /* does not have extension */ + XWStreamCtxt* aStream ) +{ + RFs fs = iCoeEnv->FsSession(); + + // write the stream to a file + TFileName nameD; + GameNameToPath( &nameD, aName ); + RFile file; + TInt err = file.Replace( fs, nameD, EFileWrite ); + XP_ASSERT( err == KErrNone ); + User::LeaveIfError( err ); + CleanupClosePushL(file); + + TInt siz = stream_getSize( aStream ); + TUint8* buf = new (ELeave) TUint8[siz]; + stream_getBytes( aStream, buf, SC(XP_U16, siz) ); + TPtrC8 tbuf( buf, siz ); + err = file.Write( tbuf, siz ); + XP_ASSERT( err == KErrNone ); + delete [] buf; + + CleanupStack::PopAndDestroy( &file ); // file + + BuildListL(); +} // StoreGameL + +void +CXWGamesMgr::LoadGameL( const TGameName* aName, XWStreamCtxt* aStream ) +{ + RFs fs = iCoeEnv->FsSession(); + + TFileName nameD; + GameNameToPath( &nameD, aName ); + + RFile file; + TInt err = file.Open( fs, nameD, EFileRead ); + if ( err != KErrNone ) { + XP_LOGF( "file.Open() => %d", err ); + } + User::LeaveIfError( err ); + CleanupClosePushL(file); + + for ( ; ; ) { + TBuf8<256> buf; + file.Read( buf, buf.MaxLength() ); + TInt nRead = buf.Size(); + if ( nRead <= 0 ) { + break; + } + stream_putBytes( aStream, (void*)buf.Ptr(), SC(XP_U16, nRead) ); + } + + CleanupStack::PopAndDestroy( &file ); +} // LoadGame + +TBool +CXWGamesMgr::DeleteSelected( TInt aIndex ) +{ + TPtrC16 name = (*iNamesList)[aIndex]; + return DeleteFileFor( &name ); +} /* DeleteSelected */ + +TBool +CXWGamesMgr::DeleteFileFor( TPtrC16* aName ) +{ + TFileName nameD; + GameNameToPath( &nameD, aName ); + + RFs fs = iCoeEnv->FsSession(); + TInt err = fs.Delete( nameD ); + TBool success = err == KErrNone; + if ( success ) { + BuildListL(); + } + return success; +} + +void +CXWGamesMgr::Rename( const TDesC16* aCurName, const TDesC16* aNewName ) +{ + TFileName newName; + GameNameToPath( &newName, aNewName ); + + TFileName anOldName; + GameNameToPath( &anOldName, aCurName ); + + RFs fs = iCoeEnv->FsSession(); + TInt err = fs.Rename( anOldName, newName ); + XP_ASSERT( err == KErrNone ); + User::LeaveIfError( err ); + + BuildListL(); +} /* Rename */ diff --git a/xwords4/symbian/src/symrsock.cpp b/xwords4/symbian/src/symrsock.cpp new file mode 100644 index 000000000..7cd459bdc --- /dev/null +++ b/xwords4/symbian/src/symrsock.cpp @@ -0,0 +1,144 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). (based on sample + * app helloworldbasic "Copyright (c) 2002, Nokia. All rights + * reserved.") + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef XWFEATURE_STANDALONE_ONLY + +#include "symrsock.h" + +/*static*/ CReadSocket* +CReadSocket::NewL( ReadNotifyCallback aCallback, void* aCallbackClosure ) +{ + CReadSocket* self = new (ELeave) CReadSocket( aCallback, aCallbackClosure ); + CleanupStack::PushL( self ); + self->ConstructL(); + CleanupStack::Pop( self ); + return self; +} + +CReadSocket::CReadSocket( ReadNotifyCallback aGotData, void* aCallbackClosure ) + : iGotData(aGotData) + , iCallbackClosure(aCallbackClosure) + , iReadState(ENotReading) + , iPort(0) + , CActive(EPriorityStandard) +{ + iInBuf.SetLength(0); +} + +CReadSocket::~CReadSocket() +{ + Cancel(); + iSocketServer.Close(); +} + +void +CReadSocket::ConstructL() +{ + CActiveScheduler::Add( this ); + TInt err = iSocketServer.Connect(); + XP_LOGF( "iSocketServer.Connect => %d", err ); + + err = iListenSocket.Open( iSocketServer, KAfInet, + KSockDatagram, KProtocolInetUdp ); + XP_LOGF( "iListenSocket.Open => %d", err ); + + if ( iPort != 0 ) { + Bind(); + } +} + +void +CReadSocket::Bind() +{ + XP_ASSERT( iPort != 0 ); + TSockAddr iSockAddress( KProtocolInetUdp ); + iSockAddress.SetPort( iPort ); + + TInt err = iListenSocket.Bind( iSockAddress ); + XP_LOGF( "iListenSocket.Bind => %d", err ); +} /* Bind */ + +void +CReadSocket::SetListenPort( TInt aPort ) +{ + XP_LOGF( "SetListenPort(%d)", aPort ); + if ( aPort != iPort ) { + TBool wasActive = iReadState == EReading && IsActive(); + if ( wasActive ) { + Cancel(); /* stop anything ongoing */ + } + iPort = aPort; + Bind(); + if ( wasActive ) { + RequestRecv(); + } + } +} /* SetListenPort */ + +void +CReadSocket::RequestRecv() +{ + XP_ASSERT( !IsActive() ); + + XP_LOGF( "calling iListenSocket.RecvFrom" ); + iListenSocket.RecvFrom( iInBuf, iRecvAddr, 0, iStatus ); + + iReadState = EReading; + SetActive(); +} /* RequestRead */ + +void +CReadSocket::RunL() +{ + XP_ASSERT( iStatus.Int() == KErrNone ); + XP_ASSERT( iReadState == EReading ); + if ( iStatus.Int() == KErrNone ) { + XP_LOGF( "SUCCESS: packet received!" ); + (*iGotData)( &iInBuf, iCallbackClosure ); + iInBuf.SetLength(0); + } + iReadState = ENotReading; + + RequestRecv(); /* We just keep listening... */ +} /* RunL */ + +void +CReadSocket::DoCancel() +{ + if ( iReadState == EReading ) { + iListenSocket.CancelRecv(); + } +} + +void +CReadSocket::Start() +{ + if ( !IsActive() ) { + RequestRecv(); + } +} + +void +CReadSocket::Stop() +{ + Cancel(); +} + +#endif diff --git a/xwords4/symbian/src/symssock.cpp b/xwords4/symbian/src/symssock.cpp new file mode 100644 index 000000000..6f1dd1429 --- /dev/null +++ b/xwords4/symbian/src/symssock.cpp @@ -0,0 +1,347 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). (based on sample + * app helloworldbasic "Copyright (c) 2002, Nokia. All rights + * reserved.") + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef XWFEATURE_STANDALONE_ONLY + +#include "symssock.h" +#include "symutil.h" + +void +CSendSocket::RunL() +{ + XP_LOGF( "CSendSocket::RunL called; iStatus=%d", iStatus.Int() ); +/* iSendTimer.Cancel(); */ + + TBool statusGood = iStatus.Int() == KErrNone; + switch ( iSSockState ) { + case ELookingUp: + iResolver.Close(); /* we probably won't need this again */ + if ( statusGood ) { + iNameRecord = iNameEntry(); + XP_LOGF( "name resolved" ); + ConnectL( TInetAddr::Cast(iNameRecord.iAddr).Address() ); + } else { + ResetState(); + } + break; + case EConnecting: + if ( statusGood ) { + iSSockState = EConnected; + XP_LOGF( "connect successful" ); + if ( iSendBuf.Length() > 0 ) { + DoActualSend(); + } else if ( iListenPending ) { + Listen(); + } + } else { + ResetState(); + } + break; + case ESending: + if ( statusGood ) { + XP_LOGF( "send successful" ); + } else { + /* Depending on error, might need to close socket, reconnect, etc. + For now we'll just trust the upper layers to figure out they + didn't get through, or user to resend. */ + XP_LOGF( "send failed with error %d", iStatus.Int() ); + } + + iSSockState = EConnected; + iSendBuf.SetLength(0); + + if ( iListenPending ) { + Listen(); + } + break; + + case EListening: + if ( statusGood ) { + if ( iDataLen == 0 ) { + /* Do nothing; we need to read again via Listen call */ + iDataLen = XP_NTOHS( *(XP_U16*)iInBufDesc->Ptr() ); + XP_LOGF( "Recv succeeded with length; now looking for %d byte" + "packet", iDataLen ); + } else { + iDataLen = 0; + XP_LOGF( "Got packet! Calling callback" ); + (*iCallback)( iInBufDesc, iClosure ); + } + iSSockState = EConnected; + Listen(); /* go back to listening */ + } else { + XP_LOGF( "listen failed with error %d", iStatus.Int() ); + ResetState(); + } + } +} /* RunL */ + +void +CSendSocket::ResetState() +{ + iSendBuf.SetLength(0); + iSSockState = ENotConnected; +} + +TBool +CSendSocket::Listen() +{ + XP_LOGF( "CSendSocket::Listen" ); + iListenPending = ETrue; + + if ( IsActive() ) { + /* since iListenPending is set, we'll eventually get to listening once + all the RunL()s get called. Do nothing. */ + } else { + if ( iSSockState == ENotConnected ) { + ConnectL(); + } else if ( iSSockState == EConnected ) { + delete iInBufDesc; + + TInt seekLen = iDataLen == 0? 2: iDataLen; + iInBufDesc = new TPtr8( iInBuf, seekLen ); + XP_LOGF( "calling iSendSocket.Recv; looking for %d bytes", + iInBufDesc->MaxSize() ); + iSendSocket.Recv( *iInBufDesc, 0, iStatus ); + + SetActive(); + iSSockState = EListening; + iListenPending = EFalse; + } else { + XP_LOGF( "iSSockState=%d", iSSockState ); + XP_ASSERT( 0 ); + } + } + return ETrue; +} /* Listen */ + +TBool +CSendSocket::CancelListen() +{ + TBool result = iSSockState == EListening; + if ( result ) { + XP_ASSERT( IsActive() ); + Cancel(); + } + return result; +} /* CancelListen */ + +void +CSendSocket::DoCancel() +{ + if ( iSSockState == ESending ) { + iSendSocket.CancelWrite(); + iSSockState = EConnected; + } else if ( iSSockState == EConnecting ) { + iSendSocket.CancelConnect(); + iSSockState = ENotConnected; + } else if ( iSSockState == ELookingUp ) { + iResolver.Cancel(); + iResolver.Close(); + } else if ( iSSockState == EListening ) { + iSendSocket.CancelRecv(); + } +} /* DoCancel */ + +CSendSocket::CSendSocket( ReadNotifyCallback aCallback, + void* aClosure ) + : CActive(EPriorityStandard) + ,iSSockState(ENotConnected) + ,iAddrSet( EFalse ) + ,iCallback(aCallback) + ,iClosure(aClosure) + ,iListenPending(EFalse) + ,iInBufDesc( NULL ) + ,iDataLen( 0 ) +{ +} + +CSendSocket::~CSendSocket() +{ + Cancel(); + iSocketServer.Close(); +} + +void +CSendSocket::ConstructL() +{ + CActiveScheduler::Add( this ); + XP_LOGF( "calling iSocketServer.Connect()" ); + TInt err = iSocketServer.Connect( 2 ); + XP_LOGF( "Connect=>%d", err ); + User::LeaveIfError( err ); +} + +/*static*/ CSendSocket* +CSendSocket::NewL( ReadNotifyCallback aCallback, void* aClosure ) +{ + CSendSocket* me = new CSendSocket( aCallback, aClosure ); + CleanupStack::PushL( me ); + me->ConstructL(); + CleanupStack::Pop( me ); + return me; +} + +void +CSendSocket::ConnectL( TUint32 aIpAddr ) +{ + XP_LOGF( "ConnectL( 0x%x )", aIpAddr ); + + TInt err = iSendSocket.Open( iSocketServer, KAfInet, + KSockStream, KProtocolInetTcp ); + XP_LOGF( "iSocket.Open => %d", err ); + User::LeaveIfError( err ); + + // Set up address information + iAddress.SetPort( iCurAddr.u.ip_relay.port ); + iAddress.SetAddress( aIpAddr ); + + // Initiate socket connection + XP_LOGF( "calling iSendSocket.Connect" ); + iSendSocket.Connect( iAddress, iStatus ); + + SetActive(); + iSSockState = EConnecting; + + // Start a timeout + /* iSendTimer.After( iConnectTimeout ); */ + +} /* ConnectL */ + +void +CSendSocket::ConnectL() +{ + XP_LOGF( "CSendSocket::ConnectL" ); + if ( iSSockState == ENotConnected ) { + TInetAddr ipAddr; + + if ( iCurAddr.u.ip_relay.hostName && iCurAddr.u.ip_relay.hostName[0] ) { + + XP_LOGF( "connecting to %s", iCurAddr.u.ip_relay.hostName ); + + TBuf16 tbuf; + tbuf.Copy( TBuf8(iCurAddr.u.ip_relay.hostName) ); + TInt err = ipAddr.Input( tbuf ); + XP_LOGF( "ipAddr.Input => %d", err ); + + if ( err != KErrNone ) { + /* need to look it up */ + err = iResolver.Open( iSocketServer, KAfInet, KProtocolInetUdp ); + XP_LOGF( "iResolver.Open => %d", err ); + User::LeaveIfError( err ); + + iResolver.GetByName( tbuf, iNameEntry, iStatus ); + iSSockState = ELookingUp; + + SetActive(); + } else { + ConnectL( ipAddr.Address() ); + } + } else { + /* PENDING FIX THIS!!!! */ + XP_LOGF( "Can't connect: no relay name" ); + } + } +} /* ConnectL */ + +void +CSendSocket::ConnectL( const CommsAddrRec* aAddr ) +{ + /* If we're connected and the address is the same, do nothing. Otherwise + disconnect, change the address, and reconnect. */ + + TBool sameAddr = iAddrSet && + (0 == XP_MEMCMP( (void*)&iCurAddr, (void*)aAddr, sizeof(aAddr) ) ); + + if ( sameAddr && iSSockState >= EConnected ) { + /* do nothing */ + } else { + Disconnect(); + + iCurAddr = *aAddr; + iAddrSet = ETrue; + + ConnectL(); + } +} /* ConnectL */ + +void +CSendSocket::Disconnect() +{ + XP_LOGF( "CSendSocket::Disconnect" ); + Cancel(); + if ( iSSockState >= EConnected ) { + iSendSocket.Close(); + } + iSSockState = ENotConnected; +} /* Disconnect */ + +TBool +CSendSocket::SendL( const XP_U8* aBuf, XP_U16 aLen, const CommsAddrRec* aAddr ) +{ + XP_LOGF( "CSendSocket::SendL called" ); + TBool success; + + XP_ASSERT( iSSockState == EListening || !IsActive() ); + if ( iSSockState == ESending ) { + success = EFalse; + } else if ( aLen > KMaxMsgLen ) { + success = EFalse; + } else if ( iSendBuf.Length() != 0 ) { + XP_LOGF( "old buffer not sent yet" ); + success = EFalse; + } else { + /* TCP-based protocol requires 16-bits of length, in network + byte-order, followed by data. */ + XP_U16 netLen = XP_HTONS( aLen ); + iSendBuf.Append( (TUint8*)&netLen, sizeof(netLen) ); + iSendBuf.Append( aBuf, aLen ); + + if ( iSSockState == ENotConnected ) { + ConnectL( aAddr ); + } else if ( iSSockState == EConnected || iSSockState == EListening ) { + DoActualSend(); + } else { + XP_ASSERT( 0 ); /* not sure why we'd be here */ + } + success = ETrue; + } + + return success; +} /* SendL */ + +void +CSendSocket::DoActualSend() +{ + XP_LOGF( "CSendSocket::DoActualSend called" ); + + if ( CancelListen() ) { + iListenPending = ETrue; + } + + iSendSocket.Write( iSendBuf, iStatus ); // Initiate actual write + SetActive(); + + // Request timeout +/* iSendTimer.After( iWriteTimeout ); */ + iSSockState = ESending; +} + +#endif diff --git a/xwords4/symbian/src/symutil.cpp b/xwords4/symbian/src/symutil.cpp new file mode 100644 index 000000000..c3bceb78b --- /dev/null +++ b/xwords4/symbian/src/symutil.cpp @@ -0,0 +1,213 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4;-*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include "comtypes.h" +#include "mempool.h" + + +void +symReplaceStrIfDiff( MPFORMAL XP_UCHAR** loc, const TDesC16& desc ) +{ + TBuf8<256> tmp; + tmp.Copy( desc ); + + if ( *loc ) { + TPtrC8 forCmp; + forCmp.Set( *loc, XP_STRLEN( *loc ) ); + + if ( tmp == forCmp ) { + return; + } + + XP_FREE( mpool, *loc ); + } + + TInt len = desc.Length(); + XP_UCHAR* newStr = (XP_UCHAR*)XP_MALLOC( mpool, len + 1 ); + XP_MEMCPY( newStr, (void*)tmp.Ptr(), len ); + newStr[len] = '\0'; + *loc = newStr; +} /* symReplaceStr */ + +void +symReplaceStrIfDiff( MPFORMAL XP_UCHAR** loc, const XP_UCHAR* str ) +{ + if ( *loc != NULL ) { + if ( 0 == XP_STRCMP( *loc, str ) ) { + return; /* nothing to do */ + } + /* need to free */ + XP_FREE( mpool, *loc ); + } + + TInt len = XP_STRLEN( str ) + 1; + *loc = (XP_UCHAR*)XP_MALLOC( mpool, len ); + XP_MEMCPY( (void*)*loc, (void*)str, len ); +} /* symReplaceStrIfDiff */ + +#ifdef DEBUG +_LIT( KXWLogdir, "xwords" ); +_LIT( KXWLogfile, "xwdebug.txt" ); +/* The emulator, anyway, doesn't do descs well even with the %S directive. + So convert 'em to desc8s... */ +void +XP_LOGDESC16( const TDesC16* desc ) +{ + TBuf8<256> buf8; + buf8.Copy( *desc ); + buf8.Append( (const unsigned char*)"\0", 1 ); + TBuf8<256> fmtDesc((unsigned char*)"des16: %s"); + + RFileLogger::WriteFormat( KXWLogdir, KXWLogfile, + EFileLoggingModeAppend, + fmtDesc, buf8.Ptr() ); +} +#endif + +extern "C" { + +int +sym_snprintf( XP_UCHAR* aBuf, XP_U16 aLen, const XP_UCHAR* aFmt, ... ) +{ + __e32_va_list ap; + va_start( ap, aFmt ); + + int result = vsprintf( (char*)aBuf, (const char*)aFmt, ap ); + XP_ASSERT( XP_STRLEN(aBuf) < aLen ); // this may not work.... + va_end(ap); + return result; +} + +#ifdef DEBUG +void sym_debugf( char* aFmt, ... ) +{ + VA_LIST ap; + VA_START( ap, aFmt ); + + TBuf8<256> fmtDesc((unsigned char*)aFmt); + + RFileLogger::WriteFormat( KXWLogdir, KXWLogfile, + EFileLoggingModeAppend, + fmtDesc, ap ); + VA_END(ap); +} + +#else + +void p_ignore( char* , ...) {} + +#endif + + +void* +sym_malloc(XP_U32 nbytes ) +{ + return malloc( nbytes ); +} + +void* sym_realloc(void* p, XP_U32 nbytes) +{ + return realloc( p, nbytes ); +} + +void +sym_free( void* p ) +{ + free( p ); +} + +void +sym_assert( XP_Bool b, XP_U32 line, const char* file ) +{ + if ( !b ) { + XP_LOGF( "ASSERTION FAILED: line %d, file %s", + line, file ); + } +} + +void +sym_memcpy( void* dest, const void* src, XP_U32 nbytes ) +{ + memcpy( dest, src, nbytes ); +} + +XP_U32 +sym_strlen( XP_UCHAR* str ) +{ + return strlen( (const char*)str ); +} + +XP_S16 +sym_strncmp( XP_UCHAR* str1, XP_UCHAR* str2, XP_U32 len ) +{ + return (XP_S16)strncmp( (const char*)str1, (const char*)str2, len ); +} + +void +sym_memset( void* dest, XP_UCHAR val, XP_U32 nBytes ) +{ + memset( dest, val, nBytes ); +} + +XP_S16 +sym_strcmp( XP_UCHAR* str1, XP_UCHAR* str2 ) +{ + return (XP_S16)strcmp( (const char*)str1, (const char*)str2 ); +} + +char* +sym_strcat( XP_UCHAR* dest, const XP_UCHAR* src ) +{ + return strcat( (char*)dest, (const char*) src ); +} + +XP_S16 +sym_memcmp( void* m1, void* m2, XP_U32 nbytes ) +{ + return (XP_S16)memcmp( m1, m2, nbytes ); +} + +XP_U32 +sym_flip_long( XP_U32 l ) +{ + XP_U32 result = + ((l & 0x000000FF) << 24) | + ((l & 0x0000FF00) << 8) | + ((l & 0x00FF0000) >> 8) | + ((l & 0xFF000000) >> 24); + return result; +} + +XP_U16 +sym_flip_short(XP_U16 s) +{ + XP_U16 result = + ((s & 0x00FF) << 8) | + ((s & 0xFF00) >> 8); + + return result; +} + +} // extern "C" diff --git a/xwords4/symbian/src/xwapp.cpp b/xwords4/symbian/src/xwapp.cpp new file mode 100644 index 000000000..3d8da2bff --- /dev/null +++ b/xwords4/symbian/src/xwapp.cpp @@ -0,0 +1,44 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). (based on sample + * app helloworldbasic "Copyright (c) 2002, Nokia. All rights + * reserved.") + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "xwdoc.h" +#include "xwapp.h" +#include "xwords.hrh" + +// UID for the application, this should correspond to the uid defined in the +// mmp file. (This is an official number from Symbian. I've been allocated a +// block of 10 of which this is the first.) +static const TUid KUidXWordsApp = {XW_UID3}; + +CApaDocument* +CXWordsApplication::CreateDocumentL() +{ + // Create an HelloWorldBasic document, and return a pointer to it + CApaDocument* document = CXWordsDocument::NewL(*this); + return document; +} + +TUid +CXWordsApplication::AppDllUid() const +{ + + return KUidXWordsApp; +} diff --git a/xwords4/symbian/src/xwappui.cpp b/xwords4/symbian/src/xwappui.cpp new file mode 100644 index 000000000..16a75375b --- /dev/null +++ b/xwords4/symbian/src/xwappui.cpp @@ -0,0 +1,98 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). (based on sample + * app helloworldbasic "Copyright (c) 2002, Nokia. All rights + * reserved.") + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#if defined SERIES_60 +# include +# include +#elif defined SERIES_80 +# include +# include +# include +#endif + +#include "xwords.pan" +#include "xwappui.h" +#include "xwappview.h" +#include "xwords.hrh" +#include "symutil.h" + +// ConstructL is called by the application framework +void CXWordsAppUi::ConstructL() +{ + BaseConstructL(); + + iAppView = CXWordsAppView::NewL( ClientRect(), Application() ); + + AddToStackL(iAppView); +} + +CXWordsAppUi::CXWordsAppUi() +{ +} + +CXWordsAppUi::~CXWordsAppUi() +{ + if ( iAppView ) { + iEikonEnv->RemoveFromStack(iAppView); + delete iAppView; + iAppView = NULL; + } + + /* Somebody on NewLC says this is required to clean up TLS allocated + via use of stdlib calls */ + CloseSTDLIB(); +} + +// handle any menu commands +void CXWordsAppUi::HandleCommandL(TInt aCommand) +{ + switch(aCommand) { + + // built-in commands here + case EEikCmdExit: + iAppView->Exiting(); + CBaActiveScheduler::Exit(); + break; + + // added commands here + default: + if ( iAppView->HandleCommand( aCommand ) == 0 ) { + Panic(EXWordsUi); + } + break; + } +} // HandleCommandL + +TKeyResponse +CXWordsAppUi::HandleKeyEventL( const TKeyEvent& aKeyEvent, + TEventCode aType ) +{ + if ( aType == EEventKey ) { + XP_LOGF( "got iScanCode: %d (%c)", aKeyEvent.iScanCode, + aKeyEvent.iScanCode ); + if ( iAppView->HandleKeyEvent( aKeyEvent ) ) { + return EKeyWasConsumed; + } + } + + return EKeyWasNotConsumed; +} /* HandleKeyEventL */ diff --git a/xwords4/symbian/src/xwappview.cpp b/xwords4/symbian/src/xwappview.cpp new file mode 100644 index 000000000..f8ce8d46f --- /dev/null +++ b/xwords4/symbian/src/xwappview.cpp @@ -0,0 +1,1375 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). (based on sample + * app helloworldbasic "Copyright (c) 2002, Nokia. All rights + * reserved.") + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#if defined SERIES_60 +# include +# include +# include "xwords_60.rsg" +#elif defined SERIES_80 +# include +# include +# include +# include +# include "xwords_80.rsg" +#endif + +#include +#include // for srand +#include +#include + +#include "xwappview.h" +#include "xwappui.h" +#include "xwords.hrh" +#include "comtypes.h" +#include "symdraw.h" +#include "symaskdlg.h" +#include "symdict.h" +#include "symgamdl.h" +#include "symblnk.h" +#include "symgmdlg.h" +#include "symutil.h" +#include "symssock.h" + +#include "LocalizedStrIncludes.h" + +// Standard construction sequence +CXWordsAppView* CXWordsAppView::NewL(const TRect& aRect, CEikApplication* aApp ) +{ + CXWordsAppView* self = CXWordsAppView::NewLC( aRect, aApp ); + CleanupStack::Pop(self); + return self; +} + +CXWordsAppView* CXWordsAppView::NewLC(const TRect& aRect, CEikApplication* aApp ) +{ + CXWordsAppView* self = new (ELeave) CXWordsAppView( aApp ); + CleanupStack::PushL(self); + self->ConstructL(aRect); + return self; +} + +CXWordsAppView::CXWordsAppView( CEikApplication* aApp ) + : iApp( aApp ) + , iHeartbeatCB( HeartbeatTimerCallback, this ) + , iHeartbeatDTE(iHeartbeatCB) + , iHBQueued(XP_FALSE) +{ +#ifdef DEBUG + TInt processHandleCount, threadHandleCount; + RThread thread; + thread.HandleCount( processHandleCount, threadHandleCount ); + XP_LOGF( "startup: processHandleCount: %d; threadHandleCount: %d", + processHandleCount, threadHandleCount ); +#endif + + iDraw = NULL; + XP_MEMSET( &iGame, 0, sizeof(iGame) ); + + iCurGameName.Copy( _L("") ); + + /* CBase derivitaves are zero'd out, they say */ + XP_ASSERT( iTimerReasons[0] == 0 && iTimerReasons[1] == 0 ); + +#ifndef XWFEATURE_STANDALONE_ONLY + comms_getInitialAddr( &iCommsAddr ); +#endif +} + +CXWordsAppView::~CXWordsAppView() +{ + DeleteGame(); + + if ( iDraw ) { + draw_destroyCtxt( iDraw ); + } + + XP_FREE( mpool, iUtil.vtable ); + vtmgr_destroy( MPPARM(mpool) iVtMgr ); + + mpool_destroy( mpool ); + + delete iDictList; + delete iRequestTimer; + delete iGamesMgr; +#ifndef XWFEATURE_STANDALONE_ONLY + delete iSendSock; + delete iNewPacketQueue; +#endif + +#ifdef DEBUG + TInt processHandleCount, threadHandleCount; + RThread thread; + thread.HandleCount( processHandleCount, threadHandleCount ); + XP_LOGF( "shutdown: processHandleCount: %d; threadHandleCount: %d", + processHandleCount, threadHandleCount ); +#endif +} + +void CXWordsAppView::ConstructL(const TRect& aRect) +{ + // Create a window for this application view + CreateWindowL(); + + // Set the windows size + SetRect(aRect); + + // Indicate that the control is blank + SetBlank(); + + iRequestTimer = CIdle::NewL( CActive::EPriorityIdle ); + iTimerRunCount = 0; + iDeltaTimer = CDeltaTimer::NewL( CActive::EPriorityStandard ); + + +#ifndef XWFEATURE_STANDALONE_ONLY + iSendSock = CSendSocket::NewL( PacketReceived, (void*)this ); + XP_LOGF( "iSendSock created" ); + iNewPacketQueue = new (ELeave)CDesC8ArrayFlat(2); +#endif + + // Set the control's border +// SetBorder(TGulBorder::EFlatContainer); + + // Set the correct application view (Note: + // ESkinAppViewWithCbaNoToolband is the default) + +#if defined SERIES_80 + //CknEnv::Skin().SetAppViewType(ESkinAppViewWithCbaNoToolband); + CknEnv::Skin().SetAppViewType(ESkinAppViewNoCbaNoToolband); +#endif + + // This doesn't do what I want +// SetExtentToWholeScreen(); + + // Activate the window, which makes it ready to be drawn + ActivateL(); + + // Now the xwords-specific stuff + XP_LOGF( "starting xwords initialization" ); + + iStartTime.HomeTime(); + TDateTime tdtime = iStartTime.DateTime(); + // seed random with current microseconds, or so.... whatever. + TInt seed = (((tdtime.Minute() * 60) + tdtime.Second()) * 1000 ) + + tdtime.MicroSecond(); + XP_LOGF( "seeding srand with %d", seed ); + srand( seed ); + +#ifdef MEM_DEBUG + mpool = mpool_make(); +#endif + + iVtMgr = make_vtablemgr( MPPARM_NOCOMMA(mpool) ); + User::LeaveIfNull( iVtMgr ); + + iUtil.vtable = (UtilVtable*)XP_MALLOC( mpool, sizeof( UtilVtable ) ); + User::LeaveIfNull( iUtil.vtable ); + SetUpUtil(); + + TFileName basePath; + GetXwordsRWDir( &basePath, EGamesLoc ); + iGamesMgr = CXWGamesMgr::NewL( MPPARM(mpool) iCoeEnv, &basePath ); + + iDraw = sym_drawctxt_make( MPPARM(mpool) &SystemGc(), iCoeEnv, + iEikonEnv, iApp ); + User::LeaveIfNull( iDraw ); + + if ( !FindAllDicts() ) { + UserErrorFromID( R_ALERT_NO_DICTS ); + User::Leave( -1 ); + } + + if ( !LoadPrefs() ) { + InitPrefs(); + } + MakeOrLoadGameL(); + + PositionBoard(); + (void)server_do( iGame.server ); // get tiles assigned etc. +} // ConstructL + +// Draw this application's view to the screen +void CXWordsAppView::Draw( const TRect& aRect ) const +{ + // Get the standard graphics context + CWindowGc& gc = SystemGc(); + gc.SetClippingRect( aRect ); + gc.Clear( aRect ); + + if ( iGame.board ) { + XP_Rect rect; + + rect.left = SC( XP_U16, aRect.iTl.iX ); + rect.top = SC( XP_U16, aRect.iTl.iY ); + rect.width = SC( XP_U16, aRect.Width() ); + rect.height = SC( XP_U16, aRect.Height() ); + + board_invalRect( iGame.board, &rect ); + board_draw( iGame.board ); + } + + DrawGameName(); +} // Draw + +/* static */ VTableMgr* +CXWordsAppView::sym_util_getVTManager( XW_UtilCtxt* uc ) +{ + CXWordsAppView* self = (CXWordsAppView*)uc->closure; + return self->iVtMgr; +} /* sym_util_getVTManager */ + +#ifndef XWFEATURE_STANDALONE_ONLY +/*static*/ XWStreamCtxt* +CXWordsAppView::sym_util_makeStreamFromAddr( XW_UtilCtxt* uc, + XP_U16 channelNo ) +{ + XP_LOGF( "sym_util_makeStreamFromAddr called" ); + CXWordsAppView* self = (CXWordsAppView*)uc->closure; + XP_ASSERT( self->iGame.comms ); + return self->MakeSimpleStream( self->sym_send_on_close, + channelNo ); +} +#endif + +#define EM BONUS_NONE +#define DL BONUS_DOUBLE_LETTER +#define DW BONUS_DOUBLE_WORD +#define TL BONUS_TRIPLE_LETTER +#define TW BONUS_TRIPLE_WORD + +static XWBonusType +sym_util_getSquareBonus( XW_UtilCtxt* /*uc*/, ModelCtxt* /*model*/, + XP_U16 col, XP_U16 row ) +{ + XP_U16 index; + XWBonusType result; + const XP_U16 fourteen = 14; + + const char defaultBoard[8*8] = { + TW,EM,EM,DL,EM,EM,EM,TW, + EM,DW,EM,EM,EM,TL,EM,EM, + + EM,EM,DW,EM,EM,EM,DL,EM, + DL,EM,EM,DW,EM,EM,EM,DL, + + EM,EM,EM,EM,DW,EM,EM,EM, + EM,TL,EM,EM,EM,TL,EM,EM, + + EM,EM,DL,EM,EM,EM,DL,EM, + TW,EM,EM,DL,EM,EM,EM,DW, + }; /* defaultBoard */ + + if ( col > 7 ) col = SC(XP_U16, fourteen - col); + if ( row > 7 ) row = SC(XP_U16, fourteen - row); + index = SC(XP_U16,(row*8) + col); + if ( index >= 8*8 ) { + result = (XWBonusType)EM; + } else { + result = (XWBonusType)defaultBoard[index]; + } + return result; +} // sym_util_getSquareBonus + +/* static */ void +CXWordsAppView::sym_util_userError( XW_UtilCtxt* uc, UtilErrID id ) +{ + TInt resourceId = 0; + + switch( id ) { + case ERR_TILES_NOT_IN_LINE: + resourceId = R_TILES_IN_LINE_ALERT; + break; + case ERR_NO_EMPTIES_IN_TURN: + resourceId = R_NO_EMPTIES_SEP_ALERT; + break; + case ERR_TWO_TILES_FIRST_MOVE: + resourceId = R_TWO_TILES_FIRST_MOVE_ALERT; + break; + case ERR_TILES_MUST_CONTACT: + resourceId = R_PLACED_MUST_CONTACT_ALERT; + break; + case ERR_TOO_FEW_TILES_LEFT_TO_TRADE: + resourceId = R_TOO_FEW_TO_TRADE_ALERT; + break; + case ERR_NOT_YOUR_TURN: + resourceId = R_NOT_YOUR_TURN_ALERT; + break; + case ERR_NO_PEEK_ROBOT_TILES: + resourceId = R_NO_PEEK_ALERT; + break; +#ifndef XWFEATURE_STANDALONE_ONLY + case ERR_SERVER_DICT_WINS: + case ERR_NO_PEEK_REMOTE_TILES: + case ERR_REG_UNEXPECTED_USER: +#endif + case ERR_CANT_TRADE_MID_MOVE: + resourceId = R_REMOVE_FIRST_ALERT; + break; + case ERR_CANT_UNDO_TILEASSIGN: + resourceId = R_NOTHING_TO_UNDO_ALERT; + break; + default: + break; + } + + CXWordsAppView* self = (CXWordsAppView*)uc->closure; + self->UserErrorFromID( resourceId ); +} // sym_util_userError + +static XP_Bool +sym_util_userQuery( XW_UtilCtxt* uc, UtilQueryID aId, + XWStreamCtxt* aStream ) +{ + CXWordsAppView* self = (CXWordsAppView*)uc->closure; + return self->UserQuery( aId, aStream ); +} /* sym_util_userQuery */ + +static XP_S16 +sym_util_userPickTile( XW_UtilCtxt* /*uc*/, const PickInfo* /*pi*/, + XP_U16 /*playerNum*/, + const XP_UCHAR4* texts, XP_U16 nTiles ) +{ + TInt result = 0; + TRAPD( error, CXWBlankSelDlg::UsePickTileDialogL( texts, nTiles, &result ) ); + XP_ASSERT( result >= 0 && result < nTiles ); + return static_cast(result); +} /* sym_util_userPickTile */ + +static XP_Bool +sym_util_askPassword( XW_UtilCtxt* /*uc*/, const XP_UCHAR* /*name*/, + XP_UCHAR* /*buf*/, XP_U16* /*len*/ ) +{ + return XP_TRUE; +} + +static void +sym_util_trayHiddenChange(XW_UtilCtxt* /*uc*/, XW_TrayVisState /*newState*/, + XP_U16 /*nVisibleRows*/) +{ +} + +static void +sym_util_yOffsetChange(XW_UtilCtxt* /*uc*/, XP_U16 /*oldOffset*/, + XP_U16 /*newOffset*/ ) +{ +} + +/* static */ void +CXWordsAppView::sym_util_notifyGameOverL( XW_UtilCtxt* uc ) +{ + CXWordsAppView* self = (CXWordsAppView*)uc->closure; + self->DrawNow(); + self->DisplayFinalScoresL(); +} + +static XP_Bool +sym_util_hiliteCell( XW_UtilCtxt* /*uc*/, XP_U16 /*col*/, XP_U16 /*row*/ ) +{ + return XP_TRUE; // don't exit early +} + +static XP_Bool +sym_util_engineProgressCallback( XW_UtilCtxt* /*uc*/ ) +{ +#ifdef SHOW_PROGRESS +#endif + return XP_TRUE; +} + +/*static*/ TInt +CXWordsAppView::HeartbeatTimerCallback( TAny* closure ) +{ +#ifdef BEYOND_IR + CXWordsAppView* self = (CXWordsAppView*)closure; + self->iHBQueued = XP_FALSE; + + TimerProc proc; + void* hbclosure; + self->GetHeartbeatCB( &proc, &hbclosure ); + (*proc)( hbclosure, TIMER_HEARTBEAT ); +#endif + return 0; +} + +/* static */ void +CXWordsAppView::sym_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why, + XP_U16 when, + TimerProc proc, void* closure ) +{ + CXWordsAppView* self = (CXWordsAppView*)uc->closure; + + self->SetHeartbeatCB( proc, closure ); + + if ( self->iHBQueued ) { + self->iDeltaTimer->Remove( self->iHeartbeatDTE ); + self->iHBQueued = XP_FALSE; + } + + self->iDeltaTimer->Queue( when * 1000000, self->iHeartbeatDTE ); + self->iHBQueued = XP_TRUE; +} + +/* static */ void +CXWordsAppView::sym_util_requestTime( XW_UtilCtxt* uc ) +{ + CXWordsAppView* self = (CXWordsAppView*)uc->closure; + self->StartIdleTimer( EUtilRequest ); +} + +/* static */ XP_U32 +CXWordsAppView::sym_util_getCurSeconds( XW_UtilCtxt* uc ) +{ + CXWordsAppView* self = (CXWordsAppView*)uc->closure; + TTime currentTime; + currentTime.HomeTime(); + + TTimeIntervalSeconds interval; + (void)currentTime.SecondsFrom( self->iStartTime, interval ); + + return (XP_U32)interval.Int(); +} + +/* static */ DictionaryCtxt* +CXWordsAppView::sym_util_makeEmptyDict( XW_UtilCtxt* uc ) +{ + CXWordsAppView* self = (CXWordsAppView*)uc->closure; + + DictionaryCtxt* dict = sym_dictionary_makeL( MPPARM(self->mpool) + NULL, NULL ); + return dict; +} + +static XP_UCHAR* +sym_util_getUserString( XW_UtilCtxt* /*uc*/, XP_U16 stringCode ) +{ + // These belong in resources. But I haven't yet figured out how + // to do 8-bit strings in resources. Also, StringLoader does + // allocations when loading strings rather than just returning + // pointers into the resources as palm does. So this method will + // have to provide permanent storage for the strings, probably via + // a string table that's filled in on demand. + + switch( stringCode ) { + case STRD_REMAINING_TILES_ADD: + return (XP_UCHAR*)"+ %d [all remaining tiles]"; + case STRD_UNUSED_TILES_SUB: + return (XP_UCHAR*)"- %d [unused tiles]"; + case STR_BONUS_ALL: + return (XP_UCHAR*)"Bonus for using all tiles: 50" XP_CR; + case STRD_TURN_SCORE: + return (XP_UCHAR*)"Score for turn: %d" XP_CR; + case STR_COMMIT_CONFIRM: + return (XP_UCHAR*)"Commit the current move?" XP_CR; + case STR_LOCAL_NAME: + return (XP_UCHAR*)"%s"; + case STR_NONLOCAL_NAME: + return (XP_UCHAR*)"%s (remote)"; + case STRD_TIME_PENALTY_SUB: + return (XP_UCHAR*)" - %d [time]"; + + case STRD_CUMULATIVE_SCORE: + return (XP_UCHAR*)"Cumulative score: %d" XP_CR; + case STRS_MOVE_ACROSS: + return (XP_UCHAR*)"move (from %s across)" XP_CR; + case STRS_MOVE_DOWN: + return (XP_UCHAR*)"move (from %s down)" XP_CR; + case STRS_TRAY_AT_START: + return (XP_UCHAR*)"Tray at start: %s" XP_CR; + + case STRS_NEW_TILES: + return (XP_UCHAR*)"New tiles: %s" XP_CR; + case STRSS_TRADED_FOR: + return (XP_UCHAR*)"Traded %s for %s."; + case STR_PASS: + return (XP_UCHAR*)"pass" XP_CR; + case STR_PHONY_REJECTED: + return (XP_UCHAR*)"Illegal word in move; turn lost!" XP_CR; + + case STRD_ROBOT_TRADED: + return (XP_UCHAR*)"Robot traded tiles %d this turn."; + case STR_ROBOT_MOVED: + return (XP_UCHAR*)"The robot made this move:" XP_CR; + case STR_REMOTE_MOVED: + return (XP_UCHAR*)"Remote player made this move:" XP_CR; + + case STR_PASSED: + return (XP_UCHAR*)"Passed"; + case STRSD_SUMMARYSCORED: + return (XP_UCHAR*)"%s:%d"; + case STRD_TRADED: + return (XP_UCHAR*)"Traded %d"; + case STR_LOSTTURN: + return (XP_UCHAR*)"Lost turn"; + + case STRS_VALUES_HEADER: + return (XP_UCHAR*)"%s counts/values:" XP_CR; + + default: + XP_LOGF( "stringCode=%d", stringCode ); + return (XP_UCHAR*)"unknown code"; + } +} // sym_util_getUserString + +static XP_Bool +sym_util_warnIllegalWord( XW_UtilCtxt* /*uc*/, BadWordInfo* /*bwi*/, + XP_U16 /*turn*/, XP_Bool /*turnLost*/ ) +{ + return XP_FALSE; +} + +#ifdef BEYOND_IR +/*static*/void +CXWordsAppView::sym_util_listenPortChange( XW_UtilCtxt* uc, XP_U16 aPort ) +{ +/* CXWordsAppView* self = (CXWordsAppView*)uc->closure; */ +// self->iReadSock->SetListenPort( aPort ); +/* self->iSendSock->Listen(); */ +} + +/*static*/void +CXWordsAppView::sym_util_addrChange( XW_UtilCtxt* uc, + const CommsAddrRec* aOld, + const CommsAddrRec* aNew ) +{ + CXWordsAppView* self = (CXWordsAppView*)uc->closure; + XP_LOGF( "util_addrChange: calling connect" ); + self->iSendSock->ConnectL( aNew ); + (void)self->iSendSock->Listen(); +} +#endif + +#ifdef XWFEATURE_SEARCHLIMIT +static XP_Bool +sym_util_getTraySearchLimits(XW_UtilCtxt* /*uc*/, XP_U16* /*min*/, XP_U16* /*max*/ ) +{ + return XP_FALSE; +} +#endif + + +void +CXWordsAppView::SetUpUtil() +{ + UtilVtable* vtable = iUtil.vtable; + iUtil.closure = (void*)this; + iUtil.gameInfo = &iGi; + MPASSIGN( iUtil.mpool, mpool ); + + vtable->m_util_getVTManager = sym_util_getVTManager; +#ifndef XWFEATURE_STANDALONE_ONLY + vtable->m_util_makeStreamFromAddr = sym_util_makeStreamFromAddr; +#endif + vtable->m_util_getSquareBonus = sym_util_getSquareBonus; + vtable->m_util_userError = sym_util_userError; + vtable->m_util_userQuery = sym_util_userQuery; + vtable->m_util_userPickTile = sym_util_userPickTile; + vtable->m_util_askPassword = sym_util_askPassword; + vtable->m_util_trayHiddenChange = sym_util_trayHiddenChange; + vtable->m_util_yOffsetChange = sym_util_yOffsetChange; + vtable->m_util_notifyGameOver = sym_util_notifyGameOverL; + vtable->m_util_hiliteCell = sym_util_hiliteCell; + vtable->m_util_engineProgressCallback = sym_util_engineProgressCallback; + vtable->m_util_setTimer = sym_util_setTimer; + vtable->m_util_requestTime = sym_util_requestTime; + vtable->m_util_getCurSeconds = sym_util_getCurSeconds; + vtable->m_util_makeEmptyDict = sym_util_makeEmptyDict; + vtable->m_util_getUserString = sym_util_getUserString; + vtable->m_util_warnIllegalWord = sym_util_warnIllegalWord; +#ifdef BEYOND_IR + vtable->m_util_addrChange = sym_util_addrChange; +#endif +#ifdef XWFEATURE_SEARCHLIMIT + vtable->m_util_getTraySearchLimits = sym_util_getTraySearchLimits; +#endif +} + +// Load the current game from prefs or else create a new one. +// Eventually this will involve putting up the new game dialog. +void +CXWordsAppView::MakeOrLoadGameL() +{ + if ( iCurGameName.Length() > 0 && iGamesMgr->Exists( &iCurGameName ) ) { + LoadOneGameL( &iCurGameName ); + } else { + + /*************************************************************** + * PENDING The code here duplicates DoNewGame. Unify the two!!!! + ***************************************************************/ + + gi_initPlayerInfo( MPPARM(mpool) &iGi, (XP_UCHAR*)"Player %d" ); + +#ifndef XWFEATURE_STANDALONE_ONLY + CommsAddrRec commsAddr; + if ( iGame.comms != NULL ) { + comms_getAddr( iGame.comms, &commsAddr ); + } else { + commsAddr = iCommsAddr; + } +#endif + + TGameInfoBuf gib( &iGi, +#ifndef XWFEATURE_STANDALONE_ONLY + &commsAddr, +#endif + iDictList ); + if ( !CXWGameInfoDlg::DoGameInfoDlgL( MPPARM(mpool) &gib, ETrue ) ) { + User::Leave(-1); + } + + gib.CopyToL( MPPARM(mpool) &iGi +#ifndef XWFEATURE_STANDALONE_ONLY + , &iCommsAddr +#endif + ); + + TFileName path; + GetXwordsRWDir( &path, EDictsLoc ); + DictionaryCtxt* dict = sym_dictionary_makeL( MPPARM(mpool) + &path, iGi.dictName ); + User::LeaveIfNull( dict ); + + XP_U16 newGameID = SC( XP_U16, sym_util_getCurSeconds( &iUtil ) ); + game_makeNewGame( MPPARM(mpool) &iGame, &iGi, + &iUtil, iDraw, newGameID, &iCp, + SYM_SEND, this ); + model_setDictionary( iGame.model, dict ); + +#ifndef XWFEATURE_STANDALONE_ONLY + if ( iGame.comms ) { + comms_setAddr( iGame.comms, &iCommsAddr ); + if ( iGi.serverRole == SERVER_ISCLIENT ) { + XWStreamCtxt* stream = MakeSimpleStream( sym_send_on_close ); + server_initClientConnection( iGame.server, stream ); + } + } +#endif + + iGamesMgr->MakeDefaultName( &iCurGameName ); + StoreOneGameL( &iCurGameName ); + } +} /* MakeOrLoadGameL */ + +void +CXWordsAppView::DeleteGame() +{ + game_dispose( &iGame ); + gi_disposePlayerInfo( MPPARM(mpool) &iGi ); +} + +void +CXWordsAppView::PositionBoard() +{ + const TRect rect = Rect(); + const XP_U16 boardWidth = 15 * scaleBoardH; + const XP_U16 scoreTop = 25; + const XP_U16 scoreHt = 120; + + board_setPos( iGame.board, 2, 2, XP_FALSE ); + board_setScale( iGame.board, scaleBoardH, scaleBoardV ); + + XP_U16 scoreLeft = 2 + boardWidth + 2 + 3; /* 2 for border, 3 for gap */ + XP_U16 scoreRight = SC(XP_U16, rect.iBr.iX - 2 - 1); /* 2 for border */ + board_setScoreboardLoc( iGame.board, scoreLeft, scoreTop, + SC( XP_U16, scoreRight - scoreLeft - 1), + scoreHt, XP_FALSE ); + board_setYOffset( iGame.board, 0 ); + + board_setTrayLoc( iGame.board, + 2 + (15 * scaleBoardH) + 5, // to right of board + // make tray bottom same as board's + 2 + (15*scaleBoardV) - scaleTrayV, + scaleTrayH, scaleTrayV, // v and h scale + 3 ); // divider width + board_invalAll( iGame.board ); + + iTitleBox.SetRect( scoreLeft, 2, scoreRight, scoreTop - 4 ); +} // PositionBoard + +int +CXWordsAppView::HandleCommand( TInt aCommand ) +{ + XP_Bool draw = XP_FALSE; + XP_Bool notDone; + + switch ( aCommand ) { + + case XW_NEWGAME_COMMAND: + draw = DoNewGame(); + break; + + case XW_SAVEDGAMES_COMMAND: + draw = DoSavedGames(); + (void)server_do( iGame.server ); + break; + case XW_PREFS_COMMAND: + case XW_ABOUT_COMMAND: + NotImpl(); + break; + + case XW_VALUES_COMMAND: + if ( iGame.server != NULL ) { + XWStreamCtxt* stream = MakeSimpleStream( NULL ); + if ( stream != NULL ) { + server_formatDictCounts( iGame.server, stream, 7 ); /* 4: ncols */ + CXWAskDlg::DoInfoDlg(MPPARM(mpool) stream, ETrue); + } + } + break; + + case XW_REMAIN_COMMAND: + if ( iGame.board != NULL ) { + XWStreamCtxt* stream = MakeSimpleStream( NULL ); + if ( stream ) { + board_formatRemainingTiles( iGame.board, stream ); + CXWAskDlg::DoInfoDlg(MPPARM(mpool) stream, ETrue); + } + } + break; + + case XW_CURINFO_COMMAND: { + NotImpl(); +/* TGameInfoBuf gib( &iGi, iDictList ); */ +/* CXWGameInfoDlg::DoGameInfoDlgL( MPPARM(mpool) &gib, EFalse ); */ + } + break; + + case XW_HISTORY_COMMAND: + if ( iGame.server ) { + XP_Bool gameOver = server_getGameIsOver( iGame.server ); + XWStreamCtxt* stream = MakeSimpleStream( NULL ); + + model_writeGameHistory( iGame.model, stream, + iGame.server, gameOver ); + if ( stream_getSize( stream ) > 0 ) { + CXWAskDlg::DoInfoDlg( MPPARM(mpool) stream, ETrue ); + } + } + + break; + + case XW_FINALSCORES_COMMAND: + if ( server_getGameIsOver( iGame.server ) ) { + DisplayFinalScoresL(); + } else if ( AskFromResId( R_CONFIRM_END_GAME ) ) { + server_endGame( iGame.server ); + draw = ETrue; + } + break; + + case XW_HINT_COMMAND: +#ifdef XWFEATURE_SEARCHLIMIT + case XW_LIMHINT_COMMAND: +#endif + break; + case XW_NEXTHINT_COMMAND: + draw = board_requestHint( iGame.board, +#ifdef XWFEATURE_SEARCHLIMIT + XP_FALSE, +#endif + + ¬Done ); + break; + case XW_UNDOCUR_COMMAND: + draw = board_replaceTiles( iGame.board ); + break; + case XW_UNDOLAST_COMMAND: + draw = server_handleUndo( iGame.server ); + break; + case XW_DONE_COMMAND: + draw = board_commitTurn( iGame.board ); + break; + case XW_JUGGLE_COMMAND: + draw = board_juggleTray( iGame.board ); + break; + + case XW_TRADE_COMMAND: + draw = board_beginTrade( iGame.board ); + break; + + case XW_HIDETRAY_COMMAND: + if ( TRAY_REVEALED == board_getTrayVisState( iGame.board ) ) { + draw = board_hideTray( iGame.board ); + } else { + draw = board_showTray( iGame.board ); + } + break; + + case XW_FLIP_COMMAND: + draw = board_flip( iGame.board ); + break; + case XW_TOGGLEVALS_COMMAND: + draw = board_toggle_showValues( iGame.board ); + break; + +#ifndef XWFEATURE_STANDALONE_ONLY + case XW_RESEND_COMMAND: + if ( iGame.comms != NULL ) { + (void)comms_resendAll( iGame.comms ); + } +#endif + + default: + return 0; + } + + if ( draw ) { + DoImmediateDraw(); + } + + return 1; +} // CXWordsAppView::HandleCommand + +void +CXWordsAppView::Exiting() +{ + StoreOneGameL( &iCurGameName ); + WritePrefs(); +} /* Exiting */ + +TBool +CXWordsAppView::HandleKeyEvent( const TKeyEvent& aKeyEvent ) +{ + XP_Bool draw = XP_FALSE; + XP_Key key = XP_KEY_NONE; + TChar chr(aKeyEvent.iCode); + + switch ( chr ) { + + case EKeyTab: // 2 // this is probably for laptop only! :-) + key = XP_FOCUSCHANGE_KEY; + break; + + case EKeyEnter: + key = XP_RETURN_KEY; + break; + case EKeyBackspace: + key = XP_CURSOR_KEY_DEL; + break; + case EKeyLeftArrow: // 14 + key = XP_CURSOR_KEY_LEFT; + break; + case EKeyRightArrow: // 15 + key = XP_CURSOR_KEY_RIGHT; + break; + case EKeyUpArrow: // 16 + key = XP_CURSOR_KEY_UP; + break; + case EKeyDownArrow: // 17 + key = XP_CURSOR_KEY_DOWN; + break; + + default: + key = (XP_Key)aKeyEvent.iScanCode; + if ( key < 0x20 || key > 0x7f ) { + key = XP_KEY_NONE; + } + break; + } + + if ( iGame.board != NULL ) { + if ( key != XP_KEY_NONE ) { + draw = board_handleKey( iGame.board, key ); + } + } + + if ( draw ) { + DoImmediateDraw(); + } + + // handled if it's one we recognize. This is probably too broad!!! + return key != XP_KEY_NONE; +} + +/* static */ TInt +CXWordsAppView::TimerCallback( TAny* aPtr ) +{ + CXWordsAppView* me = (CXWordsAppView*)aPtr; + + if ( 0 ) { + +#ifndef XWFEATURE_STANDALONE_ONLY + /* Only do one per call. Packets are higher priority */ + } else if ( me->iTimerReasons[EProcessPacket] > 0 ) { + --me->iTimerReasons[EProcessPacket]; + XP_ASSERT( me->iTimerReasons[EProcessPacket] == 0 ); + + XP_Bool draw = XP_FALSE; + + XWStreamCtxt* stream = me->MakeSimpleStream( NULL ); + + TPtrC8 packet = (*me->iNewPacketQueue)[0]; + stream_putBytes( stream, (void*)packet.Ptr(), packet.Length() ); + me->iNewPacketQueue->Delete(0); + XP_LOGF( "pulling packet off head of queue; there are %d left", + me->iNewPacketQueue->Count() ); + + CommsAddrRec addr; + addr.conType = COMMS_CONN_RELAY; + if ( comms_checkIncomingStream( me->iGame.comms, stream, &addr ) ) { + draw = server_receiveMessage( me->iGame.server, stream ); + } + stream_destroy( stream ); + sym_util_requestTime( &me->iUtil ); + + if ( draw ) { + me->DoImmediateDraw(); + } +#endif + } else if ( me->iTimerReasons[EUtilRequest] > 0 ) { + --me->iTimerReasons[EUtilRequest]; + if ( server_do( me->iGame.server ) ) { + me->DoImmediateDraw(); + } + } + + return --me->iTimerRunCount > 0; +} + +XWStreamCtxt* +CXWordsAppView::MakeSimpleStream( MemStreamCloseCallback cb, + XP_U16 channelNo ) +{ + return mem_stream_make( MPPARM(mpool) + iVtMgr, (void*)this, + channelNo, cb ); +} /* MakeSimpleStream */ + +void +CXWordsAppView::DisplayFinalScoresL() +{ + XWStreamCtxt* stream = MakeSimpleStream( NULL ); + User::LeaveIfNull( stream ); + + server_writeFinalScores( iGame.server, stream ); + + CXWAskDlg::DoInfoDlg( MPPARM(mpool) stream, ETrue ); +} /* displayFinalScores */ + +TBool +CXWordsAppView::AskFromResId( TInt aResource ) +{ + TBuf16<128> message; + StringLoader::Load( message, aResource ); + + return CXWAskDlg::DoAskDlg( MPPARM(mpool) &message ); +} + +static void +logOneFile( TPtrC name ) +{ + TBuf8<128> tmpb; + tmpb.Copy( name ); + XP_UCHAR buf[128]; + XP_MEMCPY( buf, (void*)(tmpb.Ptr()), tmpb.Length() ); + buf[tmpb.Length()] = '\0'; + XP_LOGF( "found file %s", buf ); +} /* logOneFile */ + +TBool +CXWordsAppView::FindAllDicts() +{ + /* NOTE: CEikFileNameSelector might be the way to do this and the display + * of the list in the game setup dialog. + */ + TBool found = EFalse; + RFs fs = iCoeEnv->FsSession(); + + TFindFile file_finder( fs ); + CDir* file_list; + _LIT( wildName, "*.xwd" ); + + TFileName dir; + GetXwordsRWDir( &dir, EDictsLoc ); + + TInt err = file_finder.FindWildByDir( wildName, dir, file_list ); + if ( err == KErrNone ) { + + CleanupStack::PushL( file_list ); + iDictList = new (ELeave)CDesC16ArrayFlat( file_list->Count() ); + + TInt i; + for ( i = 0; i < file_list->Count(); i++ ) { + TParse fullentry; + fullentry.Set((*file_list)[i].iName,& file_finder.File(),NULL); + logOneFile( fullentry.Name() ); + iDictList->AppendL( fullentry.Name() ); + } + + CleanupStack::PopAndDestroy( file_list ); + found = ETrue; + } + + return found; +} /* FindAllDicts */ + +void +CXWordsAppView::UserErrorFromID( TInt aResource ) +{ + if ( aResource != 0 ) { + _LIT(title,"Oops"); + TBuf16<128> message; + StringLoader::Load( message, aResource ); +#if defined SERIES_60 + CEikInfoDialog::RunDlgLD( title, message ); +#elif defined SERIES_80 + CCknInfoDialog::RunDlgLD( title, message ); +#endif + } +} + +void +CXWordsAppView::NotImpl() +{ + UserErrorFromID( R_ALERT_FEATURE_PENDING ); +} /* NotImpl */ + +void +CXWordsAppView::GetXwordsRWDir( TFileName* aPathRef, TDriveReason aWhy ) +{ + TFileName fn = iApp->BitmapStoreName(); /* isn't the a method to just get + the path? */ + TParse nameParser; + nameParser.Set( fn, NULL, NULL ); + TPtrC path = nameParser.DriveAndPath(); + + switch( aWhy ) { + case EGamesLoc: + case EDictsLoc: + aPathRef->Copy( nameParser.DriveAndPath() ); + break; + case EPrefsLoc: + aPathRef->Copy( nameParser.Path() ); + break; /* don't want a drive */ + } +} /* GetXwordsRWDir */ + +_LIT(filename,"xwdata.dat"); +TBool +CXWordsAppView::LoadPrefs() +{ + RFs fs = iCoeEnv->FsSession(); + + TFileName nameD; + GetXwordsRWDir( &nameD, EPrefsLoc ); + nameD.Append( filename ); + + /* Read in prefs etc. */ + RFileReadStream reader; + CleanupClosePushL(reader); + TInt err = reader.Open( fs, nameD, EFileRead ); + + TBool found = err == KErrNone; + if ( found ) { + iCp.showBoardArrow = reader.ReadInt8L(); + iCp.showRobotScores = reader.ReadInt8L(); + reader >> iCurGameName; + } + CleanupStack::PopAndDestroy( &reader ); // reader + + return found; +} /* LoadPrefs */ + +void +CXWordsAppView::WritePrefs() +{ + RFs fs = iCoeEnv->FsSession(); + + TFileName nameD; + GetXwordsRWDir( &nameD, EPrefsLoc ); + nameD.Append( filename ); + + RFileWriteStream writer; + CleanupClosePushL(writer); + User::LeaveIfError( writer.Replace( fs, nameD, EFileWrite ) ); + + writer.WriteInt8L( iCp.showBoardArrow ); + writer.WriteInt8L( iCp.showRobotScores ); + + writer << iCurGameName; + + CleanupStack::PopAndDestroy( &writer ); // writer +} /* WritePrefs */ + +void +CXWordsAppView::InitPrefs() +{ + iCp.showBoardArrow = XP_TRUE; // default because no pen + iCp.showRobotScores = XP_FALSE; + +#ifndef XWFEATURE_STANDALONE_ONLY + iGi.serverRole = SERVER_STANDALONE; +#endif +} + +TBool +CXWordsAppView::DoNewGame() +{ + TBool draw = EFalse; + + TBool save = AskSaveGame(); + +#ifndef XWFEATURE_STANDALONE_ONLY + CommsAddrRec commsAddr; + if ( iGame.comms != NULL ) { + comms_getAddr( iGame.comms, &commsAddr ); + } else { + commsAddr = iCommsAddr; + } +#endif + + TGameInfoBuf gib( &iGi, +#ifndef XWFEATURE_STANDALONE_ONLY + &commsAddr, +#endif + iDictList ); + if ( CXWGameInfoDlg::DoGameInfoDlgL( MPPARM(mpool) &gib, ETrue ) ) { + + if ( save ) { + StoreOneGameL( &iCurGameName ); + iGamesMgr->MakeDefaultName( &iCurGameName ); + } + + gib.CopyToL( MPPARM(mpool) &iGi +#ifndef XWFEATURE_STANDALONE_ONLY + , &iCommsAddr +#endif + ); + XP_U16 newGameID = SC( XP_U16,sym_util_getCurSeconds( &iUtil ) ); + game_reset( MPPARM(mpool) &iGame, &iGi, &iUtil, newGameID, + &iCp, SYM_SEND, this ); + + DictionaryCtxt* prevDict = model_getDictionary( iGame.model ); + if ( 0 != XP_STRCMP( dict_getName(prevDict), iGi.dictName ) ) { + dict_destroy( prevDict ); + + TFileName path; + GetXwordsRWDir( &path, EDictsLoc ); + DictionaryCtxt* dict = sym_dictionary_makeL( MPPARM(mpool) + &path, iGi.dictName ); + model_setDictionary( iGame.model, dict ); + } +#ifndef XWFEATURE_STANDALONE_ONLY + if ( iGame.comms ) { + comms_setAddr( iGame.comms, &iCommsAddr ); + + if ( iGi.serverRole == SERVER_ISCLIENT ) { + XWStreamCtxt* stream = MakeSimpleStream( sym_send_on_close ); + // server deletes stream + server_initClientConnection( iGame.server, stream ); + } + } +#endif + + board_invalAll( iGame.board ); + (void)server_do( iGame.server ); // get tiles assigned etc. + draw = ETrue; + } + return draw; +} /* DoNewGame */ + +TBool +CXWordsAppView::DoSavedGames() +{ + StoreOneGameL( &iCurGameName ); + + TGameName openName; + TBool confirmed = CXSavedGamesDlg::DoGamesPicker( MPPARM(mpool) + this, + iGamesMgr, + &iCurGameName, + &openName ); + if ( confirmed ) { + if ( 0 != iCurGameName.Compare( openName ) ) { + + iCurGameName = openName; + + game_dispose( &iGame ); + gi_disposePlayerInfo( MPPARM(mpool) &iGi ); + + LoadOneGameL( &iCurGameName ); + Window().Invalidate( iTitleBox ); + } + } + return confirmed; +} /* DoSavedGames */ + +void +CXWordsAppView::LoadOneGameL( TGameName* aGameName ) +{ + XWStreamCtxt* stream = MakeSimpleStream( NULL ); + DictionaryCtxt* dict; + + iGamesMgr->LoadGameL( aGameName, stream ); + + XP_U16 len = stream_getU8( stream ); + XP_UCHAR dictName[32]; + stream_getBytes( stream, dictName, len ); + dictName[len] = '\0'; + + TFileName path; + GetXwordsRWDir( &path, EDictsLoc ); + dict = sym_dictionary_makeL( MPPARM(mpool) &path, dictName ); + XP_ASSERT( !!dict ); + + game_makeFromStream( MPPARM(mpool) stream, &iGame, + &iGi, dict, &iUtil, iDraw, &iCp, + SYM_SEND, this ); + stream_destroy( stream ); + + PositionBoard(); +} + +void +CXWordsAppView::StoreOneGameL( TGameName* aGameName ) +{ + XWStreamCtxt* stream = MakeSimpleStream( NULL ); + + /* save the dict. NOTE: should be possible to do away with this! */ + XP_U32 len = XP_STRLEN(iGi.dictName); + XP_ASSERT( len <= 0xFF ); + stream_putU8( stream, SC(XP_U8, len) ); + stream_putBytes( stream, iGi.dictName, SC(XP_U16, len) ); + + /* Now the game */ + game_saveToStream( &iGame, &iGi, stream ); + + iGamesMgr->StoreGameL( aGameName, stream ); + stream_destroy( stream ); +} + +XP_Bool +CXWordsAppView::UserQuery( UtilQueryID aId, XWStreamCtxt* aStream ) +{ + TInt resID = 0; + + switch ( aId ) { + case QUERY_ROBOT_MOVE: + case QUERY_ROBOT_TRADE: + case QUERY_COMMIT_TURN: + XP_ASSERT( aStream ); + return CXWAskDlg::DoAskDlg( MPPARM(mpool) aStream, EFalse ); + case QUERY_COMMIT_TRADE: + XP_ASSERT( !aStream ); + resID = R_CONFIRM_TRADE; + break; + case SYM_QUERY_CONFIRM_DELGAME: + XP_ASSERT( !aStream ); + resID = R_CONFIRM_DELGAME; + break; + } + if ( resID != 0 ) { + return AskFromResId( resID ); + } + XP_ASSERT( 0 ); + return XP_FALSE; +} /* sym_util_userQuery */ + +void +CXWordsAppView::DoImmediateDraw() +{ + ActivateGc(); + + XP_ASSERT( iGame.board != NULL ); + board_draw( iGame.board ); + + DeactivateGc(); +} /* DoImmediateDraw */ + +void +CXWordsAppView::DrawGameName() const +{ + if ( iGi.dictName != NULL ) { + TBuf16<66> buf; + buf.Copy( TBuf8<32>(iGi.dictName) ); + buf.Insert( 0, _L("::") ); + buf.Insert( 0, iCurGameName ); + + CWindowGc& gc = SystemGc(); + gc.SetPenStyle( CGraphicsContext::ESolidPen ); + gc.SetPenColor( KRgbBlack ); + gc.SetBrushColor( KRgbGray ); + gc.SetBrushStyle( CGraphicsContext::ESolidBrush ); + + gc.UseFont( iCoeEnv->NormalFont() ); + gc.DrawText( buf, iTitleBox, iTitleBox.Height() - 4,/*TInt aBaselineOffset*/ + CGraphicsContext::ECenter ); + gc.DiscardFont(); + } +} + +#ifndef XWFEATURE_STANDALONE_ONLY + +/*static*/ void +CXWordsAppView::PacketReceived( const TDesC8* aBuf, void* aClosure ) +{ + CXWordsAppView* me = (CXWordsAppView*)aClosure; + + TInt count = me->iNewPacketQueue->Count(); + me->iNewPacketQueue->InsertL( count, *aBuf ); + XP_LOGF( "inserted packet: now number %d", count ); + + me->StartIdleTimer( EProcessPacket ); +} /* CXWordsAppView::PacketReceived */ + +/*static*/ XP_S16 +CXWordsAppView::sym_send( XP_U8* aBuf, XP_U16 aLen, const CommsAddrRec* aAddr, + void* aClosure ) +{ + CommsAddrRec addr; + XP_S16 result = -1; + CXWordsAppView* self = (CXWordsAppView*)aClosure; + + if ( aAddr == NULL ) { + comms_getAddr( self->iGame.comms, &addr ); + aAddr = &addr; + } + + if ( self->iSendSock->SendL( aBuf, aLen, aAddr ) ) { + result = aLen; + } + + /* Can't call listen until we've sent something.... */ + self->iSendSock->Listen(); + + return result; +} /* sym_send */ + +/*static*/ void +CXWordsAppView::sym_send_on_close( XWStreamCtxt* aStream, void* aClosure ) +{ + XP_LOGF( "sym_send_on_close called" ); + CXWordsAppView* self = (CXWordsAppView*)aClosure; + + comms_send( self->iGame.comms, aStream ); +} +#endif + +void +CXWordsAppView::StartIdleTimer( XWTimerReason_symb aWhy ) +{ + ++iTimerReasons[aWhy]; + + if ( ++iTimerRunCount == 1 ) { + iRequestTimer->Start( TCallBack( CXWordsAppView::TimerCallback, + (TAny*)this ) ); + } +} diff --git a/xwords4/symbian/src/xwdoc.cpp b/xwords4/symbian/src/xwdoc.cpp new file mode 100644 index 000000000..a0a922936 --- /dev/null +++ b/xwords4/symbian/src/xwdoc.cpp @@ -0,0 +1,63 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). (based on sample + * app helloworldbasic "Copyright (c) 2002, Nokia. All rights + * reserved.") + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "xwappui.h" +#include "xwdoc.h" + +// Standard Symbian OS construction sequence +CXWordsDocument* CXWordsDocument::NewL(CEikApplication& aApp) + { + CXWordsDocument* self = NewLC(aApp); + CleanupStack::Pop(self); + return self; + } + +CXWordsDocument* CXWordsDocument::NewLC(CEikApplication& aApp) + { + CXWordsDocument* self = new (ELeave) CXWordsDocument(aApp); + CleanupStack::PushL(self); + self->ConstructL(); + return self; + } + +void CXWordsDocument::ConstructL() + { + // no implementation required + } + +CXWordsDocument::CXWordsDocument(CEikApplication& aApp) : CEikDocument(aApp) + { + // no implementation required + } + +CXWordsDocument::~CXWordsDocument() + { + // no implementation required + } + +CEikAppUi* CXWordsDocument::CreateAppUiL() + { + // Create the application user interface, and return a pointer to it, + // the framework takes ownership of this object + CEikAppUi* appUi = new (ELeave) CXWordsAppUi; + return appUi; + } + diff --git a/xwords4/symbian/src/xwmain.cpp b/xwords4/symbian/src/xwmain.cpp new file mode 100644 index 000000000..dceb06735 --- /dev/null +++ b/xwords4/symbian/src/xwmain.cpp @@ -0,0 +1,36 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). (based on sample + * app helloworldbasic "Copyright (c) 2002, Nokia. All rights + * reserved.") + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "xwapp.h" + +// DLL entry point, return that everything is ok +GLDEF_C TInt E32Dll(TDllReason /*aReason*/) +{ + return KErrNone; +} + + +// Create an application, and return a pointer to it +EXPORT_C CApaApplication* NewApplication() +{ + return (new CXWordsApplication); +} + diff --git a/xwords4/tests/xwgame.pl b/xwords4/tests/xwgame.pl new file mode 100755 index 000000000..1afb9af50 --- /dev/null +++ b/xwords4/tests/xwgame.pl @@ -0,0 +1,82 @@ +#!/usr/bin/perl + +# Play a relay game. Params are CookieName and device count. + +use strict; + +my $xwdir = "../linux/linux"; + +my @cookies = ( "COOKIE" ); +my $nPlayers = 2; +my $port = 10999; +my $dict = "~/personal/dicts/SOWPODS2to15.xwd"; +my $quit = ""; +my $gettingCookies = 0; + +my @names = ( + "Fred", + "Barney", + "Wilma", + "Betty", +); + +while ( my $param = shift( @ARGV ) ) { + print STDERR "param $param\n"; + + if ( $param =~ m|-C| ) { + $cookies[0] = shift( @ARGV ); + $gettingCookies = 1; + next; + } elsif ( $param =~ m|-nplayers| ) { + $nPlayers = shift( @ARGV ); + } elsif ( $param =~ m|-dict| ) { + $dict = shift( @ARGV ); + } elsif ( $param =~ m|-port| ) { + $port = shift( @ARGV ); + } elsif ( $param =~ m|-quit| ) { + $quit = " -q "; + } elsif ( $gettingCookies ) { + push @cookies, $param; + next; + } else { + usage(); + exit 1; + } + $gettingCookies = 0; +} + +foreach my $cookie (@cookies) { + + my $cmdBase = "cd $xwdir && ./xwords -d $dict -C $cookie $quit -p $port"; + print "$cmdBase\n"; + + for ( my $p = 0; $p < $nPlayers; ++$p ) { + print STDERR "p = $p\n"; + my $cmd = $cmdBase; + if ( $p == 0 ) { # server case + $cmd .= " -s "; + + for ( my $i = 1; $i < $nPlayers; ++$i ) { + $cmd .= " -N "; + } + } + $cmd .= " -r $names[$p]"; + + my $pid = fork; + print STDERR "fork => $pid.\n"; + if ( $pid ) { + # parent; give child chance to get ahead +# sleep 1; + } else { + exec "$cmd 2>/dev/null &"; + last; + } + } +} + +sub usage() +{ + print STDERR <H6J0PV2#N1HK$iN7e&;gQ1_#hZ8044ta|IhII0RzKFAbtSE2ND<<8Wb47 I03?Qv0S$Q;h5!Hn literal 0 HcmV?d00001 diff --git a/xwords4/wince/bmps/flip.bmp b/xwords4/wince/bmps/flip.bmp new file mode 100755 index 0000000000000000000000000000000000000000..3f163fcae2257527ee1beb333661be165d1c58cc GIT binary patch literal 126 zcmZ?rtz&=yJ0PV2!~#&v$iN7eZ~&4=_#hZ8044ta{|{!>|6yRL{{h4wfcOCr9{}P8 YAO^~U%&r0A3Lq{3;shXe0Ad&b00^`Zb^rhX literal 0 HcmV?d00001 diff --git a/xwords4/wince/bmps/hint.bmp b/xwords4/wince/bmps/hint.bmp new file mode 100755 index 0000000000000000000000000000000000000000..005dbca0b8c7b1dc1b407970cb32b9864b3b98f6 GIT binary patch literal 126 zcmZ?rtz&=yJ0PV2!~#&v$iN7eZ~&4=_#hZ8044ta{}1OfHZU+KwlFXVb}%r2*h~%# Z3@i!|oT3JyL1G{|0S*QR1r`P{007QZ3hn>^ literal 0 HcmV?d00001 diff --git a/xwords4/wince/bmps/juggle.bmp b/xwords4/wince/bmps/juggle.bmp new file mode 100644 index 0000000000000000000000000000000000000000..e426658c3391493f5fb87b7ae1550234849834f3 GIT binary patch literal 126 zcmZ?rtz&=yJ0PV2!~#&v$iN7eZ~&4=_#hZ8044ta|BuXNV0gg5zzEbVzyUM|h(nZs TY!L__WG06L0|R>lkbwjMjU)*{ literal 0 HcmV?d00001 diff --git a/xwords4/wince/bmps/origin.bmp b/xwords4/wince/bmps/origin.bmp new file mode 100755 index 0000000000000000000000000000000000000000..6737cfd7b8acfa2c266a773d0ff0fc4fabf8a82d GIT binary patch literal 106 zcmZ?r&0>H6J0PV2#N1HK$iN7e&;gT}#Q*>Q8SEPv803LceF_W=M-mtq9z0-R_z1)x M8pH>QgXBT#04VMcQUCw| literal 0 HcmV?d00001 diff --git a/xwords4/wince/bmps/rightarrow.bmp b/xwords4/wince/bmps/rightarrow.bmp new file mode 100755 index 0000000000000000000000000000000000000000..2650daf19605b712ce96ca4495682125fd0d6c18 GIT binary patch literal 106 zcmZ?r&0>H6J0PV2#N1HK$iN7e&;gT}#Q*>Q8Gb)tVE75dKR`Hvfq_8*h(SOB!Uu_i H9QL70(Y)*K0-AbW|YuPgfvE=f^i*&8!rRDeR$JY5_^EKX0I z9LRN8LBQpH1ovJi|B0Kw?OIy$Tab5G?=Hy+rk!UD{10toOz8faqQIDTEJTQ5rLDH2 zvCdBC<2*0IVi)h4KJ(PC61KGJUrYS{e$xHTGJ*N->U-+%<=BtRdiPu_;p745ujgf( z%iKiwwO;(1<{%_qT+MuDLVK8Rc!IO2&HQcWQ%Vuivyo8L#{8SED|Icxo9 STEhx-A%mx@pUXO@geCwsp=C?} literal 0 HcmV?d00001 diff --git a/xwords4/wince/bmps/xwords4_ico_22x22.png b/xwords4/wince/bmps/xwords4_ico_22x22.png new file mode 100644 index 0000000000000000000000000000000000000000..030fe18d552df8de978162dd1f6332e21d95f397 GIT binary patch literal 343 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4fjKx9jP7LeL$-D$|*pj^6T^Rm@ z;DWu&Cj&(|3p^r=85p>QL70(Y)*K0-;44oT#}JFtPp4kwYck+*^;dThW{!VoeeC~# z*8RuCn;L&5EKhF8)AGN!ByyW+Y{aGmxo#-h`Fe`B)h z8u^H?m9>xcS+^dYpb+=9yT>|#!OYJ70n?NjT5@9N|K4hvD}K#Sd1ob;la^7&%<0ix z#piZ)v)EUju)kW^5zar&>GCQ`d6##QXCh-}E>ua@Z2QM#aa-oi0>&GRYc18chRR=& zf3;F#(Z;Pk-z4`|Ivq93Q2O|I>rU1u?(AQ}9v80&YFgFT&Ha1xF3X=YoaZlYnb7y> j?A5FVA`fR(Hr!z-w=Un3vrKRr(ANx}u6{1-oD!M<1EYsM literal 0 HcmV?d00001 diff --git a/xwords4/wince/bmps/xwords4_ico_32x32.png b/xwords4/wince/bmps/xwords4_ico_32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..1fef881b1d3a88957585aaec1f4e8e2d6f078570 GIT binary patch literal 352 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}Y)RhkE)4%c zaKYZ?lYt_f1s;*b3=G`DAk4@xYmNj^kiEpy*OmPSm!znn$qJcer+`8aJY5_^A`ZWu zX2{oUz~ee!`PjWh><)YXw_26Ic*PeQFe}N!!)d|+A@2hpzi2e9`x1LQ%<;e|z*8*5)v{u)O6=$gJPZZ2o|A zPh&W@>Y0t9S7U_NMue{Z>d^J!OzmdL19u*6+HxYIN5oJ-U+ /* swprintf */ +#include + +static void +nameToLabel( HWND hDlg, const XP_UCHAR* name, XP_U16 labelID ) +{ + wchar_t wideName[128]; + wchar_t wBuf[128]; + XP_U16 len; + + len = (XP_U16)XP_STRLEN( name ); + MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, name, len + 1, + wideName, len + 1 ); + + swprintf( wBuf, L"Enter password for %s:", wideName ); + + SendDlgItemMessage( hDlg, labelID, WM_SETTEXT, 0, (long)wBuf ); +} /* nameToLabel */ + +LRESULT CALLBACK +PasswdDlg(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + PasswdDialogState* pState; + XP_U16 id; + + if ( message == WM_INITDIALOG ) { + SetWindowLongPtr( hDlg, GWL_USERDATA, lParam ); + pState = (PasswdDialogState*)lParam; + + ceDlgSetup( &pState->dlgHdr, hDlg, DLG_STATE_TRAPBACK ); + + nameToLabel( hDlg, pState->name, IDC_PWDLABEL ); + + return TRUE; + } else { + pState = (PasswdDialogState*)GetWindowLongPtr( hDlg, GWL_USERDATA ); + if ( !!pState ) { + + if ( ceDoDlgHandle( &pState->dlgHdr, message, wParam, lParam ) ) { + return TRUE; + } + + switch ( message ) { + case WM_COMMAND: + id = LOWORD(wParam); + switch( id ) { + case IDOK: + ceGetDlgItemText( hDlg, PASS_EDIT, pState->buf, + pState->lenp ); + case IDCANCEL: + pState->userCancelled = id == IDCANCEL; + EndDialog( hDlg, id ); + + return TRUE; + } + } + } + } + return FALSE; +} /* PasswdDlg */ diff --git a/xwords4/wince/ceaskpwd.h b/xwords4/wince/ceaskpwd.h new file mode 100755 index 000000000..e29c8de6b --- /dev/null +++ b/xwords4/wince/ceaskpwd.h @@ -0,0 +1,37 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CEASKPWD_H_ +#define _CEASKPWD_H_ + +#include "stdafx.h" +#include "cemain.h" +#include "ceutil.h" + +typedef struct PasswdDialogState { + CeDlgHdr dlgHdr; + const XP_UCHAR* name; + XP_UCHAR* buf; + XP_U16* lenp; + XP_Bool userCancelled; /* needed? */ +} PasswdDialogState; + +LRESULT CALLBACK PasswdDlg(HWND, UINT, WPARAM, LPARAM); + +#endif diff --git a/xwords4/wince/ceblank.c b/xwords4/wince/ceblank.c new file mode 100755 index 000000000..658e49b6b --- /dev/null +++ b/xwords4/wince/ceblank.c @@ -0,0 +1,151 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2002,2008 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "ceblank.h" +#include "cemain.h" +#include "ceutil.h" +#include "debhacks.h" + +static void +loadLettersList( BlankDialogState* bState ) +{ + XP_U16 i; + XP_U16 nTiles = bState->nTiles; + HWND hDlg = bState->dlgHdr.hDlg; + CEAppGlobals* globals = bState->dlgHdr.globals; + const XP_UCHAR4* texts = bState->texts; + + for ( i = 0; i < nTiles; ++i ) { + XP_U16 len; + wchar_t widebuf[4]; + + len = MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, + texts[i], strlen(texts[i]), + widebuf, VSIZE(widebuf) ); + widebuf[len] = 0; + + SendDlgItemMessage( hDlg, LB_IF_PPC(globals,BLANKFACE_LIST), + ADDSTRING(globals), 0, (long)widebuf ); + } + + SendDlgItemMessage( hDlg, LB_IF_PPC(globals,BLANKFACE_LIST), + SETCURSEL(globals), 0, 0 ); +#ifdef _WIN32_WCE + SendDlgItemMessage( hDlg, BLANKFACE_LIST_PPC, LB_SETANCHORINDEX, 0, 0 ); +#endif +} /* loadLettersList */ + +#ifdef FEATURE_TRAY_EDIT +static void +showCurTray( HWND hDlg, BlankDialogState* bState ) +{ + if ( bState->pi->why == PICK_FOR_CHEAT ) { + const PickInfo* pi = bState->pi; + XP_U16 lenSoFar = 0; + XP_U16 i; + XP_UCHAR labelBuf[48]; + wchar_t widebuf[48]; + XP_UCHAR* name; + + name = bState->dlgHdr.globals->gameInfo.players[bState->playerNum].name; + + lenSoFar += XP_SNPRINTF( labelBuf + lenSoFar, + sizeof(labelBuf) - lenSoFar, + "%d of %d for %s" XP_CR "Cur", + pi->thisPick + 1, pi->nTotal, name ); + + for ( i = 0; i < pi->nCurTiles; ++i ) { + lenSoFar += XP_SNPRINTF( labelBuf+lenSoFar, + sizeof(labelBuf)-lenSoFar, "%s%s", + i==0?": ":", ", pi->curTiles[i] ); + } + + (void)MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, + labelBuf, lenSoFar + 1, + widebuf, + VSIZE(widebuf) + sizeof(widebuf[0]) ); + + SetDlgItemText( hDlg,IDC_PICKMSG, widebuf ); + } +} /* showCurTray */ +#endif + +LRESULT CALLBACK +BlankDlg(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + BlankDialogState* bState; + XP_U16 id; + + if ( message == WM_INITDIALOG ) { + SetWindowLongPtr( hDlg, GWL_USERDATA, lParam ); + bState = (BlankDialogState*)lParam; + +#ifdef FEATURE_TRAY_EDIT + if ( bState->pi->why == PICK_FOR_CHEAT ) { + showCurTray( hDlg, bState ); + ceShowOrHide( hDlg, IDC_BPICK, XP_FALSE ); + } else { + XP_ASSERT( bState->pi->why == PICK_FOR_BLANK ); + ceShowOrHide( hDlg, IDC_CPICK, XP_FALSE ); + ceShowOrHide( hDlg, IDC_PICKMSG, XP_FALSE ); + } + bState->canBackup = (bState->pi->why == PICK_FOR_CHEAT) + && (bState->pi->thisPick > 0); + ceShowOrHide( hDlg, IDC_BACKUP, bState->canBackup ); +#endif + + ceDlgSetup( &bState->dlgHdr, hDlg, DLG_STATE_TRAPBACK ); + ceDlgComboShowHide( &bState->dlgHdr, BLANKFACE_LIST ); + + loadLettersList( bState ); + } else { + bState = (BlankDialogState*)GetWindowLongPtr( hDlg, GWL_USERDATA ); + if ( !!bState ) { + + if ( ceDoDlgHandle( &bState->dlgHdr, message, wParam, lParam) ) { + return TRUE; + } + + switch ( message ) { + case WM_COMMAND: + id = LOWORD(wParam); + if ( 0 ) { +#ifdef FEATURE_TRAY_EDIT + } else if ( id == IDCANCEL ) { + bState->result = PICKER_PICKALL; + } else if ( id == IDC_BACKUP ) { + bState->result = PICKER_BACKUP; +#endif + } else if ( id == IDOK ) { + CEAppGlobals* globals = bState->dlgHdr.globals; + bState->result = (XP_S16) + SendDlgItemMessage( hDlg, + LB_IF_PPC(globals,BLANKFACE_LIST), + GETCURSEL(globals), 0, 0 ); + } else { + break; + } + EndDialog( hDlg, id ); + return TRUE; + } + } + } + + return FALSE; +} /* BlankDlg */ diff --git a/xwords4/wince/ceblank.h b/xwords4/wince/ceblank.h new file mode 100755 index 000000000..6e7be5642 --- /dev/null +++ b/xwords4/wince/ceblank.h @@ -0,0 +1,39 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2002, 2008 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CEBLANK_H_ +#define _CEBLANK_H_ + +#include "stdafx.h" +#include "cemain.h" +#include "ceutil.h" + +typedef struct BlankDialogState { + CeDlgHdr dlgHdr; + const PickInfo* pi; + XP_U16 playerNum; + const XP_UCHAR4* texts; + XP_U16 nTiles; + XP_S16 result; + XP_Bool canBackup; +} BlankDialogState; + +LRESULT CALLBACK BlankDlg(HWND, UINT, WPARAM, LPARAM); + +#endif diff --git a/xwords4/wince/ceclrsel.c b/xwords4/wince/ceclrsel.c new file mode 100644 index 000000000..692fbd122 --- /dev/null +++ b/xwords4/wince/ceclrsel.c @@ -0,0 +1,468 @@ +/* -*- fill-column: 77; c-basic-offset: 4; compile-command: "make TARGET_OS=wince DEBUG=TRUE" -*- */ +/* + * Copyright 2004-2008 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include "stdafx.h" +#include + +#include "ceclrsel.h" +#include "ceutil.h" +#include "cedebug.h" +#include "debhacks.h" + +static void colorButton( DRAWITEMSTRUCT* dis, HBRUSH brush ); + +#ifdef MY_COLOR_SEL + +typedef struct ClrEditDlgState { + CeDlgHdr dlgHdr; + HWND parent; + HWND sampleButton; + XP_U16 labelID; + + XP_U8 red; + XP_U8 green; + XP_U8 blue; + + XP_Bool inited; + XP_Bool cancelled; +} ClrEditDlgState; + +static void +initEditAndSlider( HWND hDlg, XP_U16 sliderID, XP_U8 val ) +{ + SendDlgItemMessage( hDlg, sliderID, TBM_SETRANGE, TRUE, + MAKELONG(0,255) ); + SendDlgItemMessage( hDlg, sliderID, TBM_SETPOS, TRUE, + (long)val ); + ceSetDlgItemNum( hDlg, sliderID+1, val ); +} /* initEditAndSlider */ + +static void +initChooseColor( ClrEditDlgState* eState, HWND hDlg ) +{ + initEditAndSlider( hDlg, CLREDT_SLIDER1, eState->red ); + initEditAndSlider( hDlg, CLREDT_SLIDER2, eState->green ); + initEditAndSlider( hDlg, CLREDT_SLIDER3, eState->blue ); +} /* initChooseColor */ + +static XP_U8* +colorForSlider( ClrEditDlgState* eState, XP_U16 sliderID ) +{ + switch( sliderID ) { + case CLREDT_SLIDER1: + return &eState->red; + case CLREDT_SLIDER2: + return &eState->green; + case CLREDT_SLIDER3: + return &eState->blue; + default: + XP_LOGF( "huh???" ); + return NULL; + } +} /* colorForSlider */ + +static void +updateForSlider( HWND hDlg, ClrEditDlgState* eState, XP_U16 sliderID ) +{ + XP_U8 newColor = (XP_U8)SendDlgItemMessage( hDlg, sliderID, TBM_GETPOS, + 0, 0L ); + XP_U8* colorPtr = colorForSlider( eState, sliderID ); + if ( newColor != *colorPtr ) { + *colorPtr = newColor; + + ceSetDlgItemNum( hDlg, sliderID+1, (XP_S32)newColor ); + + InvalidateRect( eState->sampleButton, NULL, TRUE /* erase */ ); + } +} /* updateForSlider */ + +static void +updateForField( HWND hDlg, ClrEditDlgState* eState, XP_U16 fieldID ) +{ + XP_S32 newColor = ceGetDlgItemNum( hDlg, fieldID ); + XP_U8* colorPtr = colorForSlider( eState, fieldID - 1 ); + XP_Bool modified = XP_FALSE;; + + if ( newColor > 255 ) { + newColor = 255; + modified = XP_TRUE; + } else if ( newColor < 0 ) { + newColor = 0; + modified = XP_TRUE; + } + if ( modified ) { + ceSetDlgItemNum( hDlg, fieldID, newColor ); + } + + if ( newColor != *colorPtr ) { + *colorPtr = (XP_U8)newColor; + + SendDlgItemMessage( hDlg, fieldID-1, TBM_SETPOS, TRUE, + (long)newColor ); + InvalidateRect( eState->sampleButton, NULL, FALSE ); + } +} /* updateForField */ + +static void +colorButtonFromState( ClrEditDlgState* eState, DRAWITEMSTRUCT* dis ) +{ + COLORREF ref = RGB( eState->red, eState->green, eState->blue ); + HBRUSH brush = CreateSolidBrush( ref ); + colorButton( dis, brush ); + DeleteObject( brush ); +} + +LRESULT CALLBACK +EditColorsDlg( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam ) +{ + ClrEditDlgState* eState; + XP_U16 wid; + XP_U16 notifyCode; + NMTOOLBAR* nmToolP; + + if ( message == WM_INITDIALOG ) { + SetWindowLongPtr( hDlg, GWL_USERDATA, lParam ); + + eState = (ClrEditDlgState*)lParam; + eState->cancelled = XP_TRUE; + eState->inited = XP_FALSE; + + ceDlgSetup( &eState->dlgHdr, hDlg, DLG_STATE_TRAPBACK ); + + wchar_t label[32]; + XP_U16 len = SendDlgItemMessage( eState->parent, eState->labelID, + WM_GETTEXT, VSIZE(label), + (long)label ); + if ( len > 0 ) { + label[len-1] = 0; /* hack: overwrite ':' */ + } + wchar_t buf[64]; + swprintf( buf, L"Edit color for %s", label ); + SendMessage( hDlg, WM_SETTEXT, 0, (LPARAM)buf ); + + eState->sampleButton = GetDlgItem( hDlg, CLSAMPLE_BUTTON_ID ); + EnableWindow( eState->sampleButton, FALSE ); + + return TRUE; + } else { + eState = (ClrEditDlgState*)GetWindowLongPtr( hDlg, GWL_USERDATA ); + if ( !eState ) { + return FALSE; + } + + if ( !eState->inited ) { + /* set to true first! Messages will be generated by + initChooseColor call below */ + eState->inited = XP_TRUE; + initChooseColor( eState, hDlg ); + } + + if ( ceDoDlgHandle( &eState->dlgHdr, message, wParam, lParam) ) { + return TRUE; + } + + switch (message) { + + case WM_DRAWITEM: + colorButtonFromState( eState, (DRAWITEMSTRUCT*)lParam ); + return TRUE; + break; + + case WM_NOTIFY: + nmToolP = (NMTOOLBAR*)lParam; + wid = nmToolP->hdr.idFrom; + switch ( wid ) { + case CLREDT_SLIDER1: + case CLREDT_SLIDER2: + case CLREDT_SLIDER3: + updateForSlider( hDlg, eState, wid ); + break; + } + break; + case WM_COMMAND: + wid = LOWORD(wParam); + switch( wid ) { + case RED_EDIT: + case GREEN_EDIT: + case BLUE_EDIT: + notifyCode = HIWORD(wParam); + if ( notifyCode == EN_CHANGE ) { + updateForField( hDlg, eState, wid ); + return TRUE; + } + break; + + case IDOK: + eState->cancelled = XP_FALSE; + /* fallthrough */ + + case IDCANCEL: + EndDialog(hDlg, wid); + return TRUE; + } + } + } + + return FALSE; +} /* EditColorsDlg */ + +static XP_Bool +myChooseColor( CeDlgHdr* dlgHdr, XP_U16 labelID, COLORREF* cref ) +{ + ClrEditDlgState state; + int result; + + XP_MEMSET( &state, 0, sizeof(state) ); + state.dlgHdr.globals = dlgHdr->globals; + state.red = GetRValue(*cref); + state.green = GetGValue(*cref); + state.blue = GetBValue(*cref); + state.labelID = labelID; + state.parent = dlgHdr->hDlg; + + XP_LOGF( "setting up IDD_COLOREDITDLG" ); + + result = DialogBoxParam( dlgHdr->globals->hInst, (LPCTSTR)IDD_COLOREDITDLG, + dlgHdr->hDlg, (DLGPROC)EditColorsDlg, (long)&state ); + + XP_LOGF( "DialogBoxParam=>%d", result ); + + if ( !state.cancelled ) { + *cref = RGB( state.red, state.green, state.blue ); + } + + return !state.cancelled; +} /* myChooseColor */ + +#endif /* MY_COLOR_SEL */ + +static void +colorButton( DRAWITEMSTRUCT* dis, HBRUSH brush ) +{ + RECT rect = dis->rcItem; + + Rectangle( dis->hDC, rect.left, rect.top, rect.right, rect.bottom ); + InsetRect( &rect, 1, 1 ); + FillRect( dis->hDC, &rect, brush ); +} + +typedef struct ColorsDlgState { + CeDlgHdr dlgHdr; + COLORREF* inColors; + + COLORREF colors[CE_NUM_EDITABLE_COLORS]; + HBRUSH brushes[CE_NUM_EDITABLE_COLORS]; + HWND buttons[CE_NUM_EDITABLE_COLORS]; + + XP_Bool cancelled; + XP_Bool inited; +} ColorsDlgState; + +#define FIRST_BUTTON DLBLTR_SAMPLE +#define LAST_BUTTON PLAYER4_SAMPLE + +static void +initColorData( ColorsDlgState* cState ) +{ + XP_U16 i; + + XP_ASSERT( (LAST_BUTTON - FIRST_BUTTON + 1) == CE_NUM_EDITABLE_COLORS ); + + for ( i = 0; i < CE_NUM_EDITABLE_COLORS; ++i ) { + COLORREF ref = cState->inColors[i]; + HWND button = GetDlgItem( cState->dlgHdr.hDlg, FIRST_BUTTON + i ); + cState->colors[i] = ref; + cState->brushes[i] = CreateSolidBrush( ref ); + cState->buttons[i] = button; + EnableWindow( button, FALSE ); + } +} /* initColorData */ + +static HBRUSH +brushForButton( ColorsDlgState* cState, HWND hwndButton ) +{ + XP_U16 i; + for ( i = 0; i < CE_NUM_EDITABLE_COLORS; ++i ) { + if ( cState->buttons[i] == hwndButton ) { + return cState->brushes[i]; + } + } + return NULL; +} /* brushForButton */ + +static void +deleteButtonBrushes( ColorsDlgState* cState ) +{ + XP_U16 i; + for ( i = 0; i < CE_NUM_EDITABLE_COLORS; ++i ) { + DeleteObject( cState->brushes[i] ); + } +} /* deleteButtonBrushes */ + +static XP_Bool +wrapChooseColor( ColorsDlgState* cState, XP_U16 button ) +{ + XP_Bool handled = XP_FALSE; + if ( button >= DLBLTR_BUTTON && button <= PLAYER4_BUTTON ) { + XP_U16 index = button - DLBLTR_BUTTON; + +#ifdef MY_COLOR_SEL + XP_U16 labelID = button + CLRSEL_LABEL_OFFSET; + COLORREF clrref = cState->colors[index]; + + if ( myChooseColor( &cState->dlgHdr, labelID, &clrref ) ) { + cState->colors[index] = clrref; + DeleteObject( cState->brushes[index] ); + cState->brushes[index] = CreateSolidBrush( clrref ); + XP_LOGF( "%s: may need to invalidate the button since " + "color's changed", __func__ ); + } +#else + CHOOSECOLOR ccs; + BOOL hitOk; + COLORREF arr[16]; + XP_U16 i; + + XP_MEMSET( &ccs, 0, sizeof(ccs) ); + XP_MEMSET( &arr, 0, sizeof(arr) ); + + for ( i = 0; i < CE_NUM_EDITABLE_COLORS; ++i ) { + arr[i] = cState->colors[i]; + } + + ccs.lStructSize = sizeof(ccs); + ccs.hwndOwner = cState->dlgHdr.hDlg; + ccs.rgbResult = cState->colors[index]; + ccs.lpCustColors = arr; + + ccs.Flags = CC_ANYCOLOR | CC_RGBINIT | CC_FULLOPEN; + + hitOk = ChooseColor( &ccs ); + + if ( hitOk ) { + cState->colors[index] = ccs.rgbResult; + DeleteObject( cState->brushes[index] ); + cState->brushes[index] = CreateSolidBrush( ccs.rgbResult ); + } +#endif + handled = XP_TRUE; + } + return handled; +} /* wrapChooseColor */ + +static void +ceDrawColorButton( ColorsDlgState* cState, DRAWITEMSTRUCT* dis ) +{ + HBRUSH brush = brushForButton( cState, dis->hwndItem ); + XP_ASSERT( !!brush ); + + colorButton( dis, brush ); +} /* ceDrawColorButton */ + +LRESULT CALLBACK +ColorsDlg( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam ) +{ + ColorsDlgState* state; + BOOL result = FALSE; + + if ( message == WM_INITDIALOG ) { + SetWindowLongPtr( hDlg, GWL_USERDATA, lParam ); + + state = (ColorsDlgState*)lParam; + state->cancelled = XP_TRUE; + state->inited = XP_FALSE; + + ceDlgSetup( &state->dlgHdr, hDlg, DLG_STATE_NONE ); + + result = TRUE; + } else { + state = (ColorsDlgState*)GetWindowLongPtr( hDlg, GWL_USERDATA ); + if ( !!state ) { + XP_U16 wid; + + if ( !state->inited ) { + initColorData( state ); + state->inited = XP_TRUE; + } + +/* XP_LOGF( "%s: event=%s (%d); wParam=0x%x; lParam=0x%lx", */ +/* __func__, messageToStr(message), message, */ +/* wParam, lParam ); */ + + if ( ceDoDlgHandle( &state->dlgHdr, message, wParam, lParam) ) { + result = TRUE; + } else { + switch (message) { + + case WM_DRAWITEM: + ceDrawColorButton( state, (DRAWITEMSTRUCT*)lParam ); + result = TRUE; + break; + + case WM_COMMAND: + if ( BN_CLICKED == HIWORD(wParam) ) { + wid = LOWORD(wParam); + switch( wid ) { + case IDOK: + state->cancelled = XP_FALSE; + /* fallthrough */ + + case IDCANCEL: + deleteButtonBrushes( state ); + EndDialog(hDlg, wid); + result = TRUE; + break; + + default: + /* it's one of the color buttons. Set up with the + appropriate color and launch ChooseColor */ + result = wrapChooseColor( state, wid ); + break; + } + } + } + } + } + } + + return result; +} /* ColorsDlg */ + +XP_Bool +ceDoColorsEdit( HWND hwnd, CEAppGlobals* globals, COLORREF* colors ) +{ + ColorsDlgState state; + + XP_MEMSET( &state, 0, sizeof(state) ); + state.dlgHdr.globals = globals; + state.inColors = colors; + + (void)DialogBoxParam( globals->hInst, (LPCTSTR)IDD_COLORSDLG, hwnd, + (DLGPROC)ColorsDlg, (long)&state ); + + if ( !state.cancelled ) { + XP_U16 i; + for ( i = 0; i < CE_NUM_EDITABLE_COLORS; ++i ) { + colors[i] = state.colors[i]; + } + } + + return !state.cancelled; +} /* ceDoColorsEdit */ diff --git a/xwords4/wince/ceclrsel.h b/xwords4/wince/ceclrsel.h new file mode 100644 index 000000000..15ac495e4 --- /dev/null +++ b/xwords4/wince/ceclrsel.h @@ -0,0 +1,28 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2004 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CECLRSEL_H_ +#define _CECLRSEL_H_ + +#include "xptypes.h" +#include "cemain.h" + +XP_Bool ceDoColorsEdit( HWND hwnd, CEAppGlobals* globals, COLORREF* colors ); + +#endif diff --git a/xwords4/wince/cecondlg.c b/xwords4/wince/cecondlg.c new file mode 100755 index 000000000..a6bc3c9e5 --- /dev/null +++ b/xwords4/wince/cecondlg.c @@ -0,0 +1,239 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH + +#include "cecondlg.h" +#include "ceutil.h" +#include "debhacks.h" + +static void +ceControlsToAddrRec( HWND hDlg, CeConnDlgState* cState ) +{ + XP_U16 len; + + if ( cState->addrRec.conType == COMMS_CONN_RELAY ) { +#ifdef XWFEATURE_RELAY + len = sizeof(cState->addrRec.u.ip_relay.hostName); + ceGetDlgItemText( hDlg, RELAYNAME_EDIT, + cState->addrRec.u.ip_relay.hostName, &len ); + cState->addrRec.u.ip_relay.port = + (XP_U16)ceGetDlgItemNum( hDlg, RELAYPORT_EDIT ); + len = sizeof(cState->addrRec.u.ip_relay.cookie); + ceGetDlgItemText( hDlg, COOKIE_EDIT, cState->addrRec.u.ip_relay.cookie, + &len ); +#endif + } else if ( cState->addrRec.conType == COMMS_CONN_BT ) { +#ifdef XWFEATURE_BLUETOOTH + if ( cState->role == SERVER_ISCLIENT ) { + len = sizeof(cState->addrRec.u.bt.hostName); + ceGetDlgItemText( hDlg, IDC_BLUET_ADDR_EDIT, + cState->addrRec.u.bt.hostName, &len ); + } +#endif + } else { + XP_ASSERT(0); + } +} /* ceControlsToAddrRec */ + +static void +adjustForConnType( HWND hDlg, const CeConnDlgState* cState ) +{ + XP_U16 relayIds[] = { + IDC_COOKIE_LAB, +#ifdef XWFEATURE_RELAY + COOKIE_EDIT,IDC_CRELAYHINT_LAB,IDC_CRELAYNAME_LAB,RELAYNAME_EDIT, + IDC_CRELAYPORT_LAB, RELAYPORT_EDIT, +#endif + 0 }; + XP_U16 btIds[] = { + IDC_BLUET_ADDR_LAB, +#ifdef XWFEATURE_BLUETOOTH + IDC_BLUET_ADDR_EDIT, IDC_BLUET_ADDR_BROWSE, +#endif + 0 }; + XP_U16* allIDs[] = { relayIds, btIds }; + XP_U16* on = NULL; + XP_U16 i; + + if ( cState->addrRec.conType == COMMS_CONN_RELAY ) { + on = relayIds; + } else if ( cState->addrRec.conType == COMMS_CONN_BT ) { + on = +#ifdef XWFEATURE_BLUETOOTH + cState->role != SERVER_ISCLIENT ? NULL: +#endif + btIds; /* we want the "disabled" message */ + } + + for ( i = 0; i < VSIZE(allIDs); ++i ) { + XP_U16* ids = allIDs[i]; + XP_Bool enable = ids == on; + while ( *ids != 0 ) { + ceShowOrHide( hDlg, *(ids++), enable ); + } + } +} /* adjustForConnType */ + +static XP_U16 +conTypeToIndex( CommsConnType conType ) +{ + XP_U16 index = 0; + switch( conType ) { + case COMMS_CONN_RELAY: + index = 1; + break; + case COMMS_CONN_BT: + index = 0; + break; + default: + XP_ASSERT(0); + } + return index; +} + +static CommsConnType +indexToConType( XP_U16 index ) +{ + CommsConnType conType = COMMS_CONN_NONE; + switch( index ) { + case 0: + conType = COMMS_CONN_BT; break; + case 1: + conType = COMMS_CONN_RELAY; break; + default: + XP_ASSERT(0); + } + return conType; +} + +static void +ceControlsFromAddrRec( HWND hDlg, const CeConnDlgState* cState ) +{ + XP_U16 i; + wchar_t* strs[] = { + L"Bluetooth" + , L"WiFi/Cellular data" + }; + + for ( i = 0; i < VSIZE(strs); ++i ) { + SendDlgItemMessage( hDlg, IDC_CONNECTCOMBO, CB_ADDSTRING, + 0, (LPARAM)strs[i] ); + } + + SendDlgItemMessage( hDlg, IDC_CONNECTCOMBO, CB_SETCURSEL, + conTypeToIndex(cState->addrRec.conType), 0L ); + + if ( cState->addrRec.conType == COMMS_CONN_RELAY ) { +#ifdef XWFEATURE_RELAY + ceSetDlgItemText( hDlg, RELAYNAME_EDIT, + cState->addrRec.u.ip_relay.hostName ); + ceSetDlgItemNum( hDlg, RELAYPORT_EDIT, + cState->addrRec.u.ip_relay.port ); + ceSetDlgItemText( hDlg, COOKIE_EDIT, + cState->addrRec.u.ip_relay.cookie ); +#endif + } else if ( cState->addrRec.conType == COMMS_CONN_BT ) { +#ifdef XWFEATURE_BLUETOOTH + if ( cState->role == SERVER_ISCLIENT ) { + ceSetDlgItemText( hDlg, IDC_BLUET_ADDR_EDIT, + cState->addrRec.u.bt.hostName ); + } +#endif + } else { + XP_ASSERT(0); + } +} + +static LRESULT CALLBACK +ConnsDlg( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam ) +{ + LRESULT result = FALSE; + + CeConnDlgState* cState; + + if ( message == WM_INITDIALOG ) { + SetWindowLongPtr( hDlg, GWL_USERDATA, lParam ); + cState = (CeConnDlgState*)lParam; + + adjustForConnType( hDlg, cState ); + + ceControlsFromAddrRec( hDlg, cState ); + + ceDlgSetup( &cState->dlgState, hDlg, DLG_STATE_NONE ); + + result = TRUE; + } else { + cState = (CeConnDlgState*)GetWindowLongPtr( hDlg, GWL_USERDATA ); + if ( !!cState ) { + CEAppGlobals* globals = cState->dlgHdr.globals; + + if ( message == WM_COMMAND ) { + XP_U16 id = LOWORD(wParam); + + switch( id ) { + + case IDC_CONNECTCOMBO: + if ( HIWORD(wParam) == CBN_SELCHANGE ) { + XP_S16 sel; + sel = SendDlgItemMessage( hDlg, IDC_CONNECTCOMBO, + CB_GETCURSEL, 0, 0L ); + cState->addrRec.conType = indexToConType( sel ); + adjustForConnType( hDlg, cState ); + result = TRUE; + } + break; + + case IDOK: + ceControlsToAddrRec( hDlg, cState ); + case IDCANCEL: + EndDialog(hDlg, id); + cState->userCancelled = id == IDCANCEL; + result = TRUE; + } + } else if ( message == WM_VSCROLL ) { + if ( !IS_SMARTPHONE(globals) ) { + ceDoDlgScroll( hDlg, wParam ); + } + } + } + } + + return result; +} /* ConnsDlg */ + +XP_Bool +WrapConnsDlg( HWND hDlg, CEAppGlobals* globals, const CommsAddrRec* addrRec, + DeviceRole role, CeConnDlgState* state ) +{ + XP_Bool result; + XP_MEMSET( state, 0, sizeof( *state ) ); + + state->globals = globals; + state->role = role; + XP_MEMCPY( &state->addrRec, addrRec, sizeof(state->addrRec) ); + + DialogBoxParam( globals->hInst, (LPCTSTR)IDD_CONNSSDLG, hDlg, + (DLGPROC)ConnsDlg, (long)state ); + + result = !state->userCancelled; + return result; +} /* WrapConnsDlg */ + +#endif diff --git a/xwords4/wince/cecondlg.h b/xwords4/wince/cecondlg.h new file mode 100755 index 000000000..5601b041b --- /dev/null +++ b/xwords4/wince/cecondlg.h @@ -0,0 +1,37 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CECONDLG_H_ +#define _CECONDLG_H_ + +#include "comms.h" +#include "cemain.h" + +typedef struct CeConnDlgState { + CeDlgHdr dlgHdr; + CommsAddrRec addrRec; + DeviceRole role; + XP_Bool userCancelled; +} CeConnDlgState; + +XP_Bool WrapConnsDlg( HWND hDlg, CEAppGlobals* globals, + const CommsAddrRec* addrRec, + DeviceRole role, CeConnDlgState* state ); + +#endif diff --git a/xwords4/wince/cedebug.c b/xwords4/wince/cedebug.c new file mode 100644 index 000000000..e455fda4c --- /dev/null +++ b/xwords4/wince/cedebug.c @@ -0,0 +1,150 @@ +/* -*- fill-column: 77; c-basic-offset: 4; compile-command: "make TARGET_OS=wince DEBUG=TRUE" -*- */ +/* + * Copyright 2008 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include "cedebug.h" + +#ifdef DEBUG + +#define CASE_STR(c) case c: str = #c; break + +const char* +messageToStr( UINT message ) +{ + const char* str; + switch( message ) { + CASE_STR( WM_NCACTIVATE ); + CASE_STR( WM_QUERYNEWPALETTE ); +#ifdef _WIN32_WCE + CASE_STR( WM_IME_NOTIFY ); + CASE_STR( WM_IME_SETCONTEXT ); +#endif + CASE_STR( WM_WINDOWPOSCHANGED ); + CASE_STR( WM_MOVE ); + CASE_STR( WM_SIZE ); + CASE_STR( WM_ACTIVATE ); + CASE_STR( WM_SETTINGCHANGE ); + CASE_STR( WM_VSCROLL ); + CASE_STR( WM_COMMAND ); + CASE_STR( WM_PAINT ); + CASE_STR( WM_LBUTTONDOWN ); + CASE_STR( WM_MOUSEMOVE ); + CASE_STR( WM_LBUTTONUP ); + CASE_STR( WM_KEYDOWN ); + CASE_STR( WM_KEYUP ); + CASE_STR( WM_CHAR ); + CASE_STR( WM_TIMER ); + CASE_STR( WM_DESTROY ); + CASE_STR( XWWM_TIME_RQST ); + CASE_STR( XWWM_PACKET_ARRIVED ); + CASE_STR( WM_DRAWITEM ); + CASE_STR( WM_NEXTDLGCTL ); + CASE_STR( WM_CTLCOLORSTATIC ); + CASE_STR( WM_CTLCOLORBTN ); + CASE_STR( WM_SETFONT ); + CASE_STR( WM_INITDIALOG ); + CASE_STR( WM_SHOWWINDOW ); + CASE_STR( WM_WINDOWPOSCHANGING ); + CASE_STR( WM_SETFOCUS ); + CASE_STR( WM_NCPAINT ); + CASE_STR( WM_ERASEBKGND ); + CASE_STR( WM_NCCALCSIZE ); + CASE_STR( WM_SETTEXT ); + CASE_STR( WM_CTLCOLORDLG ); + CASE_STR( WM_MOUSEACTIVATE ); + CASE_STR( WM_SETCURSOR ); + CASE_STR( WM_CTLCOLORLISTBOX ); + CASE_STR( WM_CTLCOLOREDIT ); + CASE_STR( WM_NCDESTROY ); + CASE_STR( WM_NOTIFY ); + CASE_STR( WM_NCHITTEST ); + CASE_STR( WM_HSCROLL ); + CASE_STR( WM_STYLECHANGED ); + CASE_STR( WM_NOTIFYFORMAT ); + CASE_STR( WM_KILLFOCUS ); + CASE_STR( WM_CTLCOLORSCROLLBAR ); + CASE_STR( WM_NCMOUSEMOVE ); + CASE_STR( SBM_SETSCROLLINFO ); + CASE_STR( WM_HOTKEY ); + CASE_STR( WM_CLOSE ); + CASE_STR( WM_ACTIVATEAPP ); + CASE_STR( WM_ENTERMENULOOP ); + CASE_STR( WM_EXITMENULOOP ); + CASE_STR( WM_INITMENUPOPUP ); + CASE_STR( WM_CANCELMODE ); + default: + str = ""; + } + return str; +} /* messageToStr */ + +void +XP_LOGW( const XP_UCHAR* prefix, const wchar_t* arg ) +{ + XP_UCHAR buf[512]; + (void)WideCharToMultiByte( CP_ACP, 0, arg, -1, + buf, sizeof(buf), NULL, NULL ); + XP_LOGF( "%s: %s", prefix, buf ); +} + +void +logRect( const char* comment, const RECT* rect ) +{ + XP_LOGF( "%s: %s: left=%ld,top=%ld,right=%ld,bottom=%ld", __func__, + comment, rect->left, rect->top, rect->right, rect->bottom ); +} + +#undef CASE_STR + +void +logLastError( const char* comment ) +{ + LPVOID lpMsgBuf; + DWORD lastErr = GetLastError(); + XP_UCHAR msg[256]; + XP_U16 len; + XP_U16 lenSoFar; + + snprintf( msg, sizeof(msg), "%s (err: %ld): ", comment, lastErr ); + lenSoFar = strlen( msg ); + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + lastErr, + 0, // Default language + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + + len = wcslen( lpMsgBuf ); + if ( len >= sizeof(msg)-lenSoFar ) { + len = sizeof(msg) - lenSoFar - 1; + } + WideCharToMultiByte( CP_ACP, 0, lpMsgBuf, len + 1, + msg + lenSoFar, len + 1, NULL, NULL ); + LocalFree( lpMsgBuf ); + + XP_LOGF( "system error: %s", msg ); +} /* logLastError */ + +#endif /* DEBUG */ diff --git a/xwords4/wince/cedebug.h b/xwords4/wince/cedebug.h new file mode 100644 index 000000000..2e3622777 --- /dev/null +++ b/xwords4/wince/cedebug.h @@ -0,0 +1,45 @@ +/* -*- fill-column: 77; c-basic-offset: 4; compile-command: "make TARGET_OS=wince DEBUG=TRUE" -*- */ + +/* Copyright 2008 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CEDEBUG_H_ +#define _CEDEBUG_H_ + +#include "cemain.h" + +const char* messageToStr( UINT message ); +void logRect( const char* comment, const RECT* rect ); + +#ifdef DEBUG +void XP_LOGW( const XP_UCHAR* prefix, const wchar_t* arg ); +void logLastError( const char* comment ); +void messageToBuf( UINT message, char* buf, int bufSize ); +#else +# define XP_LOGW( prefix, arg ) +# define logLastError(c) +#endif + +#ifdef DEBUG +# define assertOnTop( hWnd ) { \ + XP_ASSERT( (hWnd) == GetForegroundWindow() ); \ + } +#else +# define assertOnTop( w ) +#endif + +#endif /* _CEDEBUG_H_ */ diff --git a/xwords4/wince/cedefines.h b/xwords4/wince/cedefines.h new file mode 100755 index 000000000..ed338354b --- /dev/null +++ b/xwords4/wince/cedefines.h @@ -0,0 +1,37 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CEDEFINES_H_ +#define _CEDEFINES_H_ + +#define CE_MAX_ROWS 15 + +#define TRAY_BORDER 6 +#define CELL_BORDER 3 + +#define MIN_CELL_WIDTH 10 +#define MIN_CELL_HEIGHT 13 +/* 1 below for what's subtracted from bparms->trayHeight */ +#define MIN_TRAY_HEIGHT (((MIN_CELL_HEIGHT) + (TRAY_BORDER - CELL_BORDER)) + 1) + +/* Favor the tray over the scoreboard. */ +#define SCORE_TWEAK 2 + + +#endif diff --git a/xwords4/wince/cedict.c b/xwords4/wince/cedict.c new file mode 100755 index 000000000..05ad1f110 --- /dev/null +++ b/xwords4/wince/cedict.c @@ -0,0 +1,797 @@ +/* -*- fill-column: 77; compile-command: "make TARGET_OS=wince DEBUG=TRUE" -*- */ +/* + * Copyright 1997-2008 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef STUBBED_DICT + +#include /* _snwprintf */ +#include /* _snwprintf */ + +#include "stdafx.h" +/* #include */ +#include +#include "dictnryp.h" +#include "strutils.h" +#include "cedict.h" +#include "debhacks.h" +#include "cedebug.h" +#include "ceutil.h" + +typedef struct CEDictionaryCtxt { + DictionaryCtxt super; + HANDLE mappedFile; + void* mappedBase; +} CEDictionaryCtxt; + +static void ce_dict_destroy( DictionaryCtxt* dict ); +static const XP_UCHAR* ce_dict_getShortName( const DictionaryCtxt* dict ); +static void ceLoadSpecialData( CEDictionaryCtxt* ctxt, XP_U8** ptrp ); +static XP_U16 ceCountSpecials( CEDictionaryCtxt* ctxt ); +static XP_Bitmap* ceMakeBitmap( CEDictionaryCtxt* ctxt, XP_U8** ptrp ); + +static XP_U32 n_ptr_tohl( XP_U8** in ); +static XP_U16 n_ptr_tohs( XP_U8** in ); +static XP_U8* openMappedFile( MPFORMAL const wchar_t* name, + HANDLE* mappedFileP, HANDLE* hFileP, + XP_U32* sizep ); +static void closeMappedFile( MPFORMAL XP_U8* base, HANDLE mappedFile ); +static XP_Bool checkIfDictAndLegal( MPFORMAL wchar_t* path, XP_U16 pathLen, + wchar_t* name ); +static XP_Bool findAlternateDict( CEAppGlobals* globals, wchar_t* dictName ); + +#define ALIGN_COUNT 2 + +DictionaryCtxt* +ce_dictionary_make( CEAppGlobals* globals, XP_UCHAR* dictName ) +{ + CEDictionaryCtxt* ctxt = (CEDictionaryCtxt*)NULL; + HANDLE mappedFile = NULL; + + wchar_t nameBuf[MAX_PATH+1]; + HANDLE hFile; + XP_U8* ptr; + XP_U32 dictLength; + XP_UCHAR buf[CE_MAX_PATH_LEN+1]; /* in case we have to look */ + + XP_ASSERT( !!dictName ); + + MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, dictName, -1, + nameBuf, VSIZE(nameBuf) ); + + ptr = openMappedFile( MPPARM(globals->mpool) nameBuf, &mappedFile, + &hFile, &dictLength ); + if ( !ptr ) { + if ( findAlternateDict( globals, nameBuf ) ) { + (void)WideCharToMultiByte( CP_ACP, 0, nameBuf, -1, + buf, sizeof(buf), NULL, NULL ); + ptr = openMappedFile( MPPARM(globals->mpool) nameBuf, &mappedFile, + &hFile, &dictLength ); + if ( !!ptr ) { + dictName = buf; + } + } + } + + while( !!ptr ) { /* lets us break.... */ + XP_U32 offset; + XP_U16 numFaces; + XP_U16 i; + XP_U16 flags; + void* mappedBase = (void*)ptr; + XP_U8 nodeSize; + + flags = n_ptr_tohs( &ptr ); + +#ifdef NODE_CAN_4 + if ( flags == 0x0002 ) { + nodeSize = 3; + } else if ( flags == 0x0003 ) { + nodeSize = 4; + } else { + break; /* we want to return NULL */ + } +#else + if( flags != 0x0001 ) { + break; + } +#endif + ctxt = (CEDictionaryCtxt*)ce_dictionary_make_empty( globals ); + + ctxt->mappedFile = mappedFile; + ctxt->mappedBase = mappedBase; + ctxt->super.nodeSize = nodeSize; + ctxt->super.destructor = ce_dict_destroy; + ctxt->super.func_dict_getShortName = ce_dict_getShortName; + + numFaces = (XP_U16)(*ptr++); + ctxt->super.nFaces = (XP_U8)numFaces; + ctxt->super.faces16 = + XP_MALLOC( globals->mpool, + numFaces * sizeof(ctxt->super.faces16[0]) ); + +#ifdef NODE_CAN_4 + ctxt->super.is_4_byte = (ctxt->super.nodeSize == 4); + + for ( i = 0; i < numFaces; ++i ) { + ctxt->super.faces16[i] = n_ptr_tohs(&ptr); + } +#else + for ( i = 0; i < numFaces; ++i ) { + ctxt->super.faces16[i] = (XP_CHAR16)*ptr++; + } +#endif + + ctxt->super.countsAndValues = + (XP_U8*)XP_MALLOC(globals->mpool, numFaces*2); + + ptr += 2; /* skip xloc header */ + for ( i = 0; i < numFaces*2; i += 2 ) { + ctxt->super.countsAndValues[i] = *ptr++; + ctxt->super.countsAndValues[i+1] = *ptr++; + } + + ceLoadSpecialData( ctxt, &ptr ); + + dictLength -= ptr - (XP_U8*)ctxt->mappedBase; + if ( dictLength > sizeof(XP_U32) ) { + offset = n_ptr_tohl( &ptr ); + dictLength -= sizeof(offset); +#ifdef NODE_CAN_4 + XP_ASSERT( dictLength % ctxt->super.nodeSize == 0 ); +# ifdef DEBUG + ctxt->super.numEdges = dictLength / ctxt->super.nodeSize; +# endif +#else + XP_ASSERT( dictLength % 3 == 0 ); +# ifdef DEBUG + ctxt->super.numEdges = dictLength / 3; +# endif +#endif + } else { + offset = 0; + } + + if ( dictLength > 0 ) { + ctxt->super.base = (array_edge*)ptr; +#ifdef NODE_CAN_4 + ctxt->super.topEdge = ctxt->super.base + + (offset * ctxt->super.nodeSize); +#else + ctxt->super.topEdge = ctxt->super.base + (offset * 3); +#endif + } else { + ctxt->super.topEdge = (array_edge*)NULL; + ctxt->super.base = (array_edge*)NULL; + } + + setBlankTile( (DictionaryCtxt*)ctxt ); + + ctxt->super.name = copyString(globals->mpool, dictName); + break; /* exit phony while loop */ + } + return (DictionaryCtxt*)ctxt; +} /* ce_dictionary_make */ + +DictionaryCtxt* +ce_dictionary_make_empty( CEAppGlobals* XP_UNUSED_DBG(globals) ) +{ + CEDictionaryCtxt* ctxt = (CEDictionaryCtxt*)XP_MALLOC(globals->mpool, + sizeof(*ctxt)); + XP_MEMSET( ctxt, 0, sizeof(*ctxt) ); + + dict_super_init( (DictionaryCtxt*)ctxt ); + MPASSIGN( ctxt->super.mpool, globals->mpool ); + return (DictionaryCtxt*)ctxt; +} /* ce_dictionary_make_empty */ + +static void +ceLoadSpecialData( CEDictionaryCtxt* ctxt, XP_U8** ptrp ) +{ + XP_U16 nSpecials = ceCountSpecials( ctxt ); + XP_U8* ptr = *ptrp; + Tile i; + XP_UCHAR** texts; + SpecialBitmaps* bitmaps; + + texts = (XP_UCHAR**)XP_MALLOC( ctxt->super.mpool, + nSpecials * sizeof(*texts) ); + bitmaps = (SpecialBitmaps*) + XP_MALLOC( ctxt->super.mpool, nSpecials * sizeof(*bitmaps) ); + + for ( i = 0; i < ctxt->super.nFaces; ++i ) { + + XP_CHAR16 face = ctxt->super.faces16[(short)i]; + if ( IS_SPECIAL(face) ) { + + /* get the string */ + XP_U8 txtlen = *ptr++; + XP_UCHAR* text = (XP_UCHAR*)XP_MALLOC(ctxt->super.mpool, txtlen+1); + XP_MEMCPY( text, ptr, txtlen ); + ptr += txtlen; + text[txtlen] = '\0'; + XP_ASSERT( face < nSpecials ); + texts[face] = text; + + bitmaps[face].largeBM = ceMakeBitmap( ctxt, &ptr ); + bitmaps[face].smallBM = ceMakeBitmap( ctxt, &ptr ); + } + } + + ctxt->super.chars = texts; + ctxt->super.bitmaps = bitmaps; + + *ptrp = ptr; +} /* ceLoadSpecialData */ + +static XP_U16 +ceCountSpecials( CEDictionaryCtxt* ctxt ) +{ + XP_U16 result = 0; + XP_U16 i; + + for ( i = 0; i < ctxt->super.nFaces; ++i ) { + if ( IS_SPECIAL(ctxt->super.faces16[i] ) ) { + ++result; + } + } + + return result; +} /* ceCountSpecials */ + +#if 0 +static void +printBitmapData1( XP_U16 nCols, XP_U16 nRows, XP_U8* data ) +{ + char strs[20]; + XP_U16 rowBytes; + XP_U16 row, col; + + rowBytes = (nCols + 7) / 8; + while ( (rowBytes % 2) != 0 ) { + ++rowBytes; + } + + for ( row = 0; row < nRows; ++row ) { + for ( col = 0; col < nCols; ++col ) { + XP_UCHAR byt = data[col / 8]; + XP_Bool set = (byt & (0x80 >> (col % 8))) != 0; + + strs[col] = set? '#': '.'; + } + data += rowBytes; + + strs[nCols] = '\0'; + XP_DEBUGF( strs ); + } +} /* printBitmapData1 */ + +static void +printBitmapData2( XP_U16 nCols, XP_U16 nRows, XP_U8* data ) +{ + while ( nRows-- ) { + XP_UCHAR buf[100]; + XP_UCHAR* ptr = buf; + XP_U16 rowBytes = (nCols + 7) / 8; + while ( (rowBytes % ALIGN_COUNT) != 0 ) { + ++rowBytes; + } + + while ( rowBytes-- ) { + ptr += XP_SNPRINTF( ptr, sizeof(buf), "0x%.2x ", *data++ ); + } + XP_DEBUGF( buf ); + } +} /* printBitmapData2 */ + +static void +longSwapData( XP_U8* destBase, XP_U16 nRows, XP_U16 rowBytes ) +{ + XP_U32* longBase = (XP_U32*)destBase; + rowBytes /= 4; + + while ( nRows-- ) { + XP_U16 i; + for ( i = 0; i < rowBytes; ++i ) { + XP_U32 n = *longBase; + XP_U32 tmp = 0; + tmp |= (n >> 24) & 0x000000FF; + tmp |= (n >> 16) & 0x0000FF00; + tmp |= (n >> 8 ) & 0x00FF0000; + tmp |= (n >> 0 ) & 0xFF000000; + *longBase = tmp; + + ++longBase; + } + } +} /* longSwapData */ +#endif + +static XP_Bitmap* +ceMakeBitmap( CEDictionaryCtxt* XP_UNUSED_DBG(ctxt), XP_U8** ptrp ) +{ + XP_U8* ptr = *ptrp; + XP_U8 nCols = *ptr++; + CEBitmapInfo* bitmap = (CEBitmapInfo*)NULL; + + if ( nCols > 0 ) { + XP_U8* dest; + XP_U8* savedDest; + XP_U8 nRows = *ptr++; + XP_U16 rowBytes = (nCols+7) / 8; + XP_U8 srcByte = 0; + XP_U8 destByte = 0; + XP_U8 nBits; + XP_U16 i; + + bitmap = (CEBitmapInfo*)XP_MALLOC( ctxt->super.mpool, + sizeof(bitmap) ); + bitmap->nCols = nCols; + bitmap->nRows = nRows; + dest = XP_MALLOC( ctxt->super.mpool, rowBytes * nRows ); + bitmap->bits = savedDest = dest; + + nBits = nRows * nCols; + for ( i = 0; i < nBits; ++i ) { + XP_U8 srcBitIndex = i % 8; + XP_U8 destBitIndex = (i % nCols) % 8; + XP_U8 srcMask, bit; + + if ( srcBitIndex == 0 ) { + srcByte = *ptr++; + } + + srcMask = 1 << (7 - srcBitIndex); + bit = (srcByte & srcMask) != 0; + destByte |= bit << (7 - destBitIndex); + + /* we need to put the byte if we've filled it or if we're done + with the row */ + if ( (destBitIndex==7) || ((i%nCols) == (nCols-1)) ) { + *dest++ = destByte; + destByte = 0; + } + } + +/* printBitmapData1( nCols, nRows, savedDest ); */ +/* printBitmapData2( nCols, nRows, savedDest ); */ + } + + *ptrp = ptr; + return (XP_Bitmap*)bitmap; +} /* ceMakeBitmap */ + +static void +ceDeleteBitmap( const CEDictionaryCtxt* XP_UNUSED_DBG(ctxt), + XP_Bitmap* bitmap ) +{ + if ( !!bitmap ) { + CEBitmapInfo* bmi = (CEBitmapInfo*)bitmap; + XP_FREE( ctxt->super.mpool, bmi->bits ); + XP_FREE( ctxt->super.mpool, bmi ); + } +} + +static void +ce_dict_destroy( DictionaryCtxt* dict ) +{ + CEDictionaryCtxt* ctxt = (CEDictionaryCtxt*)dict; + XP_U16 nSpecials = ceCountSpecials( ctxt ); + XP_U16 i; + + if ( !!ctxt->super.chars ) { + for ( i = 0; i < nSpecials; ++i ) { + XP_UCHAR* text = ctxt->super.chars[i]; + if ( !!text ) { + XP_FREE( ctxt->super.mpool, text ); + } + } + XP_FREE( ctxt->super.mpool, ctxt->super.chars ); + } + if ( !!ctxt->super.bitmaps ) { + for ( i = 0; i < nSpecials; ++i ) { + ceDeleteBitmap( ctxt, ctxt->super.bitmaps[i].largeBM ); + ceDeleteBitmap( ctxt, ctxt->super.bitmaps[i].smallBM ); + } + XP_FREE( ctxt->super.mpool, ctxt->super.bitmaps ); + } + + XP_FREE( ctxt->super.mpool, ctxt->super.faces16 ); + XP_FREE( ctxt->super.mpool, ctxt->super.countsAndValues ); + XP_FREE( ctxt->super.mpool, ctxt->super.name ); + + closeMappedFile( MPPARM(ctxt->super.mpool) ctxt->mappedBase, + ctxt->mappedFile ); + XP_FREE( ctxt->super.mpool, ctxt ); +} // ce_dict_destroy + +static const XP_UCHAR* +ce_dict_getShortName( const DictionaryCtxt* dict ) +{ + const XP_UCHAR* name = dict_getName( dict ); + return bname( name ); +} /* ce_dict_getShortName */ + +static XP_U8* +openMappedFile( MPFORMAL const wchar_t* name, HANDLE* mappedFileP, + HANDLE* hFileP, XP_U32* sizep ) +{ + XP_U8* ptr = NULL; + HANDLE hFile; + +#if defined _WIN32_WCE + hFile = CreateFileForMapping( name, + GENERIC_READ, + FILE_SHARE_READ, /* (was 0: no sharing) */ + NULL, /* security */ + OPEN_EXISTING, + FILE_FLAG_RANDOM_ACCESS, + NULL ); + + if ( hFile == INVALID_HANDLE_VALUE ) { + logLastError( "CreateFileForMapping" ); + } else { + HANDLE mappedFile; + + mappedFile = CreateFileMapping( hFile, + NULL, + PAGE_READONLY, + 0, + 0, + NULL ); + + + if ( mappedFile != INVALID_HANDLE_VALUE ) { + void* mappedBase = MapViewOfFile( mappedFile, + FILE_MAP_READ, + 0, 0, 0 ); + ptr = (XP_U8*)mappedBase; + *mappedFileP = mappedFile; + *hFileP = hFile; + if ( sizep != NULL ) { + *sizep = GetFileSize( hFile, NULL ); + } + } + } +#else + hFile = CreateFile( name, + GENERIC_READ, + FILE_SHARE_READ, + NULL, /* security */ + OPEN_EXISTING, + FILE_FLAG_RANDOM_ACCESS, + NULL ); + if ( hFile != INVALID_HANDLE_VALUE ) { + + DWORD size = GetFileSize( hFile, NULL ); + + ptr = XP_MALLOC( mpool, size ); + if ( ptr != NULL ) { + DWORD nRead; + if ( ReadFile( hFile, ptr, size, &nRead, NULL ) ) { + XP_ASSERT( nRead == size ); + } else { + XP_FREE( mpool, ptr ); + ptr = NULL; + } + } + + CloseHandle( hFile ); + + *hFileP = NULL; /* nothing to close later */ + if ( sizep != NULL ) { + *sizep = size; + } + *mappedFileP = (HANDLE)ptr; + } +#endif + return ptr; +} /* openMappedFile */ + +static void +closeMappedFile( MPFORMAL XP_U8* base, + HANDLE XP_UNUSED_32(mappedFile) ) +{ +#ifdef _WIN32_WCE + UnmapViewOfFile( base ); + CloseHandle( mappedFile ); +#else + XP_FREE( mpool, base ); +#endif +} + +static XP_Bool +checkIfDictAndLegal( MPFORMAL wchar_t* path, XP_U16 pathLen, + wchar_t* name ) +{ + XP_Bool result = XP_FALSE; + XP_U16 len; + + len = wcslen(name); + + /* are the last four bytes ".xwd"? */ + if ( 0 == lstrcmp( name + len - 4, L".xwd" ) ) { + XP_U16 flags; + HANDLE mappedFile, hFile; + XP_U8* base; + wchar_t pathBuf[CE_MAX_PATH_LEN+1]; + + wcscpy( pathBuf, path ); + pathBuf[pathLen] = 0; + wcscat( pathBuf, name ); + +#ifdef DEBUG + { + char narrowName[CE_MAX_PATH_LEN+1]; + int len = wcslen( pathBuf ); + len = WideCharToMultiByte( CP_ACP, 0, pathBuf, len + 1, + narrowName, len + 1, NULL, NULL ); + } +#endif + + base = openMappedFile( MPPARM(mpool) pathBuf, &mappedFile, + &hFile, NULL ); + if ( !!base ) { + XP_U8* ptr = base; + + flags = n_ptr_tohs( &ptr ); + closeMappedFile( MPPARM(mpool) base, mappedFile ); +#ifdef NODE_CAN_4 + /* are the flags what we expect */ + result = flags == 0x0002 || flags == 0x0003; +#else + result = flags == 0x0001; +#endif + } + } + + return result; +} /* checkIfDictAndLegal */ + +static XP_Bool +locateOneDir( MPFORMAL wchar_t* path, OnePathCB cb, void* ctxt, XP_U16 nSought, + XP_U16* nFoundP ) +{ + WIN32_FIND_DATA data; + HANDLE fileH; + XP_Bool done = XP_FALSE; + XP_U16 startLen; + + lstrcat( path, L"\\" ); + startLen = wcslen(path); /* record where we were so can back up */ + lstrcat( path, L"*" ); + + XP_MEMSET( &data, 0, sizeof(data) ); + + /* Looks like I need to look at every file. If it's a directory I search + it recursively. If it's an .xwd file I check whether it's got the + right flags and if so I return its name. */ + + fileH = FindFirstFile( path, &data ); + + if ( fileH != INVALID_HANDLE_VALUE ) { + for ( ; ; ) { + + if ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0){ + + if ( ( data.cFileName[0] == '.' ) + && ( (data.cFileName[1] == '.') + || (data.cFileName[1] == '\0' ) ) ) { + /* skip . and .. */ + } else { + lstrcpy( path+startLen, data.cFileName ); + done = locateOneDir( MPPARM(mpool) path, cb, ctxt, + nSought, nFoundP ); + XP_ASSERT( done || *nFoundP < nSought ); + if ( done ) { + break; + } + } + } else if ( checkIfDictAndLegal( MPPARM(mpool) path, startLen, + data.cFileName ) ) { + XP_ASSERT( *nFoundP < nSought ); + + lstrcpy( path+startLen, data.cFileName ); + done = (*cb)( path, (*nFoundP)++, ctxt ) + || *nFoundP == nSought; + if ( done ) { + break; + } + } + + if ( !FindNextFile( fileH, &data ) ) { + XP_ASSERT( GetLastError() == ERROR_NO_MORE_FILES ); + break; + } + path[startLen] = 0; + } + + (void)FindClose( fileH ); + } + return done; +} /* locateOneDir */ + +static XP_Bool +getDictDir( wchar_t* buf, XP_U16 bufLen ) +{ + /* I wanted to use SHGetKnownFolderPath to search in \\Program + Files\\Crosswords, but perhaps it's better to search in the directory + in which the app is located. If I get CAB files working for + Smartphone, then that directory will be \\Program Files\\Crosswords. + But if users have to install files using the File Explorer it'll be + easier for them if all that's required is that the app and dictionaries + be in the same place. GetModuleFileName() supports both. + */ + + DWORD nChars = GetModuleFileName( NULL, buf, bufLen ); + XP_Bool success = nChars < bufLen; + if ( success ) { + wchar_t* lastSlash = wcsrchr( buf, '\\' ); + if ( !!lastSlash ) { + *lastSlash = 0; + } + } + +/* SHGetSpecialFolderPath(NULL,NULL,0,FALSE); */ + + return success; +} /* getDictDir */ + +XP_U16 +ceLocateNDicts( CEAppGlobals* globals, XP_U16 nSought, OnePathCB cb, + void* ctxt ) +{ + XP_U16 nFound = 0; + wchar_t path[CE_MAX_PATH_LEN+1]; + + if ( getDictDir( path, VSIZE(path) ) ) { + locateOneDir( MPPARM(globals->mpool) path, cb, ctxt, nSought, &nFound ); + } + + if ( nFound < nSought ) { + ceGetPath( globals, PROGFILES_PATH, path, VSIZE(path) ); + locateOneDir( MPPARM(globals->mpool) path, cb, ctxt, nSought, &nFound ); + } + + if ( nFound < nSought ) { + WIN32_FIND_DATA data; + HANDLE fileH; + + XP_MEMSET( &data, 0, sizeof(data) ); + + fileH = FindFirstFile( L"\\*", &data ); + while ( fileH != INVALID_HANDLE_VALUE ) { + if ( ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) + && (((data.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) != 0) ) ) { + wsprintf( path, L"\\%s\\Crosswords", data.cFileName ); + + XP_LOGW( "looking in:", path ); + locateOneDir( MPPARM(globals->mpool) path, cb, ctxt, + nSought, &nFound ); + } + if ( nFound >= nSought ) { + break; + } + + if ( !FindNextFile( fileH, &data ) ) { + break; + } + } + } + + return nFound; +} /* ceLocateNDicts */ + +typedef struct FindOneData { + wchar_t* result; + const wchar_t* sought; + XP_Bool found; +} FindOneData; + +static XP_Bool +matchShortName( const wchar_t* wPath, XP_U16 XP_UNUSED(index), void* ctxt ) +{ + FindOneData* datap = (FindOneData*)ctxt; + wchar_t buf[CE_MAX_PATH_LEN+1]; + wchar_t* name; + + XP_ASSERT( !datap->found ); + + name = wbname( buf, sizeof(buf), wPath ); + if ( 0 == wcscmp( name, datap->sought ) ) { + wcscpy( datap->result, wPath ); + datap->found = XP_TRUE; + } + return datap->found; +} /* matchShortName */ + +/* Users sometimes move dicts. Given a path to a dict that doesn't exist, See + * if another with the same short name exists somewhere else we're willing to + * look. + */ +static XP_Bool +findAlternateDict( CEAppGlobals* globals, wchar_t* path ) +{ + wchar_t shortPath[CE_MAX_PATH_LEN+1]; + FindOneData data; + + XP_MEMSET( &data, 0, sizeof(data) ); + data.sought = wbname( shortPath, sizeof(shortPath), path ); + data.result = path; + + (void)ceLocateNDicts( globals, CE_MAXDICTS, matchShortName, + &data ); + return data.found; +} /* findAlternateDict */ + +static XP_U32 +n_ptr_tohl( XP_U8** inp ) +{ + XP_U32 t; + XP_MEMCPY( &t, *inp, sizeof(t) ); + + *inp += sizeof(t); + + return XP_NTOHL(t); +} /* n_ptr_tohl */ + +static XP_U16 +n_ptr_tohs( XP_U8** inp ) +{ + XP_U16 t; + XP_MEMCPY( &t, *inp, sizeof(t) ); + + *inp += sizeof(t); + + return XP_NTOHS(t); +} /* n_ptr_tohs */ + +const XP_UCHAR* +bname( const XP_UCHAR* in ) +{ + XP_U16 len = (XP_U16)XP_STRLEN(in); + const XP_UCHAR* out = in + len - 1; + + while ( *out != '\\' && out >= in ) { + --out; + } + return out + 1; +} /* bname */ + +wchar_t* +wbname( wchar_t* buf, XP_U16 buflen, const wchar_t* in ) +{ + wchar_t* result; + + _snwprintf( buf, buflen, L"%s", in ); + result = buf + wcslen( buf ) - 1; + + /* wipe out extension */ + while ( *result != '.' ) { + --result; + XP_ASSERT( result > buf ); + } + *result = 0; + + while ( result >= buf && *result != '\\' ) { + --result; + } + + return result + 1; +} /* wbname */ + +#endif /* ifndef STUBBED_DICT */ diff --git a/xwords4/wince/cedict.h b/xwords4/wince/cedict.h new file mode 100755 index 000000000..132b46f3b --- /dev/null +++ b/xwords4/wince/cedict.h @@ -0,0 +1,51 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ + +/* Copyright 1999-2006 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CEDICT_H_ +#define _CEDICT_H_ + +#include "cemain.h" + +#define CE_MAXDICTS 0x7FFF + +typedef struct CEBitmapInfo { + XP_U8* bits; + XP_U16 nCols; + XP_U16 nRows; +} CEBitmapInfo; + +DictionaryCtxt* ce_dictionary_make(CEAppGlobals* globals, XP_UCHAR* name); +DictionaryCtxt* ce_dictionary_make_empty( CEAppGlobals* globals ); + +/* Callback: return true if done; false to continue */ +typedef XP_Bool (*OnePathCB)( const wchar_t* wPath, XP_U16 index, void* ctxt ); + +/* ceLocateNDicts: Allocate and store in bufs ptrs to up to nSought paths to + * dict files. Return the number actually found. Caller is responsible for + * making sure bufs contains nSought slots. + */ +XP_U16 ceLocateNDicts( CEAppGlobals* globals, XP_U16 nSought, + OnePathCB cb, void* ctxt ); + +/* return just the name, no extension, of dict, written to buf, pointed to by + return value (which is into buf, but not necessarily the first char.) */ +wchar_t* wbname( wchar_t* buf, XP_U16 buflen, const wchar_t* in ); + +const XP_UCHAR* bname( const XP_UCHAR* in ); +#endif diff --git a/xwords4/wince/cedraw.c b/xwords4/wince/cedraw.c new file mode 100755 index 000000000..e3e14259c --- /dev/null +++ b/xwords4/wince/cedraw.c @@ -0,0 +1,1856 @@ +/* -*- fill-column: 77; compile-command: "make TARGET_OS=wince DEBUG=TRUE"; -*- */ +/* + * Copyright 2000-2008 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* #include */ +/* #include */ + +#include + +#include /* for sprintf, etc. */ + +#include "xptypes.h" +#include "cedraw.h" +#include "board.h" +#include "draw.h" +#include "mempool.h" + +#include "cemain.h" +#include "cedict.h" +#include "cedefines.h" +#include "cedebug.h" +#include "debhacks.h" + +#ifndef DRAW_FUNC_NAME +#define DRAW_FUNC_NAME(nam) ce_draw_ ## nam +#endif + +#define FCE_TEXT_PADDING 2 +#define CE_MINI_V_PADDING 6 +#define CE_MINI_H_PADDING 8 +#define CE_MINIW_PADDING 0 +#define CE_TIMER_PADDING -2 +#define IS_TURN_VPAD 2 + +//#define DRAW_FOCUS_FRAME 1 +#ifdef DRAW_FOCUS_FRAME +# define CE_FOCUS_BORDER_WIDTH 6 +# define TREAT_AS_CURSOR(d,f) ((((f) & CELL_ISCURSOR) != 0) && !(d)->topFocus ) +#else +# define TREAT_AS_CURSOR(d,f) (((f) & CELL_ISCURSOR) != 0) +#endif + + +typedef enum { NO_FOCUS, SINGLE_FOCUS, TOP_FOCUS } CeFocusLevel; + +typedef enum { + RFONTS_TRAY + ,RFONTS_TRAYVAL + ,RFONTS_CELL + ,RFONTS_REM + ,RFONTS_PTS + ,RFONTS_SCORE + ,RFONTS_SCORE_BOLD + + ,N_RESIZE_FONTS +} RFIndex; + +typedef struct _PenColorPair { + COLORREF ref; + HGDIOBJ pen; +} PenColorPair; + +typedef struct _FontCacheEntry { + HFONT setFont; + /* NOTE: indexHt >= glyphHt. fontHt will usually be > indexHt */ + XP_U16 indexHt; /* the size we match on, the space we have */ + XP_U16 indexWidth; /* width we match on. 0 if we don't care */ + XP_U16 lfHeight; /* What CE thinks is the "size" of the font we + found to best fill indexHt */ + XP_U16 glyphHt; /* range from tops to bottoms of glyphs we use */ + XP_U16 offset; +} FontCacheEntry; + +struct CEDrawCtx { + DrawCtxVTable* vtable; + + HWND mainWin; + CEAppGlobals* globals; + const DictionaryCtxt* dict; + + COLORREF prevBkColor; + + HBRUSH brushes[CE_NUM_COLORS]; +#ifdef DRAW_FOCUS_FRAME + PenColorPair pens[CE_NUM_COLORS]; +#endif + HGDIOBJ hintPens[MAX_NUM_PLAYERS]; + + FontCacheEntry fcEntry[N_RESIZE_FONTS]; + + HBITMAP rightArrow; + HBITMAP downArrow; + HBITMAP origin; + + XP_U16 trayOwner; + XP_U16 miniLineHt; + XP_Bool scoreIsVertical; + XP_Bool topFocus; + + MPSLOT +}; + +static void ceClearToBkground( CEDrawCtx* dctx, const XP_Rect* rect ); +static void ceDrawBitmapInRect( HDC hdc, const RECT* r, HBITMAP bitmap ); +static void ceClipToRect( HDC hdc, const RECT* rt ); +static void ceClearFontCache( CEDrawCtx* dctx ); + +#ifdef DEBUG +const char* +RFI2Str( RFIndex rfi ) +{ + const char* str; +# define CASE_STR(c) case c: str = #c; break + switch( rfi ) { + CASE_STR( RFONTS_TRAY ); + CASE_STR( RFONTS_TRAYVAL ); + CASE_STR( RFONTS_CELL ); + CASE_STR( RFONTS_REM ); + CASE_STR( RFONTS_SCORE ); + CASE_STR( RFONTS_SCORE_BOLD ); + default: + str = ""; + } +# undef CASE_STR + return str; +} +#else +# define RFI2Str( rfi ) "" +#endif + +static void +XPRtoRECT( RECT* rt, const XP_Rect* xprect ) +{ + rt->left = xprect->left; + rt->top = xprect->top; + rt->right = rt->left + xprect->width; + rt->bottom = rt->top + xprect->height; +} /* XPRtoRECT */ + +#ifdef DRAW_FOCUS_FRAME +static HGDIOBJ +ceGetPen( CEDrawCtx* dctx, XP_U16 colorIndx, XP_U16 width ) +{ + PenColorPair* pair = &dctx->pens[colorIndx]; + HGDIOBJ pen = pair->pen; + COLORREF ref = dctx->globals->appPrefs.colors[colorIndx]; + + /* Make sure cached value is still good */ + if ( !!pen && (ref != pair->ref) ) { + DeleteObject( pen ); + pen = NULL; + } + + if ( !pen ) { + pen = CreatePen( PS_SOLID, width, ref ); + pair->pen = pen; + pair->ref = ref; + } + return pen; +} +#endif + +static void +ceDrawTextClipped( HDC hdc, wchar_t* buf, XP_S16 len, XP_Bool clip, + const FontCacheEntry* fce, XP_U16 left, XP_U16 top, + XP_U16 width, XP_U16 hJust ) +{ + RECT rect = { + .left = left, + .top = top, + .bottom = top + fce->glyphHt, + .right = left + width + }; + + if ( clip ) { + ceClipToRect( hdc, &rect ); + } + rect.top -= fce->offset; +/* XP_LOGF( "%s: drawing left: %ld, top: %ld, right: %ld, bottom: %ld", */ +/* __func__, rect.left, rect.top, rect.right, rect.bottom ); */ + DrawText( hdc, buf, len, &rect, DT_SINGLELINE | DT_TOP | hJust ); +} /* ceDrawTextClipped */ + +static void +ceDrawLinesClipped( HDC hdc, const FontCacheEntry* fce, XP_UCHAR* buf, + XP_Bool clip, const RECT* bounds ) +{ + XP_U16 top = bounds->top; + XP_U16 width = bounds->right - bounds->left; + + for ( ; ; ) { + XP_UCHAR* newline = strstr( buf, XP_CR ); + XP_U16 len = newline==NULL? strlen(buf): newline - buf; + wchar_t widebuf[len]; + + MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, buf, len, + widebuf, len ); + + ceDrawTextClipped( hdc, widebuf, len, clip, fce, bounds->left, top, + width, DT_CENTER ); + if ( !newline ) { + break; + } + top += fce->glyphHt + FCE_TEXT_PADDING; + buf = newline + XP_STRLEN(XP_CR); /* skip '\n' */ + } +} /* ceDrawLinesClipped */ + +/* CE doesn't have FrameRect, so we'll roll our own */ +#ifdef DRAW_FOCUS_FRAME +static int +ceDrawFocusRect( HDC hdc, CEDrawCtx* dctx, LPCRECT rect ) +{ + RECT lr = *rect; + HGDIOBJ oldObj; + HGDIOBJ pen = ceGetPen( dctx, CE_FOCUS_COLOR, CE_FOCUS_BORDER_WIDTH ); + + InsetRect( &lr, CE_FOCUS_BORDER_WIDTH/2, CE_FOCUS_BORDER_WIDTH/2 ); + + oldObj = SelectObject( hdc, pen ); + + MoveToEx( hdc, lr.left, lr.top, NULL ); + LineTo( hdc, lr.right, lr.top ); + LineTo( hdc, lr.right, lr.bottom ); + LineTo( hdc, lr.left, lr.bottom ); + LineTo( hdc, lr.left, lr.top ); + + SelectObject( hdc, oldObj ); + return 0; +} +#endif + +static void +ceClipToRect( /* CEDrawCtx* dctx, */HDC hdc, const RECT* rt ) +{ + /* +NULLREGION Region is empty. +SIMPLEREGION Region is a single rectangle. +COMPLEXREGION Region is more than one rectangle. +ERROR + */ +#if 0 + /* Docs suggest I should be able to reuse the region but it doesn't + work. At least under WINE... */ + HRGN clipRgn = dctx->clipRgn; + int ret; + if ( !clipRgn ) { + clipRgn = CreateRectRgn( rt->left, rt->top, rt->right, rt->bottom ); + dctx->clipRgn = clipRgn; + } else { + (void)SetRectRgn( clipRgn, rt->left, rt->top, rt->right, rt->bottom ); + } + (void)SelectClipRgn( hdc, clipRgn ); /* returns SIMPLEREGION */ +#else + HRGN clipRgn = CreateRectRgn( rt->left, rt->top, rt->right, rt->bottom ); + SelectClipRgn( hdc, clipRgn ); + DeleteObject( clipRgn ); +#endif +} /* ceClipToRect */ + +static void +makeTestBuf( CEDrawCtx* dctx, XP_UCHAR* buf, XP_U16 bufLen, RFIndex index ) +{ + switch( index ) { + case RFONTS_TRAY: + case RFONTS_CELL: { + Tile tile; + Tile blank = (Tile)-1; + const DictionaryCtxt* dict = dctx->dict; + XP_U16 nFaces = dict_numTileFaces( dict ); + Tile tiles[nFaces]; + XP_U16 nOut = 0; + XP_ASSERT( !!dict ); + XP_ASSERT( nFaces < bufLen ); + if ( dict_hasBlankTile(dict) ) { + blank = dict_getBlankTile( dict ); + } + for ( tile = 0; tile < nFaces; ++tile ) { + if ( tile != blank ) { + tiles[nOut++] = tile; + } + } + buf[0] = '0'; /* so can use for numbers too */ + (void)dict_tilesToString( dict, tiles, nOut, &buf[1], bufLen-1 ); + } + break; + case RFONTS_TRAYVAL: + strcpy( buf, "10" ); /* all numbers the same :-) */ + break; + case RFONTS_REM: + strcpy( buf, "Rem" ); + break; + case RFONTS_PTS: + strcpy( buf, "123p" ); + break; + case RFONTS_SCORE: + case RFONTS_SCORE_BOLD: + strcpy( buf, "0:" ); + break; + case N_RESIZE_FONTS: + XP_ASSERT(0); + } + XP_LOGF( "%s=>\"%s\"", __func__, buf ); +} /* makeTestBuf */ + +static void +ceMeasureGlyph( HDC hdc, HBRUSH white, wchar_t glyph, + XP_U16 minTopSeen, XP_U16 maxBottomSeen, + XP_U16* top, XP_U16* bottom ) +{ + SIZE size; + XP_U16 xx, yy; + XP_Bool done; + + GetTextExtentPoint32( hdc, &glyph, 1, &size ); + RECT rect = { 0, 0, size.cx, size.cy }; + FillRect( hdc, &rect, white ); + DrawText( hdc, &glyph, 1, &rect, DT_TOP | DT_LEFT ); + +#ifdef DEBUG +/* if ( logGlyphs ) { */ +/* wchar_t foo[2] = { glyph, 0 }; */ +/* char tbuf[size.cx+1]; */ +/* XP_LOGW( __func__, foo ); */ +/* for ( yy = 0; yy < size.cy; ++yy ) { */ +/* XP_MEMSET( tbuf, 0, size.cx+1 ); */ +/* for ( xx = 0; xx < size.cx; ++xx ) { */ +/* COLORREF ref = GetPixel( hdc, xx, yy ); */ +/* XP_ASSERT( ref != CLR_INVALID ); */ +/* strcat( tbuf, ref==0? " " : "x" ); */ +/* } */ +/* XP_LOGF( "line[%.2d] = %s", yy, tbuf ); */ +/* } */ +/* } */ +#endif + + /* Find out if this guy's taller than what we have */ + for ( done = XP_FALSE, yy = 0; yy < minTopSeen && !done; ++yy ) { + for ( xx = 0; xx < size.cx; ++xx ) { + COLORREF ref = GetPixel( hdc, xx, yy ); + if ( ref == CLR_INVALID ) { + break; /* done this line */ + } else if ( ref == 0 ) { /* a pixel set! */ + *top = yy; + done = XP_TRUE; + break; + } + } + } + + /* Extends lower than seen */ + for ( done = XP_FALSE, yy = size.cy - 1; yy > maxBottomSeen && !done; + --yy ) { + for ( xx = 0; xx < size.cx; ++xx ) { + COLORREF ref = GetPixel( hdc, xx, yy ); + if ( ref == CLR_INVALID ) { + break; + } else if ( ref == 0 ) { /* a pixel set! */ + *bottom = yy; + done = XP_TRUE; + break; + } + } + } +/* XP_LOGF( "%s: top: %d; bottom: %d", __func__, *top, *bottom ); */ +} /* ceMeasureGlyph */ + +static void +ceMeasureGlyphs( CEDrawCtx* dctx, HDC hdc, wchar_t* str, + XP_U16* hasMinTop, XP_U16* hasMaxBottom ) +{ + HBRUSH white = dctx->brushes[CE_WHITE_COLOR]; + XP_U16 ii; + XP_U16 len = wcslen(str); + XP_U16 minTopSeen, maxBottomSeen; + XP_U16 maxBottomIndex = 0; + XP_U16 minTopIndex = 0; + + minTopSeen = 1000; /* really large... */ + maxBottomSeen = 0; + for ( ii = 0; ii < len; ++ii ) { + XP_U16 thisTop, thisBottom; + + /* TODO: Find a way to to keep minTopIndex && maxBottomIndex the same + IFF there's a character, like Q, that has the lowest point but + as high a top as anybody else. Maybe for > until both are set, + then >= ? */ + + ceMeasureGlyph( hdc, white, str[ii], + minTopSeen, maxBottomSeen, + &thisTop, &thisBottom ); + if ( thisBottom > maxBottomSeen ) { + maxBottomSeen = thisBottom; + maxBottomIndex = ii; + } + if ( thisTop < minTopSeen ) { + minTopSeen = thisTop; + minTopIndex = ii; + } + } + + *hasMinTop = minTopIndex; + *hasMaxBottom = maxBottomIndex; +} /* ceMeasureGlyphs */ + +static void +ceClearFontCache( CEDrawCtx* dctx ) +{ + XP_U16 ii; + for ( ii = 0; ii < N_RESIZE_FONTS; ++ii ) { + if ( !!dctx->fcEntry[ii].setFont ) { + DeleteObject( dctx->fcEntry[ii].setFont ); + } + } + XP_MEMSET( &dctx->fcEntry, 0, sizeof(dctx->fcEntry) ); +} + +static void +ceFillFontInfo( const CEDrawCtx* dctx, LOGFONT* fontInfo, + XP_U16 height/* , XP_Bool bold */ ) +{ + XP_MEMSET( fontInfo, 0, sizeof(*fontInfo) ); + fontInfo->lfHeight = height; + fontInfo->lfQuality = PROOF_QUALITY; + +/* if ( bold ) { */ +/* fontInfo->lfWeight = FW_BOLD; */ +/* } else { */ +/* fontInfo->lfWeight = FW_LIGHT; */ +/* } */ + wcscpy( fontInfo->lfFaceName, +#ifdef FORCE_FONT + FORCE_FONT +#else + IS_SMARTPHONE(dctx->globals)? L"Segoe Condensed" : L"Tahoma" +#endif + ); +} + +static void +ceBestFitFont( CEDrawCtx* dctx, const XP_U16 soughtHeight, + const XP_U16 soughtWidth, /* pass 0 to ignore width */ + RFIndex index, FontCacheEntry* fce ) +{ + wchar_t widebuf[65]; + XP_U16 len; + XP_U16 hasMinTop, hasMaxBottom; + XP_Bool firstPass; + HBRUSH white = dctx->brushes[CE_WHITE_COLOR]; + HDC memDC = CreateCompatibleDC( NULL ); + HBITMAP memBM; + XP_U16 testHeight = soughtHeight * 2; + HFONT testFont = NULL; + /* For nextFromHeight and nextFromWidth, 0 means "found" */ + XP_U16 nextFromHeight = soughtHeight; + XP_U16 nextFromWidth = soughtWidth; /* starts at 0 if width ignored */ + + char sample[65]; + + makeTestBuf( dctx, sample, VSIZE(sample), index ); + len = 1 + strlen(sample); + MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, sample, len, widebuf, len ); + + memBM = CreateCompatibleBitmap( memDC, testHeight, testHeight ); + SelectObject( memDC, memBM ); + + for ( firstPass = XP_TRUE; ; ) { + XP_U16 prevHeight = testHeight; + LOGFONT fontInfo; + + if ( !!testFont ) { + DeleteObject( testFont ); + } + + ceFillFontInfo( dctx, &fontInfo, testHeight ); + testFont = CreateFontIndirect( &fontInfo ); + + if ( !!testFont ) { + XP_U16 thisHeight, top, bottom; + + SelectObject( memDC, testFont ); + + /* first time, measure all of them to determine which chars have + the set's high and low points. Note that we need to measure + even if height already fits because that's how glyphHt and top + are calculated. */ + if ( nextFromHeight > 0 || nextFromWidth > 0 ) { + if ( firstPass ) { + ceMeasureGlyphs( dctx, memDC, widebuf, &hasMinTop, + &hasMaxBottom ); + firstPass = XP_FALSE; + } + /* Thereafter, just measure the two we know about */ + ceMeasureGlyph( memDC, white, widebuf[hasMinTop], 1000, 0, + &top, &bottom ); + if ( hasMaxBottom != hasMinTop ) { + ceMeasureGlyph( memDC, white, widebuf[hasMaxBottom], + top, bottom, &top, &bottom ); + } + + thisHeight = bottom - top + 1; + + if ( nextFromHeight > 0 ) { /* skip if height already fits */ + /* If we don't meet the height test, continue based on + best guess at height. Only after height looks ok do + we try based on width */ + + if ( thisHeight > soughtHeight ) { /* height too big... */ + nextFromHeight = 1 + ((testHeight * soughtHeight) + / thisHeight); + } else { + nextFromHeight = 0; /* we're good */ + } + } + } + + if ( (soughtWidth > 0) && (nextFromWidth > 0) ) { + SIZE size; + GetTextExtentPoint32( memDC, widebuf, len-1, &size ); + + if ( size.cx > soughtWidth ) { /* width too big... */ + nextFromWidth = 1 + ((testHeight * soughtWidth) / size.cx); + } else { + nextFromWidth = 0; + } + } + + if ( (0 == nextFromWidth) && (0 == nextFromHeight) ) { + /* we get here, we have our font */ + fce->setFont = testFont; + fce->indexHt = soughtHeight; + fce->indexWidth = soughtWidth; + fce->lfHeight = testHeight; + fce->offset = top; + fce->glyphHt = thisHeight; + XP_LOGF( "Found for %s: indexHt: %d; lfHeight: %d; glyphHt: %d", + RFI2Str(index), fce->indexHt, fce->lfHeight, + fce->glyphHt ); + break; + } + + if ( nextFromHeight > 0 ) { + testHeight = nextFromHeight; + } + if ( nextFromWidth > 0 && nextFromWidth < testHeight ) { + testHeight = nextFromWidth; + } + if ( testHeight >= prevHeight ) { + /* guarantee progress regardless of rounding errors */ + testHeight = prevHeight - 1; + } + } + } + + DeleteObject( memBM ); + DeleteDC( memDC ); +} /* ceBestFitFont */ + +static const FontCacheEntry* +ceGetSizedFont( CEDrawCtx* dctx, XP_U16 height, XP_U16 width, RFIndex index ) +{ + FontCacheEntry* fce = &dctx->fcEntry[index]; + if ( (0 != height) /* 0 means use what we have */ + && (fce->indexHt != height || fce->indexWidth != width) ) { + XP_LOGF( "%s: no match for %s (have %d, want %d (width %d) " + "so recalculating", + __func__, RFI2Str(index), fce->indexHt, height, width ); + ceBestFitFont( dctx, height, width, index, fce ); + } + + XP_ASSERT( !!fce->setFont ); + return fce; +} /* ceGetSizedFont */ + +static void +ceMeasureText( CEDrawCtx* dctx, HDC hdc, const FontCacheEntry* fce, + const XP_UCHAR* str, XP_S16 padding, + XP_U16* widthP, XP_U16* heightP ) +{ + XP_U16 height = 0; + XP_U16 maxWidth = 0; + + for ( ; ; ) { + wchar_t widebuf[32]; + XP_UCHAR* nextStr = strstr( str, XP_CR ); + XP_U16 len = nextStr==NULL? strlen(str): nextStr - str; + SIZE size; + + XP_ASSERT( nextStr != str ); + + MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, str, len, + widebuf, VSIZE(widebuf) ); + GetTextExtentPoint32( hdc, widebuf, len, &size ); + + maxWidth = (XP_U16)XP_MAX( maxWidth, size.cx ); + if ( !!fce ) { + size.cy = fce->glyphHt; + padding = FCE_TEXT_PADDING; /* minimal */ + } + height += size.cy; + dctx->miniLineHt = (XP_U16)size.cy; + + if ( nextStr == NULL ) { + break; + } + height += padding; + str = nextStr + XP_STRLEN(XP_CR); /* skip '\n' */ + } + + *widthP = maxWidth; + *heightP = height; +} /* ceMeasureText */ + +static void +drawTextLines( CEDrawCtx* dctx, HDC hdc, const XP_UCHAR* text, XP_S16 padding, + const RECT* rp, int flags ) +{ + wchar_t widebuf[128]; + RECT textRt = *rp; + + for ( ; ; ) { /* draw up to the '\n' each time */ + XP_UCHAR* nextStr = strstr( text, XP_CR ); + XP_U16 len; + + if ( nextStr == NULL ) { + len = XP_STRLEN(text); + } else { + len = nextStr - text; + } + + MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, text, len, + widebuf, VSIZE(widebuf) ); + + textRt.bottom = textRt.top + dctx->miniLineHt; + + DrawText( hdc, widebuf, len, &textRt, flags ); + + if ( nextStr == NULL ) { + break; + } + textRt.top = textRt.bottom + padding; + text = nextStr + XP_STRLEN(XP_CR); + } +} /* drawTextLines */ + +static void +ceGetCharValHts( const CEDrawCtx* dctx, const XP_Rect* xprect, + XP_Bool valHidden, XP_U16* charHtP, XP_U16* valHtP ) +{ + XP_U16 visHt = xprect->height - TRAY_BORDER; + XP_U16 visWidth = xprect->width - 5; /* ??? */ + XP_U16 minHt; + XP_U16 charHt, valHt; + + /* if tiles are wider than tall we can let them overlap vertically */ + if ( valHidden ) { + valHt = 0; + charHt = visHt; + } else if ( visWidth > visHt ) { + if ( visWidth > (visHt*2) ) { + charHt = visHt; + valHt = (3*visHt) / 4; + } else { + charHt = (visHt * 4) / 5; + valHt = visHt / 2; + } + } else { + valHt = visHt / 3; + charHt = visHt - valHt; + } + + minHt = dctx->globals->cellHt - CELL_BORDER; + if ( charHt < minHt ) { + charHt = minHt; + } + + if ( !valHidden && (valHt < MIN_CELL_HEIGHT - CELL_BORDER - 2) ) { + valHt = MIN_CELL_HEIGHT - CELL_BORDER - 2; + } + *valHtP = valHt; + *charHtP = charHt; +/* XP_LOGF( "%s(width:%d, height:%d)=>char: %d, val:%d", __func__, */ +/* xprect->width, xprect->height, *charHt, *valHt ); */ +} /* ceGetCharValHts */ + +DLSTATIC XP_Bool +DRAW_FUNC_NAME(boardBegin)( DrawCtx* p_dctx, + const XP_Rect* XP_UNUSED(rect), + DrawFocusState dfs ) +{ + CEDrawCtx* dctx = (CEDrawCtx*)p_dctx; + CEAppGlobals* globals = dctx->globals; + HDC hdc = globals->hdc; + XP_Bool canDraw = !!hdc && !!dctx->dict; + if ( canDraw ) { + dctx->prevBkColor = GetBkColor( hdc ); + dctx->topFocus = dfs == DFS_TOP; + } + return canDraw; +} /* draw_boardBegin */ + +DLSTATIC void +DRAW_FUNC_NAME(objFinished)( DrawCtx* XP_UNUSED(p_dctx), + BoardObjectType XP_UNUSED(typ), + const XP_Rect* XP_UNUSED(rect), + DrawFocusState XP_UNUSED(dfs) ) +{ +#ifdef DRAW_FOCUS_FRAME + if ( (dfs == DFS_TOP) && (typ == OBJ_BOARD || typ == OBJ_TRAY) ) { + CEDrawCtx* dctx = (CEDrawCtx*)p_dctx; + CEAppGlobals* globals = dctx->globals; + HDC hdc = globals->hdc; + RECT rt; + + XPRtoRECT( &rt, rect ); + ceClipToRect( hdc, &rt ); + + ceDrawFocusRect( hdc, dctx, &rt ); + } +#endif +} + +static XP_U16 +getPlayerColor( XP_S16 player ) +{ + if ( player < 0 ) { + return CE_BLACK_COLOR; + } else { + return CE_PLAYER0_COLOR + player; + } +} /* getPlayerColor */ + +static void +ceDrawLine( HDC hdc, XP_S32 x1, XP_S32 y1, XP_S32 x2, XP_S32 y2 ) +{ + MoveToEx( hdc, x1, y1, NULL ); + LineTo( hdc, x2, y2); +} /* ceDrawLine */ + +static void +ceDrawHintBorders( CEDrawCtx* dctx, const XP_Rect* xprect, HintAtts hintAtts ) +{ + if ( hintAtts != HINT_BORDER_NONE && hintAtts != HINT_BORDER_CENTER ) { + RECT rt; + HDC hdc = dctx->globals->hdc; + HGDIOBJ pen, oldPen; + + pen = dctx->hintPens[dctx->trayOwner]; + if ( !pen ) { + COLORREF ref = dctx->globals->appPrefs.colors[CE_PLAYER0_COLOR + + dctx->trayOwner]; + pen = CreatePen( PS_SOLID, 4, ref ); /* 4 must be an even number + thx to clipping */ + dctx->hintPens[dctx->trayOwner] = pen; + } + + XPRtoRECT( &rt, xprect ); + InsetRect( &rt, 1, 1 ); + + oldPen = SelectObject( hdc, pen ); + + if ( (hintAtts & HINT_BORDER_LEFT) != 0 ) { + ceDrawLine( hdc, rt.left+1, rt.top, rt.left+1, rt.bottom+1 ); + } + if ( (hintAtts & HINT_BORDER_RIGHT) != 0 ) { + ceDrawLine( hdc, rt.right, rt.top, rt.right, rt.bottom+1 ); + } + if ( (hintAtts & HINT_BORDER_TOP) != 0 ) { + ceDrawLine( hdc, rt.left, rt.top+1, rt.right+1, rt.top+1 ); + } + if ( (hintAtts & HINT_BORDER_BOTTOM) != 0 ) { + ceDrawLine( hdc, rt.left, rt.bottom, rt.right+1, rt.bottom ); + } + (void)SelectObject( hdc, oldPen ); + } +} /* ceDrawHintBorders */ + +static void +ceSetTextColor( HDC hdc, const CEDrawCtx* dctx, XP_U16 index ) +{ + SetTextColor( hdc, dctx->globals->appPrefs.colors[index] ); +} + +static void +ceSetBkColor( HDC hdc, const CEDrawCtx* dctx, XP_U16 index ) +{ + SetBkColor( hdc, dctx->globals->appPrefs.colors[index] ); +} + +DLSTATIC XP_Bool +DRAW_FUNC_NAME(drawCell)( DrawCtx* p_dctx, const XP_Rect* xprect, + const XP_UCHAR* letters, + const XP_Bitmap XP_UNUSED(bitmap), + Tile XP_UNUSED(tile), XP_S16 owner, + XWBonusType bonus, HintAtts hintAtts, + CellFlags flags ) +{ + CEDrawCtx* dctx = (CEDrawCtx*)p_dctx; + CEAppGlobals* globals = dctx->globals; + HDC hdc = globals->hdc; + RECT rt; + RECT textRect; + XP_U16 bkIndex; + XP_U16 foreColorIndx; + XP_Bool isPending = (flags & CELL_HIGHLIGHT) != 0; + XP_Bool isFocussed = TREAT_AS_CURSOR( dctx, flags ); + XP_Bool isDragSrc = (flags & CELL_DRAGSRC) != 0; + HFONT oldFont; + const FontCacheEntry* fce; + + XP_ASSERT( !!hdc ); + + XPRtoRECT( &rt, xprect ); + ++rt.bottom; + ++rt.right; + ceClipToRect( hdc, &rt ); + + Rectangle( hdc, rt.left, rt.top, rt.right, rt.bottom ); + textRect = rt; + + InsetRect( &rt, 1, 1 ); + ceClipToRect( hdc, &rt ); + + XP_ASSERT( xprect->height == globals->cellHt ); + fce = ceGetSizedFont( dctx, xprect->height - CELL_BORDER, + 0, RFONTS_CELL ); + oldFont = SelectObject( hdc, fce->setFont ); + + /* always init to silence compiler warning */ + foreColorIndx = getPlayerColor(owner); + + if ( !isDragSrc && !!letters ) { + if ( isPending ) { + bkIndex = CE_BLACK_COLOR; + foreColorIndx = CE_WHITE_COLOR; + } else { + bkIndex = CE_TILEBACK_COLOR; + // foreColorIndx already has right val + } + } else if ( bonus == BONUS_NONE ) { + bkIndex = CE_BKG_COLOR; + } else { + bkIndex = (bonus - BONUS_DOUBLE_LETTER) + CE_BONUS0_COLOR; + } + + if ( isFocussed ) { + bkIndex = CE_FOCUS_COLOR; + } + + FillRect( hdc, &rt, dctx->brushes[bkIndex] ); + + if ( (flags&CELL_ISBLANK) != 0 ) { + /* For some reason windoze won't let me paint just the corner pixels + when certain colors are involved, but it will let me paint the + whole rect and then erase all but the corners. File this under + "don't ask, but it works". */ + RECT tmpRT; + FillRect( hdc, &rt, + dctx->brushes[isPending?CE_WHITE_COLOR:CE_BLACK_COLOR] ); + tmpRT = rt; + InsetRect( &tmpRT, 0, 2 ); + FillRect( hdc, &tmpRT, dctx->brushes[bkIndex] ); + + tmpRT = rt; + InsetRect( &tmpRT, 1, 0 ); + FillRect( hdc, &tmpRT, dctx->brushes[bkIndex] ); + } + + ceSetBkColor( hdc, dctx, bkIndex ); + + if ( !isDragSrc && !!letters && (letters[0] != '\0') ) { + wchar_t widebuf[4]; + + MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, letters, -1, + widebuf, VSIZE(widebuf) ); + + ceSetTextColor( hdc, dctx, foreColorIndx ); +#ifndef _WIN32_WCE + MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, letters, -1, + widebuf, VSIZE(widebuf) ); +#endif + ceDrawTextClipped( hdc, widebuf, -1, XP_FALSE, fce, xprect->left+1, + xprect->top+2, xprect->width, DT_CENTER ); + } else if ( (flags&CELL_ISSTAR) != 0 ) { + ceSetTextColor( hdc, dctx, CE_BLACK_COLOR ); + ceDrawBitmapInRect( hdc, &textRect, dctx->origin ); + } + + ceDrawHintBorders( dctx, xprect, hintAtts ); + + SelectObject( hdc, oldFont ); + + return XP_TRUE; +} /* ce_draw_drawCell */ + +DLSTATIC void +DRAW_FUNC_NAME(invertCell)( DrawCtx* XP_UNUSED(p_dctx), + const XP_Rect* XP_UNUSED(rect) ) +{ +} /* ce_draw_invertCell */ + +#ifdef DEBUG +#if 0 +static char* +logClipResult( int icrResult ) +{ +#define caseStr(d) case d: return #d + switch ( icrResult ) { + caseStr(SIMPLEREGION); + caseStr(COMPLEXREGION); + caseStr(NULLREGION); + caseStr(ERROR); + } +#undef caseStr + return "unknown"; +} /* logClipResult */ +#endif +#endif + +DLSTATIC XP_Bool +DRAW_FUNC_NAME(trayBegin)( DrawCtx* p_dctx, const XP_Rect* XP_UNUSED(rect), + XP_U16 owner, DrawFocusState dfs ) +{ + CEDrawCtx* dctx = (CEDrawCtx*)p_dctx; + CEAppGlobals* globals = dctx->globals; + HDC hdc = globals->hdc; + XP_Bool canDraw = !!hdc && !!dctx->dict; + if ( canDraw ) { + dctx->trayOwner = owner; + dctx->topFocus = dfs == DFS_TOP; + } + return canDraw; +} /* ce_draw_trayBegin */ + +static void +drawDrawTileGuts( DrawCtx* p_dctx, const XP_Rect* xprect, + const XP_UCHAR* letters, XP_S16 val, CellFlags flags ) +{ + CEDrawCtx* dctx = (CEDrawCtx*)p_dctx; + CEAppGlobals* globals = dctx->globals; + HDC hdc = globals->hdc; + wchar_t widebuf[4]; + RECT rt; + XP_U16 index; + XP_Bool highlighted = XP_FALSE; + CeFocusLevel focLevel; + XP_Bool isEmpty = (flags & CELL_ISEMPTY) != 0; + + if ( 0 != (flags & CELL_ISCURSOR) ) { + if ( dctx->topFocus ) { + focLevel = TOP_FOCUS; + } else { + focLevel = SINGLE_FOCUS; + } + } else { + focLevel = NO_FOCUS; + } + + XPRtoRECT( &rt, xprect ); + ceClipToRect( hdc, &rt ); + FillRect( hdc, &rt, dctx->brushes[focLevel == TOP_FOCUS?CE_FOCUS_COLOR:CE_BKG_COLOR] ); + + if ( !isEmpty || focLevel == SINGLE_FOCUS ) { /* don't draw anything unless SINGLE_FOCUS */ + XP_U16 backIndex = focLevel == NO_FOCUS? CE_TILEBACK_COLOR : CE_FOCUS_COLOR; + + ceSetBkColor( hdc, dctx, backIndex ); + + InsetRect( &rt, 1, 0 ); + ++rt.top; /* inset top but not bottom */ + Rectangle( hdc, rt.left, rt.top, rt.right, rt.bottom); /* draw frame */ + InsetRect( &rt, 1, 1 ); + + if ( !isEmpty ) { + index = getPlayerColor(dctx->trayOwner); + ceSetTextColor( hdc, dctx, index ); + + /* For some reason Rectangle isn't using the background brush to + fill, so FillRect has to get called after Rectangle. Need to + call InsetRect either way to put chars in the right place. */ + highlighted = (flags & CELL_HIGHLIGHT) != 0; + if ( highlighted ) { + /* draw thicker hilight frame */ + Rectangle( hdc, rt.left, rt.top, rt.right, rt.bottom ); + InsetRect( &rt, 1, 1 ); + } + } + + FillRect( hdc, &rt, dctx->brushes[backIndex] ); + + if ( !isEmpty ) { + const FontCacheEntry* fce; + /* Dumb to calc these when only needed once.... */ + XP_U16 valHt, charHt; + XP_Bool valHidden = 0 != (flags & CELL_VALHIDDEN); + ceGetCharValHts( dctx, xprect, valHidden, &charHt, &valHt ); + + if ( !highlighted ) { + InsetRect( &rt, 1, 1 ); + } + + if ( !!letters ) { + fce = ceGetSizedFont( dctx, charHt, 0, RFONTS_TRAY ); + HFONT oldFont = SelectObject( hdc, fce->setFont ); + MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, letters, -1, + widebuf, VSIZE(widebuf) ); + + ceDrawTextClipped( hdc, widebuf, -1, XP_TRUE, fce, + xprect->left + 4, xprect->top + 4, + xprect->width - 8, + valHidden?DT_CENTER:DT_LEFT ); + SelectObject( hdc, oldFont ); + } + + if ( val >= 0 && !valHidden ) { + fce = ceGetSizedFont( dctx, valHt, 0, RFONTS_TRAYVAL ); + HFONT oldFont = SelectObject( hdc, fce->setFont ); + swprintf( widebuf, L"%d", val ); + + ceDrawTextClipped( hdc, widebuf, -1, XP_TRUE, fce, + xprect->left + 4, + xprect->top + xprect->height - 4 - fce->glyphHt, + xprect->width - 8, DT_RIGHT ); + SelectObject( hdc, oldFont ); + } + } + } +} /* drawDrawTileGuts */ + +DLSTATIC void +DRAW_FUNC_NAME(drawTile)( DrawCtx* p_dctx, const XP_Rect* xprect, + const XP_UCHAR* letters, XP_Bitmap XP_UNUSED(bitmap), + XP_S16 val, CellFlags flags ) +{ + drawDrawTileGuts( p_dctx, xprect, letters, val, flags ); +} /* ce_draw_drawTile */ + +#ifdef POINTER_SUPPORT +DLSTATIC void +DRAW_FUNC_NAME(drawTileMidDrag)( DrawCtx* p_dctx, const XP_Rect* xprect, + const XP_UCHAR* letters, + XP_Bitmap XP_UNUSED(bitmap), + XP_S16 val, XP_U16 owner, CellFlags flags ) +{ + draw_trayBegin( p_dctx, xprect, owner, DFS_NONE ); + drawDrawTileGuts( p_dctx, xprect, letters, val, flags ); +} /* ce_draw_drawTile */ +#endif + +DLSTATIC void +DRAW_FUNC_NAME(drawTileBack)( DrawCtx* p_dctx, const XP_Rect* xprect, + CellFlags flags ) +{ + drawDrawTileGuts( p_dctx, xprect, "?", -1, flags ); +} /* ce_draw_drawTileBack */ + +DLSTATIC void +DRAW_FUNC_NAME(drawTrayDivider)( DrawCtx* p_dctx, const XP_Rect* rect, + CellFlags flags ) +{ + CEDrawCtx* dctx = (CEDrawCtx*)p_dctx; + CEAppGlobals* globals = dctx->globals; + HDC hdc = globals->hdc; + RECT rt; + XP_Bool selected = (flags & CELL_HIGHLIGHT) != 0; + XP_Bool isFocussed = TREAT_AS_CURSOR(dctx,flags); + + XPRtoRECT( &rt, rect ); + ceClipToRect( hdc, &rt ); + + if ( isFocussed ) { + FillRect( hdc, &rt, dctx->brushes[CE_FOCUS_COLOR] ); + InsetRect( &rt, 0, (rt.bottom - rt.top) >> 2 ); + } + + if ( selected ) { + Rectangle( hdc, rt.left, rt.top, rt.right, rt.bottom ); + } else { + FillRect( hdc, &rt, dctx->brushes[dctx->trayOwner+CE_PLAYER0_COLOR] ); + } +} /* ce_draw_drawTrayDivider */ + +static void +ceClearToBkground( CEDrawCtx* dctx, const XP_Rect* rect ) +{ + CEAppGlobals* globals = dctx->globals; + HDC hdc = globals->hdc; + RECT rt; + + XPRtoRECT( &rt, rect ); + + FillRect( hdc, &rt, dctx->brushes[CE_BKG_COLOR] ); +} /* ceClearToBkground */ + +/* Draw bitmap in rect. Use StretchBlt to fit it to the rect, but don't + * change the proportions. + */ +static void +ceDrawBitmapInRect( HDC hdc, const RECT* rect, HBITMAP bitmap ) +{ + BITMAP bmp; + int nBytes; + int left = rect->left; + int top = rect->top; + XP_U16 width = rect->right - left; + XP_U16 height = rect->bottom - top; + XP_U16 ii; + HDC tmpDC; + + nBytes = GetObject( bitmap, sizeof(bmp), &bmp ); + XP_ASSERT( nBytes > 0 ); + if ( nBytes == 0 ) { + logLastError( "ceDrawBitmapInRect:GetObject" ); + } + + for ( ii = 1; + ((bmp.bmWidth * ii) <= width) && ((bmp.bmHeight * ii) <= height); + ++ii ) { + /* do nothing */ + } + + if ( --ii == 0 ) { + XP_LOGF( "%s: cell too small for bitmap", __func__ ); + ii = 1; + } + + tmpDC = CreateCompatibleDC( hdc ); + SelectObject( tmpDC, bitmap ); + + (void)IntersectClipRect( tmpDC, left, top, rect->right, rect->bottom ); + + width = bmp.bmWidth * ii; + height = bmp.bmHeight * ii; + + left += ((rect->right - left) - width) / 2; + top += ((rect->bottom - top) - height) / 2; + + StretchBlt( hdc, left, top, width, height, + tmpDC, 0, 0, bmp.bmHeight, bmp.bmWidth, SRCCOPY ); + DeleteDC( tmpDC ); +} /* ceDrawBitmapInRect */ + +DLSTATIC void +DRAW_FUNC_NAME(drawBoardArrow)( DrawCtx* p_dctx, const XP_Rect* xprect, + XWBonusType cursorBonus, XP_Bool vertical, + HintAtts hintAtts, CellFlags flags ) +{ + CEDrawCtx* dctx = (CEDrawCtx*)p_dctx; + CEAppGlobals* globals = dctx->globals; + HDC hdc = globals->hdc; + RECT rt; + XP_U16 bkIndex; + HBITMAP cursor; + + XPRtoRECT( &rt, xprect ); + ++rt.bottom; + ++rt.right; + ceClipToRect( hdc, &rt ); + + Rectangle( hdc, rt.left, rt.top, rt.right, rt.bottom ); + InsetRect( &rt, 1, 1 ); + ceClipToRect( hdc, &rt ); + + if ( vertical ) { + cursor = dctx->downArrow; + } else { + cursor = dctx->rightArrow; + } + + if ( TREAT_AS_CURSOR( dctx, flags ) ) { + bkIndex = CE_FOCUS_COLOR; + } else if ( cursorBonus == BONUS_NONE ) { + bkIndex = CE_BKG_COLOR; + } else { + bkIndex = cursorBonus - BONUS_DOUBLE_LETTER + CE_BONUS0_COLOR; + } + FillRect( hdc, &rt, dctx->brushes[bkIndex] ); + ceSetBkColor( hdc, dctx, bkIndex ); + ceSetTextColor( hdc, dctx, CE_BLACK_COLOR ); + + ceDrawBitmapInRect( hdc, &rt, cursor ); + + ceDrawHintBorders( dctx, xprect, hintAtts ); +} /* ce_draw_drawBoardArrow */ + +DLSTATIC void +DRAW_FUNC_NAME(scoreBegin)( DrawCtx* p_dctx, const XP_Rect* xprect, + XP_U16 XP_UNUSED(numPlayers), + DrawFocusState XP_UNUSED(dfs) ) +{ + CEDrawCtx* dctx = (CEDrawCtx*)p_dctx; + CEAppGlobals* globals = dctx->globals; + XP_ASSERT( !!globals->hdc ); + ceSetBkColor( globals->hdc, dctx, CE_BKG_COLOR ); + + dctx->scoreIsVertical = xprect->height > xprect->width; + + /* I don't think the clip rect's set at this point but drawing seems fine + anyway.... ceClearToBkground() is definitely needed here. */ + ceClearToBkground( (CEDrawCtx*)p_dctx, xprect ); +} /* ce_draw_scoreBegin */ + +static void +formatRemText( XP_S16 nTilesLeft, XP_Bool isVertical, XP_UCHAR* buf ) +{ + const char* fmt = "Rem%s%d"; + const char* sep = isVertical? XP_CR : ":"; + + XP_ASSERT( nTilesLeft > 0 ); + sprintf( buf, fmt, sep, nTilesLeft ); +} /* formatRemText */ + +DLSTATIC void +DRAW_FUNC_NAME(measureRemText)( DrawCtx* p_dctx, const XP_Rect* xprect, + XP_S16 nTilesLeft, + XP_U16* widthP, XP_U16* heightP ) +{ + if ( nTilesLeft > 0 ) { + CEDrawCtx* dctx = (CEDrawCtx*)p_dctx; + CEAppGlobals* globals = dctx->globals; + HDC hdc = globals->hdc; + XP_UCHAR buf[16]; + const FontCacheEntry* fce; + XP_U16 height; + HFONT oldFont; + + XP_ASSERT( !!hdc ); + + formatRemText( nTilesLeft, dctx->scoreIsVertical, buf ); + + height = xprect->height - 2; /* space for border */ + if ( height > globals->cellHt - CELL_BORDER ) { + height = globals->cellHt - CELL_BORDER; + } + + fce = ceGetSizedFont( dctx, height, xprect->width - 2, RFONTS_REM ); + oldFont = SelectObject( hdc, fce->setFont ); + ceMeasureText( dctx, hdc, fce, buf, 0, widthP, heightP ); + + (void)SelectObject( hdc, oldFont ); + + /* Put back the 2 we took above */ + *heightP += 2; + *widthP += 2; + } else { + *widthP = *heightP = 0; + } +} /* ce_draw_measureRemText */ + +DLSTATIC void +DRAW_FUNC_NAME(drawRemText)( DrawCtx* p_dctx, const XP_Rect* rInner, + const XP_Rect* XP_UNUSED(rOuter), + XP_S16 nTilesLeft, XP_Bool focussed ) +{ + CEDrawCtx* dctx = (CEDrawCtx*)p_dctx; + CEAppGlobals* globals = dctx->globals; + HDC hdc = globals->hdc; + XP_UCHAR buf[16]; + HFONT oldFont; + const FontCacheEntry* fce; + RECT rt; + + formatRemText( nTilesLeft, dctx->scoreIsVertical, buf ); + + XPRtoRECT( &rt, rInner ); + if ( focussed ) { + ceSetBkColor( hdc, dctx, CE_FOCUS_COLOR ); + FillRect( hdc, &rt, dctx->brushes[CE_FOCUS_COLOR] ); + } + + InsetRect( &rt, 1, 1 ); + fce = ceGetSizedFont( dctx, 0, 0, RFONTS_REM ); + oldFont = SelectObject( hdc, fce->setFont ); + + ceDrawLinesClipped( hdc, fce, buf, XP_TRUE, &rt ); + + (void)SelectObject( hdc, oldFont ); +} /* ce_draw_drawRemText */ + +static void +ceFormatScoreText( CEDrawCtx* dctx, const DrawScoreInfo* dsi, + XP_UCHAR* buf, XP_U16 buflen ) +{ + XP_UCHAR bullet[] = {'•', '\0'}; + XP_UCHAR optPart[16]; + + /* For a horizontal scoreboard, we want *300:6* + * For a vertical, it's + * + * 300 + * 6 + * + * with IS_TURN_VPAD-height rects above and below + */ + + if ( !dsi->isTurn || dctx->scoreIsVertical ) { + bullet[0] = '\0'; + } + + if ( dsi->nTilesLeft >= 0 ) { + sprintf( optPart, "%s%d", dctx->scoreIsVertical? XP_CR : ":", + dsi->nTilesLeft ); + } else { + optPart[0] = '\0'; + } + + snprintf( buf, buflen, "%s%d%s%s", + bullet, dsi->totalScore, optPart, bullet ); +} /* ceFormatScoreText */ + +DLSTATIC void +DRAW_FUNC_NAME(measureScoreText)( DrawCtx* p_dctx, const XP_Rect* xprect, + const DrawScoreInfo* dsi, + XP_U16* widthP, XP_U16* heightP ) +{ + CEDrawCtx* dctx = (CEDrawCtx*)p_dctx; + CEAppGlobals* globals = dctx->globals; + HDC hdc = globals->hdc; + XP_UCHAR buf[32]; + HFONT oldFont; + const FontCacheEntry* fce; + XP_U16 fontHt, cellHt; + + cellHt = globals->cellHt; + if ( !dctx->scoreIsVertical ) { + cellHt -= SCORE_TWEAK; + } + fontHt = xprect->height; + if ( fontHt > cellHt ) { + fontHt = cellHt; + } + fontHt -= 2; /* for whitespace top and bottom */ + if ( !dsi->selected ) { + fontHt -= 2; /* use smaller font for non-selected */ + } + + ceFormatScoreText( dctx, dsi, buf, sizeof(buf) ); + + fce = ceGetSizedFont( dctx, fontHt, 0, + dsi->selected ? RFONTS_SCORE_BOLD:RFONTS_SCORE ); + oldFont = SelectObject( hdc, fce->setFont ); + + ceMeasureText( dctx, hdc, fce, buf, 0, widthP, heightP ); + + SelectObject( hdc, oldFont ); + + if ( dsi->isTurn && dctx->scoreIsVertical ) { + *heightP += IS_TURN_VPAD * 2; + } +} /* ce_draw_measureScoreText */ + +DLSTATIC void +DRAW_FUNC_NAME(score_drawPlayer)( DrawCtx* p_dctx, + const XP_Rect* rInner, + const XP_Rect* rOuter, + const DrawScoreInfo* dsi ) +{ + CEDrawCtx* dctx = (CEDrawCtx*)p_dctx; + CEAppGlobals* globals = dctx->globals; + HDC hdc = globals->hdc; + RECT rt; + XP_UCHAR buf[20]; + HFONT oldFont; + XP_Bool isFocussed = (dsi->flags & CELL_ISCURSOR) != 0; + XP_U16 bkIndex = isFocussed ? CE_FOCUS_COLOR : CE_BKG_COLOR; + const FontCacheEntry* fce; + + fce = ceGetSizedFont( dctx, 0, 0, + dsi->selected ? RFONTS_SCORE_BOLD:RFONTS_SCORE ); + + oldFont = SelectObject( hdc, fce->setFont ); + + ceSetTextColor( hdc, dctx, getPlayerColor(dsi->playerNum) ); + ceSetBkColor( hdc, dctx, bkIndex ); + + XPRtoRECT( &rt, rOuter ); + ceClipToRect( hdc, &rt ); + if ( isFocussed ) { + FillRect( hdc, &rt, dctx->brushes[CE_FOCUS_COLOR] ); + } + + ceFormatScoreText( dctx, dsi, buf, sizeof(buf) ); + + XPRtoRECT( &rt, rInner ); + + if ( dsi->isTurn && dctx->scoreIsVertical ) { + Rectangle( hdc, rt.left, rt.top-IS_TURN_VPAD, rt.right, rt.top ); + Rectangle( hdc, rt.left, rt.bottom, rt.right, + rt.bottom + IS_TURN_VPAD ); + + rt.top += IS_TURN_VPAD; + rt.bottom -= IS_TURN_VPAD; + } + + ceDrawLinesClipped( hdc, fce, buf, XP_TRUE, &rt ); + + SelectObject( hdc, oldFont ); +} /* ce_draw_score_drawPlayer */ + +DLSTATIC void +DRAW_FUNC_NAME(score_pendingScore)( DrawCtx* p_dctx, const XP_Rect* xprect, + XP_S16 score, XP_U16 playerNum, + CellFlags flags ) +{ + CEDrawCtx* dctx = (CEDrawCtx*)p_dctx; + CEAppGlobals* globals = dctx->globals; + HDC hdc = globals->hdc; + + wchar_t widebuf[5]; + RECT rt; + XP_Bool focussed = TREAT_AS_CURSOR(dctx,flags); + XP_U16 bkIndex = focussed ? CE_FOCUS_COLOR : CE_BKG_COLOR; + const FontCacheEntry* fce; + HFONT oldFont; + XP_U16 spareHt; + + fce = ceGetSizedFont( dctx, xprect->height, xprect->width, RFONTS_PTS ); + spareHt = xprect->height - fce->glyphHt; + + oldFont = SelectObject( hdc, fce->setFont ); + + ceSetTextColor( hdc, dctx, getPlayerColor(playerNum) ); + ceSetBkColor( hdc, dctx, bkIndex ); + + XPRtoRECT( &rt, xprect ); + ceClipToRect( hdc, &rt ); + FillRect( hdc, &rt, dctx->brushes[bkIndex] ); + + if ( score < 0 ) { + widebuf[0] = '?'; + widebuf[1] = '?'; + widebuf[2] = '\0'; + } else { + swprintf( widebuf, L"%dp", score ); + } + + ceDrawTextClipped( hdc, widebuf, -1, XP_FALSE, fce, + xprect->left, xprect->top + (spareHt/2), + xprect->width, DT_CENTER ); + + (void)SelectObject( hdc, oldFont ); +} /* ce_draw_score_pendingScore */ + +DLSTATIC void +DRAW_FUNC_NAME(drawTimer)( DrawCtx* p_dctx, const XP_Rect* rInner, + const XP_Rect* XP_UNUSED(rOuter), + XP_U16 player, XP_S16 secondsLeft ) +{ + CEDrawCtx* dctx = (CEDrawCtx*)p_dctx; + CEAppGlobals* globals = dctx->globals; + HDC hdc = globals->hdc; + XP_UCHAR buf[16]; + XP_U16 mins, secs; + RECT rt; + PAINTSTRUCT ps; + XP_Bool isNegative; + HFONT oldFont; + const FontCacheEntry* fce; + + fce = ceGetSizedFont( dctx, 0, 0, RFONTS_SCORE ); + + XPRtoRECT( &rt, rInner ); + + isNegative = secondsLeft < 0; + if ( isNegative ) { + secondsLeft *= -1; + } + + mins = secondsLeft / 60; + secs = secondsLeft % 60; + + snprintf( buf, sizeof(buf), + dctx->scoreIsVertical? "%s%.1dm" XP_CR "%.2ds" : "%s%.1d:%.2d", + isNegative? "-": "", mins, secs ); + + if ( !hdc ) { + InvalidateRect( dctx->mainWin, &rt, FALSE ); + hdc = BeginPaint( dctx->mainWin, &ps ); + } + + ceClipToRect( hdc, &rt ); + + ceSetTextColor( hdc, dctx, getPlayerColor(player) ); + ceSetBkColor( hdc, dctx, CE_BKG_COLOR ); + ceClearToBkground( dctx, rInner ); + + oldFont = SelectObject( hdc, fce->setFont ); + ++rt.top; + ceDrawLinesClipped( hdc, fce, buf, XP_TRUE, &rt ); + SelectObject( hdc, oldFont ); + + if ( !globals->hdc ) { + EndPaint( dctx->mainWin, &ps ); + } +} /* ce_draw_drawTimer */ + +DLSTATIC const XP_UCHAR* +DRAW_FUNC_NAME(getMiniWText)( DrawCtx* XP_UNUSED(p_dctx), + XWMiniTextType whichText ) +{ + XP_UCHAR* str; + + switch( whichText ) { + case BONUS_DOUBLE_LETTER: + str = "Double letter"; + break; + case BONUS_DOUBLE_WORD: + str = "Double word"; + break; + case BONUS_TRIPLE_LETTER: + str = "Triple letter"; + break; + case BONUS_TRIPLE_WORD: + str = "Triple word"; + break; + case INTRADE_MW_TEXT: + str = "Trading tiles." XP_CR "Select 'Turn done' when ready"; + break; + default: + XP_ASSERT( XP_FALSE ); + str = NULL; + break; + } + + return str; +} /* ce_draw_getMiniWText */ + +DLSTATIC void +DRAW_FUNC_NAME(measureMiniWText)( DrawCtx* p_dctx, const XP_UCHAR* str, + XP_U16* widthP, XP_U16* heightP ) +{ + CEDrawCtx* dctx = (CEDrawCtx*)p_dctx; + HDC hdc = GetDC(dctx->mainWin); + ceMeasureText( dctx, hdc, NULL, str, CE_MINIW_PADDING, widthP, heightP ); + *heightP += CE_MINI_V_PADDING; + *widthP += CE_MINI_H_PADDING; +} /* ce_draw_measureMiniWText */ + +DLSTATIC void +DRAW_FUNC_NAME(drawMiniWindow)( DrawCtx* p_dctx, const XP_UCHAR* text, + const XP_Rect* rect, + void** XP_UNUSED(closureP) ) +{ + CEDrawCtx* dctx = (CEDrawCtx*)p_dctx; + CEAppGlobals* globals = dctx->globals; + HDC hdc; + RECT rt, textRt; + PAINTSTRUCT ps; + + XPRtoRECT( &rt, rect ); + + if ( !!globals->hdc ) { + hdc = globals->hdc; + } else { + InvalidateRect( dctx->mainWin, &rt, FALSE ); + hdc = BeginPaint( dctx->mainWin, &ps ); + } + ceClipToRect( hdc, &rt ); + + ceClearToBkground( dctx, rect ); + + ceSetBkColor( hdc, dctx, CE_BKG_COLOR ); + ceSetTextColor( hdc, dctx, CE_BLACK_COLOR ); + + Rectangle( hdc, rt.left, rt.top, rt.right, rt.bottom ); + InsetRect( &rt, 1, 1 ); + Rectangle( hdc, rt.left, rt.top, rt.right, rt.bottom ); + + textRt = rt; + textRt.top += 2; + InsetRect( &textRt, 3, 0 ); + + drawTextLines( dctx, hdc, text, CE_MINIW_PADDING, &textRt, + DT_CENTER | DT_VCENTER ); + + if ( !globals->hdc ) { + EndPaint( dctx->mainWin, &ps ); + } +} /* ce_draw_drawMiniWindow */ + +DLSTATIC void +DRAW_FUNC_NAME(destroyCtxt)( DrawCtx* p_dctx ) +{ + XP_U16 ii; + CEDrawCtx* dctx = (CEDrawCtx*)p_dctx; + + for ( ii = 0; ii < CE_NUM_COLORS; ++ii ) { + DeleteObject( dctx->brushes[ii] ); +#ifdef DRAW_FOCUS_FRAME + if ( !!dctx->pens[ii].pen ) { + DeleteObject( dctx->pens[ii].pen ); + } +#endif + } + + for ( ii = 0; ii < VSIZE(dctx->hintPens); ++ii ) { + if ( !!dctx->hintPens[ii] ) { + DeleteObject( dctx->hintPens[ii] ); + } + } + + ceClearFontCache( dctx ); + + DeleteObject( dctx->rightArrow ); + DeleteObject( dctx->downArrow ); + DeleteObject( dctx->origin ); + +#ifndef DRAW_LINK_DIRECT + XP_FREE( dctx->mpool, p_dctx->vtable ); +#endif + + XP_FREE( dctx->mpool, dctx ); +} /* ce_draw_destroyCtxt */ + +DLSTATIC void +DRAW_FUNC_NAME(dictChanged)( DrawCtx* p_dctx, const DictionaryCtxt* dict ) +{ + CEDrawCtx* dctx = (CEDrawCtx*)p_dctx; + XP_ASSERT( !!dict ); + + /* If we don't yet have a dict, stick with the cache we have, which is + either empty or came from the saved game and likely belong with the + dict we're getting now. */ + if ( !!dctx->dict && !dict_tilesAreSame( dctx->dict, dict ) ) { + ceClearFontCache( dctx ); + } + dctx->dict = dict; +} + +#ifdef DRAW_LINK_DIRECT +DLSTATIC XP_Bool +DRAW_FUNC_NAME(vertScrollBoard)( DrawCtx* p_dctx, XP_Rect* rect, + XP_S16 dist, DrawFocusState XP_UNUSED(dfs) ) +{ + XP_Bool success = XP_FALSE; + /* board passes in the whole board rect, so we need to subtract from it + the height of the area to be overwritten. If dist is negative, the + dest is above the src. Otherwise it's below. */ + + CEDrawCtx* dctx = (CEDrawCtx*)p_dctx; + CEAppGlobals* globals = dctx->globals; + int destY, srcY; + RECT rt; + XP_Bool down = dist <= 0; + + XPRtoRECT( &rt, rect ); + ceClipToRect( globals->hdc, &rt ); + + if ( down ) { + srcY = rect->top; + dist = -dist; /* make it positive */ + destY = srcY + dist; + } else { + destY = rect->top; + srcY = destY + dist; + } + + success = FALSE != BitBlt( globals->hdc, /* HDC hdcDest, */ + rect->left, /* int nXDest */ + destY, + rect->width, /* width */ + rect->height - dist, /* int nHeight */ + globals->hdc, /* HDC hdcSrc */ + rect->left, /* int nXSrc */ + srcY, + SRCCOPY ); /* DWORD dwRop */ + /* need to return the rect that must still be redrawn */ + if ( success ) { + if ( !down ) { + rect->top += rect->height - dist; + } + rect->height = dist; + } + return success; +} +#else /* #ifdef DRAW_LINK_DIRECT */ +static void +ce_draw_doNothing( DrawCtx* dctx, ... ) +{ +} /* ce_draw_doNothing */ +#endif + +void +ce_draw_update( CEDrawCtx* dctx ) +{ + XP_U16 ii; + + for ( ii = 0; ii < CE_NUM_COLORS; ++ii ) { + if ( !!dctx->brushes[ii] ) { + DeleteObject( dctx->brushes[ii] ); + } + dctx->brushes[ii] = CreateSolidBrush(dctx->globals->appPrefs.colors[ii]); + } +} /* ce_drawctxt_update */ + +static void +drawColoredRect( CEDrawCtx* dctx, const RECT* invalR, XP_U16 index ) +{ + CEAppGlobals* globals = dctx->globals; + FillRect( globals->hdc, invalR, dctx->brushes[index] ); +} + +void +ce_draw_erase( CEDrawCtx* dctx, const RECT* invalR ) +{ + drawColoredRect( dctx, invalR, CE_BKG_COLOR ); +} + +void +ce_draw_focus( CEDrawCtx* dctx, const RECT* invalR ) +{ + drawColoredRect( dctx, invalR, CE_FOCUS_COLOR ); +} + +#ifndef _WIN32_WCE +HBRUSH +ce_draw_getFocusBrush( const CEDrawCtx* dctx ) +{ + return dctx->brushes[CE_FOCUS_COLOR]; +} +#endif + +CEDrawCtx* +ce_drawctxt_make( MPFORMAL HWND mainWin, CEAppGlobals* globals ) +{ + CEDrawCtx* dctx = (CEDrawCtx*)XP_MALLOC( mpool, + sizeof(*dctx) ); + XP_MEMSET( dctx, 0, sizeof(*dctx) ); + MPASSIGN(dctx->mpool, mpool); + +#ifndef DRAW_LINK_DIRECT + dctx->vtable = (DrawCtxVTable*)XP_MALLOC( mpool, sizeof(*((dctx)->vtable))); + + for ( i = 0; i < sizeof(*dctx->vtable)/4; ++i ) { + ((void**)(dctx->vtable))[i] = ce_draw_doNothing; + } + + SET_VTABLE_ENTRY( dctx->vtable, draw_destroyCtxt, ce ); + SET_VTABLE_ENTRY( dctx->vtable, draw_dictChanged, ce ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_boardBegin, ce ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawCell, ce ); + SET_VTABLE_ENTRY( dctx->vtable, draw_invertCell, ce ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_trayBegin, ce ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTile, ce ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTileBack, ce ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTrayDivider, ce ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_clearRect, ce ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawBoardArrow, ce ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_scoreBegin, ce ); + SET_VTABLE_ENTRY( dctx->vtable, draw_measureRemText, ce ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawRemText, ce ); + SET_VTABLE_ENTRY( dctx->vtable, draw_measureScoreText, ce ); + SET_VTABLE_ENTRY( dctx->vtable, draw_score_drawPlayer, ce ); + SET_VTABLE_ENTRY( dctx->vtable, draw_score_pendingScore, ce ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_objFinished, ce ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_drawTimer, ce ); + + SET_VTABLE_ENTRY( dctx->vtable, draw_getMiniWText, ce ); + SET_VTABLE_ENTRY( dctx->vtable, draw_measureMiniWText, ce ); + SET_VTABLE_ENTRY( dctx->vtable, draw_drawMiniWindow, ce ); +#endif + + dctx->mainWin = mainWin; + dctx->globals = globals; + + ce_draw_update( dctx ); + + dctx->rightArrow = LoadBitmap( globals->hInst, + MAKEINTRESOURCE(IDB_RIGHTARROW) ); + dctx->downArrow = LoadBitmap( globals->hInst, + MAKEINTRESOURCE(IDB_DOWNARROW) ); + dctx->origin = LoadBitmap( globals->hInst, + MAKEINTRESOURCE(IDB_ORIGIN) ); + + return dctx; +} /* ce_drawctxt_make */ + +void +ce_draw_toStream( const CEDrawCtx* dctx, XWStreamCtxt* stream ) +{ + XP_U16 ii; + + stream_putU8( stream, N_RESIZE_FONTS ); + for ( ii = 0; ii < N_RESIZE_FONTS; ++ii ) { + const FontCacheEntry* fce = &dctx->fcEntry[ii]; + stream_putU8( stream, fce->indexHt ); + if ( fce->indexHt > 0 ) { + stream_putU8( stream, fce->indexWidth ); + stream_putU8( stream, fce->lfHeight ); + stream_putU8( stream, fce->offset ); + stream_putU8( stream, fce->glyphHt ); + } + } +} + +//#define DROP_CACHE +void +ce_draw_fromStream( CEDrawCtx* dctx, XWStreamCtxt* stream, XP_U8 flags ) +{ + XP_U16 ii; + XP_U16 nEntries; + + ceClearFontCache( dctx ); /* no leaking! */ + + nEntries = (XP_U16)stream_getU8( stream ); + + for ( ii = 0; ii < nEntries; ++ii ) { + FontCacheEntry fce; + + fce.indexHt = (XP_U16)stream_getU8( stream ); + if ( fce.indexHt > 0 ) { + if ( flags >= CE_GAMEFILE_VERSION2 ) { + fce.indexWidth = (XP_U16)stream_getU8( stream ); + } + fce.lfHeight = (XP_U16)stream_getU8( stream ); + fce.offset = (XP_U16)stream_getU8( stream ); + fce.glyphHt = (XP_U16)stream_getU8( stream ); + + /* We need to read from the file no matter how many entries, but + only populate what we have room for -- in case N_RESIZE_FONTS + was different when file written. */ +#ifndef DROP_CACHE + if ( ii < N_RESIZE_FONTS ) { + LOGFONT fontInfo; + + ceFillFontInfo( dctx, &fontInfo, fce.lfHeight ); + fce.setFont = CreateFontIndirect( &fontInfo ); + XP_ASSERT( !!fce.setFont ); + if ( !!fce.setFont ) { + XP_MEMCPY( &dctx->fcEntry[ii], &fce, + sizeof(dctx->fcEntry[ii]) ); + } + } +#endif + } + } +} /* ce_draw_fromStream */ diff --git a/xwords4/wince/cedraw.h b/xwords4/wince/cedraw.h new file mode 100644 index 000000000..99b9e38ab --- /dev/null +++ b/xwords4/wince/cedraw.h @@ -0,0 +1,38 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4;-*- */ +/* + * Copyright 2000-2008 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CEDRAW_H_ +#define _CEDRAW_H_ + +#include "cemain.h" + +typedef struct CEDrawCtx CEDrawCtx; + +CEDrawCtx* ce_drawctxt_make( MPFORMAL HWND mainWin, CEAppGlobals* globals ); +void ce_draw_update( CEDrawCtx* dctx ); +void ce_draw_erase( CEDrawCtx* dctx, const RECT* invalR ); +void ce_draw_focus( CEDrawCtx* dctx, const RECT* invalR ); +#ifndef _WIN32_WCE +HBRUSH ce_draw_getFocusBrush( const CEDrawCtx* dctx ); +#endif + +void ce_draw_toStream( const CEDrawCtx* dctx, XWStreamCtxt* stream ); +void ce_draw_fromStream( CEDrawCtx* dctx, XWStreamCtxt* stream, XP_U8 flags ); +#endif + diff --git a/xwords4/wince/cefonts.c b/xwords4/wince/cefonts.c new file mode 100644 index 000000000..d1bb4b763 --- /dev/null +++ b/xwords4/wince/cefonts.c @@ -0,0 +1,265 @@ +/* -*- fill-column: 77; c-basic-offset: 4; compile-command: "make TARGET_OS=wince DEBUG=TRUE" -*- */ +/* + * Copyright 2004-2008 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef ALLOW_CHOOSE_FONTS + +#include +#include "stdafx.h" +#include + +#include "ceclrsel.h" +#include "ceutil.h" +#include "cedebug.h" +#include "debhacks.h" +#include "cefonts.h" + +#define MIN_FONT_SHOWN 6 +#define MAX_FONT_SHOWN 36 + +typedef struct _FontsDlgState { + CeDlgHdr dlgHdr; + HDC tmpDC; + RECT textRect; + XP_U16 fontsComboId; + XP_U16 fontSizeId; + XP_Bool inited; +} FontsDlgState; + +#ifndef _WIN32_WCE +# define HAS_ENUMFONTFAMILIESEX +#endif + +#ifdef HAS_ENUMFONTFAMILIESEX +# define ENUMFONTFAMILIES(a,b,c,d,e) EnumFontFamiliesEx((a),(b),(d),(e),0) +#else +# define ENUMFONTFAMILIES(a,b,c,d,e) EnumFontFamilies((a),(c),(d),(e)) +#endif + + +/* int CALLBACK EnumFontFamProc( */ +/* ENUMLOGFONT *lpelf, // logical-font data */ +/* NEWTEXTMETRIC *lpntm, // physical-font data */ +/* DWORD FontType, // type of font */ +/* LPARAM lParam // application-defined data */ +/* ); */ + +static int +fontProc2( ENUMLOGFONTEX* lpelfe, + NEWTEXTMETRIC/*EX*/* XP_UNUSED(lpntme), + DWORD FontType, LPARAM lParam) +{ +/* if ( !lstrcmp( L"Western", lpelfe->elfScript ) */ +/* && ((FontType & TRUETYPE_FONTTYPE) != 0 ) ) { */ + + FontsDlgState* state = (FontsDlgState*)lParam; + CEAppGlobals* globals = state->dlgHdr.globals; + + if ( 0 > SendDlgItemMessage( state->dlgHdr.hDlg, + state->fontsComboId, + FINDSTRINGEXACT(globals), -1, + (LPARAM)lpelfe->elfLogFont.lfFaceName ) ) { + SendDlgItemMessage( state->dlgHdr.hDlg, state->fontsComboId, + ADDSTRING(globals), 0, + (LPARAM)lpelfe->elfLogFont.lfFaceName ); + } +/* } */ + return 1; +} + +static int +fontProc( ENUMLOGFONTEX* lpelfe, // logical-font data + NEWTEXTMETRICEX* XP_UNUSED(lpntme), // physical-font data + DWORD XP_UNUSED(FontType), // type of font + LPARAM lParam) // application-defined data +{ + FontsDlgState* state = (FontsDlgState*)lParam; + CEAppGlobals* globals = state->dlgHdr.globals; +#ifdef HAS_ENUMFONTFAMILIESEX + LOGFONT fontInfo; + + XP_MEMSET( &fontInfo, 0, sizeof(fontInfo) ); + fontInfo.lfCharSet = DEFAULT_CHARSET; + wcscpy( fontInfo.lfFaceName, lpelfe->elfLogFont.lfFaceName ); +#endif + + ENUMFONTFAMILIES( state->tmpDC, + &fontInfo, + lpelfe->elfLogFont.lfFaceName, + fontProc2, (LPARAM)state ); + return 1; +} + +static void +ceLoadFontsInfo( FontsDlgState* state ) +{ + LOGFONT fontInfo; + XP_U16 ii; + HWND hDlg = state->dlgHdr.hDlg; + CEAppGlobals* globals = state->dlgHdr.globals; + + XP_MEMSET( &fontInfo, 0, sizeof(fontInfo) ); + fontInfo.lfCharSet = DEFAULT_CHARSET; + + state->tmpDC = CreateCompatibleDC( NULL ); + + XP_LOGF( "%s: calling EnumFontFamilies", __func__ ); + ENUMFONTFAMILIES( state->tmpDC, + &fontInfo, + NULL, + fontProc, + (LPARAM)state ); + + DeleteDC( state->tmpDC ); + state->tmpDC = NULL; + SendDlgItemMessage( hDlg, state->fontsComboId, SETCURSEL(globals), 0, 0L ); + + /* Stuff the size list */ + for ( ii = MIN_FONT_SHOWN; ii <= MAX_FONT_SHOWN; ++ii ) { + wchar_t widebuf[4]; + swprintf( widebuf, L"%d", ii ); + SendDlgItemMessage( hDlg, state->fontSizeId, + ADDSTRING(globals), 0, (LPARAM)widebuf ); + } + SendDlgItemMessage( hDlg, state->fontSizeId, SETCURSEL(globals), 0, 0L ); +} + +static void +ceDrawWithFont( FontsDlgState* state, HDC hdc ) +{ + HWND hDlg = state->dlgHdr.hDlg; + CEAppGlobals* globals = state->dlgHdr.globals; + XP_S16 selFont = SendDlgItemMessage( hDlg, state->fontsComboId, + GETCURSEL(globals), 0, 0L); + XP_S16 selSize = SendDlgItemMessage( hDlg, state->fontSizeId, + GETCURSEL(globals), 0, 0L); + if ( selFont >= 0 && selSize >= 0 ) { + LOGFONT fontInfo; + wchar_t fontName[33]; + HFONT oldFont, newFont; + RECT rect; + wchar_t buf[16]; + wchar_t* lines[] = { + buf + ,L"ABCDEFGHIJKL" + ,L"MNOPQRSTUV" + ,L"WXYZ0123456789" + }; + XP_U16 ii; + + (void)SendDlgItemMessage( hDlg, state->fontsComboId, + GETLBTEXT(globals), selFont, + (LPARAM)fontName ); + + XP_MEMSET( &fontInfo, 0, sizeof(fontInfo) ); + wcscpy( fontInfo.lfFaceName, fontName ); + fontInfo.lfHeight = selSize + MIN_FONT_SHOWN; + swprintf( buf, L"Size: %d", fontInfo.lfHeight ); + + newFont = CreateFontIndirect( &fontInfo ); + oldFont = SelectObject( hdc, newFont ); + + rect = state->textRect; + for ( ii = 0; ii < sizeof(lines)/sizeof(lines[0]); ++ii ) { + DrawText( hdc, lines[ii], -1, &rect, + DT_SINGLELINE | DT_TOP | DT_LEFT ); + rect.top += MAX_FONT_SHOWN - 4; + } + + SelectObject( hdc, oldFont ); + } +} + +LRESULT CALLBACK +FontsDlgProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam ) +{ + FontsDlgState* state; + BOOL result = FALSE; + + if ( message == WM_INITDIALOG ) { + SetWindowLongPtr( hDlg, GWL_USERDATA, lParam ); + + state = (FontsDlgState*)lParam; + state->inited = XP_FALSE; + + state->fontsComboId = LB_IF_PPC(state->dlgHdr.globals,FONTS_COMBO); + state->fontSizeId = LB_IF_PPC(state->dlgHdr.globals,FONTSIZE_COMBO); + + XP_LOGF( "calling ceDlgSetup" ); + ceDlgSetup( &state->dlgHdr, hDlg, DLG_STATE_NONE ); + XP_LOGF( "ceDlgSetup done" ); + + result = TRUE; + } else { + state = (FontsDlgState*)GetWindowLongPtr( hDlg, GWL_USERDATA ); + if ( !!state ) { + if ( !state->inited ) { + state->inited = XP_TRUE; + ceLoadFontsInfo( state ); + } + + if ( ceDoDlgHandle( &state->dlgHdr, message, wParam, lParam) ) { + result = TRUE; + } else if ( WM_NOTIFY == message ) { + InvalidateRect( hDlg, &state->textRect, TRUE ); + } else if ( message == WM_COMMAND ) { + XP_U16 wid = LOWORD(wParam); + if ( CBN_SELCHANGE == HIWORD(wParam) ) { + if ( state->fontsComboId == wid ) { + InvalidateRect( hDlg, &state->textRect, TRUE ); + } else if ( state->fontSizeId == wid ) { + InvalidateRect( hDlg, &state->textRect, TRUE ); + } + } else if ( BN_CLICKED == HIWORD(wParam) ) { + switch( wid ) { + case IDOK: + case IDCANCEL: + EndDialog( hDlg, LOWORD(wParam) ); + result = TRUE; + break; + } + } + } else if ( message == WM_PAINT ) { + PAINTSTRUCT ps; + HDC hdc = BeginPaint( hDlg, &ps); + ceDrawWithFont( state, hdc ); + EndPaint( hDlg, &ps ); + result = FALSE; + } + } + } + return result; +} + +void +ceShowFonts( HWND hDlg, CEAppGlobals* globals ) +{ + FontsDlgState state; + XP_MEMSET( &state, 0, sizeof(state) ); + state.dlgHdr.globals = globals; + + state.textRect.left = 5; + state.textRect.top = 60; + state.textRect.right = 300; + state.textRect.bottom = state.textRect.top + (4*MAX_FONT_SHOWN); + + (void)DialogBoxParam( globals->hInst, (LPCTSTR)IDD_FONTSSDLG, hDlg, + (DLGPROC)FontsDlgProc, (long)&state ); +} + +#endif diff --git a/xwords4/wince/cefonts.h b/xwords4/wince/cefonts.h new file mode 100644 index 000000000..0176c4d12 --- /dev/null +++ b/xwords4/wince/cefonts.h @@ -0,0 +1,29 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2004 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CEFONTS_H_ +#define _CEFONTS_H_ + +#include "xptypes.h" +#include "cemain.h" + +void ceShowFonts( HWND hDlg, CEAppGlobals* globals ); + + +#endif diff --git a/xwords4/wince/ceginfo.c b/xwords4/wince/ceginfo.c new file mode 100755 index 000000000..543f8aa6d --- /dev/null +++ b/xwords4/wince/ceginfo.c @@ -0,0 +1,732 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; compile-command: "make TARGET_OS=wince DEBUG=TRUE"; -*- */ +/* + * Copyright 2002-2008 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include /* swprintf */ +#include "ceginfo.h" +#include "cemain.h" +#include "ceutil.h" +#include "cedict.h" +#include "cecondlg.h" +#include "strutils.h" +#include "cedebug.h" +#include "strutils.h" + +#define NUM_COLS 4 +#define MENUDICTS_INCR 16 + +static XP_S16 +findInsertPoint( const wchar_t* wPath, wchar_t** menuDicts, + XP_U16 nMenuDicts ) +{ + XP_S16 loc = 0; /* simple case: nothing here */ + + if ( nMenuDicts > 0 ) { + wchar_t thisShortBuf[CE_MAX_PATH_LEN+1]; + wchar_t* thisShortName = wbname( thisShortBuf, sizeof(thisShortBuf), + wPath ); + + /* If the short path doesn't already exist, find where it belongs. This + is wasteful if we're doing this a lot since the short path isn't + cached. */ + for ( /* loc = 0*/; loc < nMenuDicts; ++loc ) { + wchar_t oneShortBuf[CE_MAX_PATH_LEN+1]; + wchar_t* oneShortName = wbname( oneShortBuf, sizeof(oneShortBuf), + menuDicts[loc] ); + int diff = _wcsicmp( thisShortName, oneShortName ); + if ( diff > 0 ) { + continue; + } else if ( diff == 0 ) { + loc = -1; + } + break; + } + } + + return loc; +} /* findInsertPoint */ + +static XP_Bool +addDictToState( const wchar_t* wPath, XP_U16 XP_UNUSED(index), void* ctxt ) +{ + GameInfoState* giState = (GameInfoState*)ctxt; + /* Let's display only the short form, but save the whole path */ + wchar_t* wstr; + XP_U16 len; + XP_S16 loc; /* < 0 means skip it */ + + loc = findInsertPoint( wPath, giState->menuDicts, + giState->nMenuDicts ); + + if ( loc >= 0 ) { + /* make a copy of the long name */ + len = wcslen( wPath ) + 1; + wstr = (wchar_t*)XP_MALLOC( giState->dlgHdr.globals->mpool, + len * sizeof(wstr[0]) ); + + XP_MEMCPY( wstr, wPath, len*sizeof(wstr[0]) ); + if ( !giState->menuDicts ) { + XP_ASSERT( giState->nMenuDicts == 0 ); + XP_ASSERT( giState->capMenuDicts == 0 ); + giState->capMenuDicts = MENUDICTS_INCR; + giState->menuDicts + = (wchar_t**)XP_MALLOC( giState->dlgHdr.globals->mpool, + giState->capMenuDicts + * sizeof(giState->menuDicts[0]) ); + } else if ( giState->nMenuDicts == giState->capMenuDicts ) { + giState->capMenuDicts += MENUDICTS_INCR; + giState->menuDicts + = (wchar_t**)XP_REALLOC( giState->dlgHdr.globals->mpool, + giState->menuDicts, + giState->capMenuDicts + * sizeof(giState->menuDicts[0]) ); + } + + if ( loc < giState->nMenuDicts ) { + XP_MEMMOVE( &giState->menuDicts[loc+1], &giState->menuDicts[loc], + (giState->nMenuDicts - loc) + * sizeof(giState->menuDicts[0]) ); + } + giState->menuDicts[loc] = wstr; + ++giState->nMenuDicts; + } + + return XP_FALSE; +} /* addDictToState */ + +static void +addDictsToMenu( GameInfoState* giState ) +{ + wchar_t* shortname; + wchar_t shortPath[CE_MAX_PATH_LEN+1]; + XP_U16 i, nMenuDicts = giState->nMenuDicts; + XP_S16 sel = 0; + CEAppGlobals* globals = giState->dlgHdr.globals; + + /* insert the short names in the menu */ + for ( i = 0; i < nMenuDicts; ++i ) { + wchar_t* wPath = giState->menuDicts[i]; + shortname = wbname( shortPath, sizeof(shortPath), wPath ); + SendDlgItemMessage( giState->dlgHdr.hDlg, giState->dictListId, + ADDSTRING(globals), 0, (long)shortname ); + + if ( giState->newDictName[0] != 0 && sel == 0 ) { + XP_UCHAR buf[CE_MAX_PATH_LEN+1]; + WideCharToMultiByte( CP_ACP, 0, wPath, -1, buf, sizeof(buf), + NULL, NULL ); + if ( 0 == XP_STRCMP( buf, giState->newDictName ) ) { + sel = i; + } + } + } + + SendDlgItemMessage( giState->dlgHdr.hDlg, giState->dictListId, + SETCURSEL(globals), sel, 0L ); +} /* addDictsToMenu */ + +static void +cleanupGameInfoState( GameInfoState* giState ) +{ + if ( !!giState->menuDicts ) { + XP_U16 nMenuDicts = giState->nMenuDicts; + XP_U16 i; + for ( i = 0; i < nMenuDicts; ++i ) { + XP_FREE( giState->dlgHdr.globals->mpool, giState->menuDicts[i] ); + } + XP_FREE( giState->dlgHdr.globals->mpool, giState->menuDicts ); + giState->menuDicts = NULL; + } + + if ( !!giState->moveIds ) { + XP_FREE( giState->dlgHdr.globals->mpool, giState->moveIds ); + giState->moveIds = NULL; + } +} /* cleanupGameInfoState */ + +static void +loadFromGameInfo( GameInfoState* giState ) +{ + XP_U16 i; + CEAppGlobals* globals = giState->dlgHdr.globals; + CurGameInfo* gi = &globals->gameInfo; + +#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH + wchar_t* roles[] = { L"Standalone", L"Host", L"Guest" }; + for ( i = 0; i < VSIZE(roles); ++i ) { + SendDlgItemMessage( hDlg, IDC_ROLECOMBO, CB_ADDSTRING, 0, + (long)roles[i] ); + } +#endif + + for ( i = 0; i < MAX_NUM_PLAYERS; ++i ) { + wchar_t widebuf[8]; + /* put a string in the moronic combobox */ + swprintf( widebuf, L"%d", i + 1 ); + SendDlgItemMessage( giState->dlgHdr.hDlg, giState->nPlayersId, + ADDSTRING(globals), 0, + (long)widebuf ); + } + + newg_load( giState->newGameCtx, gi ); + +#ifndef STUBBED_DICT + if ( !!gi->dictName ) { + XP_MEMCPY( giState->newDictName, gi->dictName, + (XP_U16)XP_STRLEN(gi->dictName)+1 ); + } + if ( giState->isNewGame ) { + (void)ceLocateNDicts( globals, CE_MAXDICTS, addDictToState, giState ); + } else { + wchar_t wPath[CE_MAX_PATH_LEN+1]; + XP_ASSERT( gi->dictName[0] != '\0' ); + MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, gi->dictName, -1, + wPath, VSIZE(wPath) ); + (void)addDictToState( wPath, 0, giState ); + } + addDictsToMenu( giState ); +#endif + + if ( !giState->isNewGame ) { + ceEnOrDisable( giState->dlgHdr.hDlg, giState->dictListId, XP_FALSE ); + } +} /* loadFromGameInfo */ + +static XP_Bool +stateToGameInfo( GameInfoState* giState ) +{ + CEAppGlobals* globals = giState->dlgHdr.globals; + CurGameInfo* gi = &globals->gameInfo; + HWND hDlg = giState->dlgHdr.hDlg; + XP_Bool timerOn; + XP_Bool success = newg_store( giState->newGameCtx, gi, XP_TRUE ); + + if ( success ) { + + /* dictionary */ { + int sel; + sel = SendDlgItemMessage( hDlg, giState->dictListId, + GETCURSEL(globals), 0, 0L ); + if ( sel >= 0 ) { + WideCharToMultiByte( CP_ACP, 0, giState->menuDicts[sel], -1, + giState->newDictName, + sizeof(giState->newDictName), NULL, NULL ); + } + replaceStringIfDifferent( globals->mpool, &gi->dictName, + giState->newDictName ); + } + + /* timer */ + timerOn = ceGetChecked( hDlg, TIMER_CHECK ); + gi->timerEnabled = timerOn; + if ( timerOn ) { + XP_UCHAR numBuf[10]; + XP_U16 len = sizeof(numBuf); + ceGetDlgItemText( hDlg, TIMER_EDIT, numBuf, &len ); + if ( len > 0 ) { + XP_U16 num = atoi( numBuf ); + gi->gameSeconds = num * 60; + } + } + + /* preferences */ + if ( giState->prefsChanged ) { + loadCurPrefsFromState( globals, &globals->appPrefs, gi, + &giState->prefsPrefs ); + } + } + + return success; +} /* stateToGameInfo */ + +#ifndef DM_RESETSCROLL +//http://www.nah6.com/~itsme/cvs-xdadevtools/itsutils/src/its_windows_message_list.txt +# define DM_RESETSCROLL 0x0402 +#endif + +static void +raiseForHiddenPlayers( GameInfoState* giState, XP_U16 nPlayers ) +{ + HWND hDlg = giState->dlgHdr.hDlg; + XP_U16 ii; + XP_S16 moveY; + + if ( nPlayers != giState->prevNPlayers ) { + if ( !giState->moveIds ) { + XP_S16 ids[32]; + HWND child; + RECT rect; + XP_U16 playersBottom; + + ceGetItemRect( hDlg, NAME_EDIT4, &rect ); + playersBottom = rect.bottom; + ceGetItemRect( hDlg, NAME_EDIT3, &rect ); + giState->playersSpacing = playersBottom - rect.bottom; + + for ( child = GetWindow( hDlg, GW_CHILD ), ii = 0; + !!child; + child = GetWindow( child, GW_HWNDNEXT ) ) { + XP_S16 resID = GetDlgCtrlID( child ); + if ( resID > 0 ) { + ceGetItemRect( hDlg, resID, &rect ); + if ( rect.top > playersBottom ) { + XP_ASSERT( ii < VSIZE(ids)-1 ); + ids[ii] = resID; + ++ii; + } + } + } + giState->moveIds = XP_MALLOC( giState->dlgHdr.globals->mpool, + sizeof(giState->moveIds[0]) * ii ); + XP_MEMCPY( giState->moveIds, ids, + sizeof(giState->moveIds[0]) * ii ); + giState->nMoveIds = ii; + } + + moveY = giState->playersSpacing * (nPlayers - giState->prevNPlayers); + for ( ii = 0; ii < giState->nMoveIds; ++ii ) { + ceMoveItem( hDlg, giState->moveIds[ii], 0, moveY ); + } + giState->prevNPlayers = nPlayers; + +#ifdef _WIN32_WCE + if ( IS_SMARTPHONE(giState->dlgHdr.globals) ) { + SendMessage( hDlg, DM_RESETSCROLL, (WPARAM)FALSE, (LPARAM)TRUE ); + } +#endif + } +} + +static void +handlePrefsButton( HWND hDlg, CEAppGlobals* globals, GameInfoState* giState ) +{ + CePrefsDlgState state; + + /* need to keep my stuff in a temporary place and to read back out of it + if launched a second time before the user's cancelled or not out of + the calling dlg.*/ + + if ( WrapPrefsDialog( hDlg, globals, &state, &giState->prefsPrefs, + giState->isNewGame ) ) { + giState->prefsChanged = XP_TRUE; + giState->colorsChanged = state.colorsChanged; + /* nothing to do until user finally does confirm the parent dialog */ + } +} /* handlePrefsButton */ + +#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH +static void +handleConnOptionsButton( HWND hDlg, CEAppGlobals* globals, + DeviceRole role, GameInfoState* giState ) +{ + CeConnDlgState state; + + if ( WrapConnsDlg( hDlg, globals, &giState->prefsPrefs.addrRec, + role, &state ) ) { + XP_MEMCPY( &giState->prefsPrefs.addrRec, &state.addrRec, + sizeof(giState->prefsPrefs.addrRec) ); + giState->addrChanged = XP_TRUE; + } +} +#endif + +static XP_U16 +resIDForCol( XP_U16 player, NewGameColumn col ) +{ + XP_U16 resID = 0; + switch ( col ) { +#ifndef XWFEATURE_STANDALONE_ONLY + case NG_COL_REMOTE: + resID = REMOTE_CHECK1; + break; +#endif + case NG_COL_ROBOT: + resID = ROBOT_CHECK1; + break; + case NG_COL_NAME: + resID = NAME_EDIT1; + break; + case NG_COL_PASSWD: + resID = PASS_EDIT1; + break; + } + XP_ASSERT( resID != 0 ); + return resID + ( player * NUM_COLS ); +} /* resIDForCol */ + +static XP_U16 +resIDForAttr( GameInfoState* state, NewGameAttr attr ) +{ + XP_U16 resID = 0; + switch( attr ) { + case NG_ATTR_NPLAYERS: + resID = state->nPlayersId; + break; +#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH + case NG_ATTR_ROLE: + resID = IDC_ROLECOMBO; + break; + case NG_ATTR_REMHEADER: + resID = IDC_REMOTE_LABEL; + break; +#endif + case NG_ATTR_NPLAYHEADER: + resID = IDC_TOTAL_LABEL; + break; + case NG_ATTR_CANJUGGLE: + resID = GIJUGGLE_BUTTON; + break; + default: + break; + } + XP_ASSERT( resID != 0 ); + return resID; +} /* resIDForAttr */ + +static void +doForNWEnable( HWND hDlg, XP_U16 resID, XP_TriEnable enable ) +{ + XP_Bool makeVisible = enable != TRI_ENAB_HIDDEN; + ceShowOrHide( hDlg, resID, makeVisible ); + if ( makeVisible ) { + ceEnOrDisable( hDlg, resID, enable == TRI_ENAB_ENABLED ); + } +} /* doForNWEnable */ + +static void +ceEnableColProc( void* closure, XP_U16 player, NewGameColumn col, + XP_TriEnable enable ) +{ + GameInfoState* giState = (GameInfoState*)closure; + XP_U16 resID = resIDForCol( player, col ); + doForNWEnable( giState->dlgHdr.hDlg, resID, enable ); +} + +static void +ceEnableAttrProc( void* closure, NewGameAttr attr, XP_TriEnable enable ) +{ + GameInfoState* giState = (GameInfoState*)closure; + XP_U16 resID = resIDForAttr( giState, attr ); + doForNWEnable( giState->dlgHdr.hDlg, resID, enable ); +} /* ceEnableAttrProc */ + +static void +ceGetColProc( void* closure, XP_U16 player, NewGameColumn col, + NgCpCallbk cpcb, const void* cpClosure ) +{ + NGValue value; + GameInfoState* giState = (GameInfoState*)closure; + XP_U16 resID = resIDForCol( player, col ); + XP_UCHAR txt[128]; + XP_U16 len; + + switch ( col ) { +#ifndef XWFEATURE_STANDALONE_ONLY + case NG_COL_REMOTE: +#endif + case NG_COL_ROBOT: + value.ng_bool = ceGetChecked( giState->dlgHdr.hDlg, resID ); + break; + case NG_COL_NAME: + case NG_COL_PASSWD: + len = sizeof(txt); + ceGetDlgItemText( giState->dlgHdr.hDlg, resID, txt, &len ); + value.ng_cp = &txt[0]; + break; + default: + XP_ASSERT(0); + } + + (*cpcb)( value, cpClosure ); +} /* ceGetColProc */ + +static void +ceSetColProc( void* closure, XP_U16 player, NewGameColumn col, + const NGValue value ) +{ + GameInfoState* giState = (GameInfoState*)closure; + XP_U16 resID = resIDForCol( player, col ); + const XP_UCHAR* cp; + XP_UCHAR buf[16]; + + switch( col ) { + case NG_COL_PASSWD: + case NG_COL_NAME: + if ( NULL != value.ng_cp ) { + cp = value.ng_cp; + } else if ( col == NG_COL_NAME ) { + cp = buf; + snprintf( cp, sizeof(buf), "Player %d", player + 1 ); + } else { + cp = ""; + } + ceSetDlgItemText( giState->dlgHdr.hDlg, resID, cp ); + break; +#ifndef XWFEATURE_STANDALONE_ONLY + case NG_COL_REMOTE: +#endif + case NG_COL_ROBOT: + ceSetChecked( giState->dlgHdr.hDlg, resID, value.ng_bool ); + break; + default: + XP_ASSERT(0); + } +} /* ceSetColProc */ + +static void +ceSetAttrProc(void* closure, NewGameAttr attr, const NGValue value ) +{ + GameInfoState* giState = (GameInfoState*)closure; + XP_U16 resID = resIDForAttr( giState, attr ); + CEAppGlobals* globals = giState->dlgHdr.globals; + + switch ( attr ) { + case NG_ATTR_NPLAYERS: + SendDlgItemMessage( giState->dlgHdr.hDlg, resID, + SETCURSEL(globals), + value.ng_u16 - 1, 0L ); + raiseForHiddenPlayers( giState, value.ng_u16 ); + break; +#ifndef XWFEATURE_STANDALONE_ONLY + case NG_ATTR_ROLE: + SendDlgItemMessage( giState->dlgHdr.hDlg, resID, SETCURSEL(globals), + value.ng_role, 0L ); + break; +#endif + case NG_ATTR_NPLAYHEADER: + ceSetDlgItemText( giState->dlgHdr.hDlg, resID, value.ng_cp ); + break; + default: + break; + } +} /* ceSetAttrProc */ + +static XP_U16 +playerFromID( XP_U16 id, XP_U16 base ) +{ + XP_U16 player; + player = (id - base) / NUM_COLS; + return player; +} + +static void +handleColChecked( GameInfoState* giState, XP_U16 id, XP_U16 base ) +{ + NGValue value; + XP_U16 player = playerFromID( id, base ); + + value.ng_bool = ceGetChecked( giState->dlgHdr.hDlg, id ); + + newg_colChanged( giState->newGameCtx, player ); +} + +/* It's too much work at this point to get the icon button looking good, + * e.g. properly greyed-out when disabled. So I'm sticking with the "J". + * Here's the code to start with if I get more ambitious. Remember: the + * GIJUGGLE_BUTTON needs to have the BS_OWNERDRAW attribute for this to work. + */ +#ifdef OWNERDRAW_JUGGLE +static void +ceDrawIconButton( CEAppGlobals* globals, DRAWITEMSTRUCT* dis ) +{ + HBITMAP bmp = LoadBitmap( globals->hInst, + MAKEINTRESOURCE(IDB_JUGGLEBUTTON) ); + if ( !!bmp ) { + ceDrawBitmapInRect( dis->hDC, &dis->rcItem, bmp ); + DeleteObject( bmp ); + } +} /* ceDrawColorButton */ +#endif + +static void +checkUpdateCombo( GameInfoState* giState, XP_U16 id ) +{ + if ( (id == giState->nPlayersId) + && giState->isNewGame ) { /* ignore if in info mode */ + NGValue value; + XP_U16 nPlayers = 1 + (XP_U16) + SendDlgItemMessage( giState->dlgHdr.hDlg, id, + GETCURSEL(giState->dlgHdr.globals), 0, 0L); + value.ng_u16 = nPlayers; + XP_ASSERT( !!giState->newGameCtx ); + newg_attrChanged( giState->newGameCtx, + NG_ATTR_NPLAYERS, value ); + + raiseForHiddenPlayers( giState, nPlayers ); + } +} /* checkUpdateCombo */ + +LRESULT CALLBACK +GameInfo(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + CEAppGlobals* globals; + XP_U16 id; + GameInfoState* giState; + LRESULT result = FALSE; + +/* XP_LOGF( "%s: %s(%d)", __func__, messageToStr( message ), message ); */ + + if ( message == WM_INITDIALOG ) { + SetWindowLongPtr( hDlg, GWL_USERDATA, lParam ); + giState = (GameInfoState*)lParam; + globals = giState->dlgHdr.globals; + + giState->nPlayersId = LB_IF_PPC(globals,IDC_NPLAYERSCOMBO); + giState->dictListId = LB_IF_PPC(globals,IDC_DICTLIST); + giState->prevNPlayers = MAX_NUM_PLAYERS; + + ceDlgSetup( &giState->dlgHdr, hDlg, DLG_STATE_TRAPBACK ); + ceDlgComboShowHide( &giState->dlgHdr, IDC_NPLAYERSCOMBO ); + ceDlgComboShowHide( &giState->dlgHdr, IDC_DICTLIST ); + + giState->newGameCtx = newg_make( MPPARM(globals->mpool) + giState->isNewGame, + &globals->util, + ceEnableColProc, + ceEnableAttrProc, + ceGetColProc, + ceSetColProc, + ceSetAttrProc, + giState ); + + loadFromGameInfo( giState ); + loadStateFromCurPrefs( globals, &globals->appPrefs, &globals->gameInfo, + &giState->prefsPrefs ); + + if ( giState->isNewGame ) { + (void)SetWindowText( hDlg, L"New game" ); + } + + result = TRUE; + + } else { + giState = (GameInfoState*)GetWindowLongPtr( hDlg, GWL_USERDATA ); + if ( !!giState ) { + globals = giState->dlgHdr.globals; + + XP_ASSERT( hDlg == giState->dlgHdr.hDlg ); + result = ceDoDlgHandle( &giState->dlgHdr, message, wParam, lParam ); + if ( !result ) { + switch (message) { + +#ifdef OWNERDRAW_JUGGLE + case WM_DRAWITEM: /* for BS_OWNERDRAW style */ + ceDrawIconButton( globals, (DRAWITEMSTRUCT*)lParam ); + result = TRUE; + break; +#endif + + case WM_NOTIFY: + if ( !!giState->newGameCtx ) { + checkUpdateCombo( giState, LOWORD(wParam)-1 ); + } + break; + + case WM_COMMAND: + result = TRUE; + id = LOWORD(wParam); + if ( id == giState->nPlayersId ) { + if ( HIWORD(wParam) == CBN_SELCHANGE ) { + checkUpdateCombo( giState, id ); + } + } else { + switch( id ) { + case ROBOT_CHECK1: + case ROBOT_CHECK2: + case ROBOT_CHECK3: + case ROBOT_CHECK4: + handleColChecked( giState, id, ROBOT_CHECK1 ); + break; + +#ifndef XWFEATURE_STANDALONE_ONLY + case REMOTE_CHECK1: + case REMOTE_CHECK2: + case REMOTE_CHECK3: + case REMOTE_CHECK4: + handleColChecked( giState, id, REMOTE_CHECK1 ); + break; +#endif + +#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH + case IDC_ROLECOMBO: + if ( HIWORD(wParam) == CBN_SELCHANGE ) { + if ( giState->isNewGame ) { /* ignore if in info + mode */ + NGValue value; + value.ng_role = + (DeviceRole)SendDlgItemMessage( hDlg, + IDC_ROLECOMBO, + CB_GETCURSEL, 0, + 0L); + newg_attrChanged( giState->newGameCtx, + NG_ATTR_ROLE, value ); + /* If we've switched to a state where we'll be + connecting */ + if ( value.ng_role != SERVER_STANDALONE ) { + handleConnOptionsButton( hDlg, globals, + value.ng_role, + giState ); + } + } + } + break; +#endif + case GIJUGGLE_BUTTON: + XP_ASSERT( giState->isNewGame ); + /* Juggle vs switch. On Win32, updates are + coalesced so you don't see anything on screen + if you change a field then change it back. In + terms of messages, all we see here is a + WM_CTLCOLOREDIT for each field being changed. + If I post a custom event here, it comes in + *before* the WM_CTLCOLOREDIT events. Short of + a timer, which starts a race with the user, I + see no way to get notified after the drawing's + done. So for now, we switch rather than + juggle: call juggle until something actually + happens. */ + while ( !newg_juggle( giState->newGameCtx ) ) { + } + break; + + case OPTIONS_BUTTON: + handlePrefsButton( hDlg, globals, giState ); + break; + + case IDOK: + if ( !stateToGameInfo( giState ) ) { + break; + } + case IDCANCEL: + EndDialog(hDlg, id); + giState->userCancelled = id == IDCANCEL; + cleanupGameInfoState( giState ); + newg_destroy( giState->newGameCtx ); + giState->newGameCtx = NULL; + } + break; + default: + result = FALSE; + } + } + } + } + } + + return result; +} /* GameInfo */ diff --git a/xwords4/wince/ceginfo.h b/xwords4/wince/ceginfo.h new file mode 100755 index 000000000..b92dda3d1 --- /dev/null +++ b/xwords4/wince/ceginfo.h @@ -0,0 +1,60 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CEGINFO_H_ +#define _CEGINFO_H_ + +#include "stdafx.h" +#include "cemain.h" +#include "ceprefs.h" +#include "cedict.h" +#include "ceutil.h" +#include "nwgamest.h" + +typedef struct GameInfoState { + CeDlgHdr dlgHdr; + NewGameCtx* newGameCtx; + XP_UCHAR newDictName[CE_MAX_PATH_LEN+1]; + + XP_U16 capMenuDicts; + XP_U16 nMenuDicts; + wchar_t** menuDicts; + XP_U16 nPlayersId; + XP_U16 dictListId; + + XP_Bool isNewGame; /* newGame or GameInfo */ + XP_Bool userCancelled; /* OUT param */ + + XP_Bool prefsChanged; + XP_Bool colorsChanged; + XP_Bool addrChanged; + CePrefsPrefs prefsPrefs; + + /* Support for repositioning lower items based on num players */ + XP_U16* moveIds; + XP_U16 nMoveIds; + XP_U16 prevNPlayers; + XP_U16 playersSpacing; + +} GameInfoState; + + +LRESULT CALLBACK GameInfo(HWND, UINT, WPARAM, LPARAM); + +#endif diff --git a/xwords4/wince/cehntlim.c b/xwords4/wince/cehntlim.c new file mode 100755 index 000000000..3bdf6d313 --- /dev/null +++ b/xwords4/wince/cehntlim.c @@ -0,0 +1,108 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2004 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef XWFEATURE_SEARCHLIMIT + +#include +#include "cehntlim.h" + +static void +initComboBox( HintLimitsState* state, XP_U16 id, XP_U16 startVal ) +{ + HWND hDlg = state->dlgHdr.hDlg; + XP_U16 ii; + for ( ii = 0; ii < MAX_TRAY_TILES; ++ii ) { + wchar_t str[4]; + swprintf( str, L"%d", ii+1 ); + + SendDlgItemMessage( hDlg, id, INSERTSTRING(state->dlgHdr.globals), ii, (long)str ); + + if ( (ii+1) == startVal ) { + SendDlgItemMessage( hDlg, id, SETCURSEL(state->dlgHdr.globals), ii, 0L ); + } + } + +} /* initComboBox */ + +static XP_U16 +getComboValue( HintLimitsState* state, XP_U16 id ) +{ + HWND hDlg = state->dlgHdr.hDlg; + LONG result; + result = SendDlgItemMessage( hDlg, id, GETCURSEL(state->dlgHdr.globals), 0, 0L ); + if ( result == CB_ERR ) { + result = 1; + } + return (XP_U16)result + 1; /* number is 1-based but index 0-based */ +} /* getComboValue */ + +LRESULT CALLBACK +HintLimitsDlg( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam ) +{ + HintLimitsState* hState; + + if ( message == WM_INITDIALOG ) { + SetWindowLongPtr( hDlg, GWL_USERDATA, lParam ); + hState = (HintLimitsState*)lParam; + + ceDlgSetup( &hState->dlgHdr, hDlg, DLG_STATE_NONE ); + ceDlgComboShowHide( &hState->dlgHdr, HC_MIN_COMBO ); + ceDlgComboShowHide( &hState->dlgHdr, HC_MAX_COMBO ); + + return TRUE; + } else { + hState = (HintLimitsState*)GetWindowLongPtr( hDlg, GWL_USERDATA ); + if ( !!hState ) { + + if ( !hState->inited ) { + initComboBox( hState, + LB_IF_PPC(hState->dlgHdr.globals, HC_MIN_COMBO), + hState->min ); + initComboBox( hState, + LB_IF_PPC(hState->dlgHdr.globals,HC_MAX_COMBO), + hState->max ); + hState->inited = XP_TRUE; + } + + if ( ceDoDlgHandle( &hState->dlgHdr, message, wParam, lParam) ) { + return TRUE; + } + + if ( (message == WM_COMMAND) && (BN_CLICKED == HIWORD(wParam) ) ) { + XP_U16 id = LOWORD(wParam); + switch( id ) { + case IDOK: + hState->min = getComboValue( hState, + LB_IF_PPC(hState->dlgHdr.globals,HC_MIN_COMBO) ); + hState->max = getComboValue( hState, + LB_IF_PPC(hState->dlgHdr.globals,HC_MAX_COMBO) ); + case IDCANCEL: + hState->cancelled = id == IDCANCEL; + + EndDialog( hDlg, id ); + return TRUE; + } + } + } + } + + return FALSE; +} /* HintLimitsDlg */ + +#endif /* XWFEATURE_SEARCHLIMIT */ diff --git a/xwords4/wince/cehntlim.h b/xwords4/wince/cehntlim.h new file mode 100755 index 000000000..791556c1e --- /dev/null +++ b/xwords4/wince/cehntlim.h @@ -0,0 +1,39 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2004 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CEHNTLIM_H_ +#define _CEHNTLIM_H_ + +#ifdef XWFEATURE_SEARCHLIMIT + +#include "cemain.h" +#include "ceutil.h" + +typedef struct HintLimitsState { + CeDlgHdr dlgHdr; + XP_U16 min, max; + XP_Bool inited; + XP_Bool cancelled; +} HintLimitsState; + +LRESULT CALLBACK HintLimitsDlg(HWND, UINT, WPARAM, LPARAM); + +#endif /* XWFEATURE_SEARCHLIMIT */ + +#endif diff --git a/xwords4/wince/ceir.h b/xwords4/wince/ceir.h new file mode 100755 index 000000000..5f794a63d --- /dev/null +++ b/xwords4/wince/ceir.h @@ -0,0 +1,32 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CEIR_H_ +#define _CEIR_H_ + + + +#ifdef XWFEATURE_STANDALONE_ONLY +# define ce_ir_send (TransportSend)NULL +#else +XP_S16 ce_ir_send( XP_U8* buf, XP_U16 len, CommsAddrRec* addr, void* closure ); +#endif + + +#endif diff --git a/xwords4/wince/cemain.c b/xwords4/wince/cemain.c new file mode 100755 index 000000000..fec0e330a --- /dev/null +++ b/xwords4/wince/cemain.c @@ -0,0 +1,3399 @@ +/* -*- fill-column: 77; compile-command: "make -j TARGET_OS=wince DEBUG=TRUE" -*- */ +/* + * Copyright 2002-2008 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Derived from code generated by M$'s eVC++. + */ + +#include "stdafx.h" +#include "xwords4.h" +#include +#include +#include +#include /* time() */ +#include +#ifdef _WIN32_WCE +# include +#endif +/* #include */ +#include "strutils.h" + +#include "memstream.h" + +#include "cemain.h" +#include "cedefines.h" + +#include "ceginfo.h" +#include "cestrbx.h" +#include "cedict.h" +#include "ceblank.h" +#include "ceprefs.h" +#include "ceaskpwd.h" +#include "ceutil.h" +#include "ceir.h" +#include "ceclrsel.h" +#include "cehntlim.h" +#include "cedebug.h" +#include "LocalizedStrIncludes.h" +#include "debhacks.h" +#include "cesvdgms.h" +#include "cedraw.h" + +#include "dbgutil.h" + +#define MAX_LOADSTRING 100 + +#define MAX_SCROLLBAR_WIDTH 12 +#define MIN_SCROLLBAR_WIDTH 6 +#define SCROLLBARID 0x4321 /* needs to be unique! */ + +#ifdef MEM_DEBUG +# define MEMPOOL globals->mpool, +#else +# define MEMPOOL +#endif + +typedef struct FileWriteState { + CEAppGlobals* globals; + XP_UCHAR* path; +} FileWriteState; + +/* forward util function decls */ +#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH +static XP_S16 ce_send_proc( const XP_U8* buf, XP_U16 len, + const CommsAddrRec* addr, + void* closure ); + +#define CE_SEND_PROC ce_send_proc +#else +#define CE_SEND_PROC NULL +#endif + +#ifdef COMMS_HEARTBEAT +static void ce_reset_proc( void* closure ); +# define CE_RESET_PROC ce_send_proc, +#else +# define CE_RESET_PROC +#endif + +static VTableMgr* ce_util_getVTManager( XW_UtilCtxt* uc ); +static void ce_util_userError( XW_UtilCtxt* uc, UtilErrID id ); +static XP_Bool ce_util_userQuery( XW_UtilCtxt* uc, UtilQueryID id, + XWStreamCtxt* stream ); +static XWBonusType ce_util_getSquareBonus( XW_UtilCtxt* uc, + const ModelCtxt* model, + XP_U16 col, XP_U16 row ); +static XP_S16 ce_util_userPickTile( XW_UtilCtxt* uc, const PickInfo* pi, + XP_U16 playerNum, + const XP_UCHAR4* texts, XP_U16 nTiles ); +static XP_Bool ce_util_askPassword( XW_UtilCtxt* uc, const XP_UCHAR* name, + XP_UCHAR* buf, XP_U16* len ); +static void ce_util_trayHiddenChange( XW_UtilCtxt* uc, + XW_TrayVisState newState, + XP_U16 nVisibleRows ); +static void ce_util_yOffsetChange( XW_UtilCtxt* uc, XP_U16 oldOffset, + XP_U16 newOffset ); +static void ce_util_turnChanged( XW_UtilCtxt* uc ); +static void ce_util_notifyGameOver( XW_UtilCtxt* uc ); +static XP_Bool ce_util_hiliteCell( XW_UtilCtxt* uc, XP_U16 col, + XP_U16 row ); +static XP_Bool ce_util_engineProgressCallback( XW_UtilCtxt* uc ); +static void ce_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why, XP_U16 when, + XWTimerProc proc, void* closure); +static XP_Bool ce_util_altKeyDown( XW_UtilCtxt* uc ); +static void ce_util_requestTime( XW_UtilCtxt* uc ); +static XP_U32 ce_util_getCurSeconds( XW_UtilCtxt* uc ); +static DictionaryCtxt* ce_util_makeEmptyDict( XW_UtilCtxt* uc ); +#ifdef XWFEATURE_RELAY +static XWStreamCtxt* ce_util_makeStreamFromAddr( XW_UtilCtxt* uc, + XP_PlayerAddr channelNo ); +#endif +static const XP_UCHAR* ce_util_getUserString( XW_UtilCtxt* uc, + XP_U16 stringCode ); +static XP_Bool ce_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi, + XP_U16 turn, XP_Bool turnLost ); +static void ce_util_remSelected( XW_UtilCtxt* uc ); +#if defined XWFEATURE_BLUETOOTH || defined XWFEATURE_RELAY +static void ce_util_addrChange( XW_UtilCtxt* uc, const CommsAddrRec* oldAddr, + const CommsAddrRec* newAddr ); +#endif + +#ifdef XWFEATURE_SEARCHLIMIT +static XP_Bool ce_util_getTraySearchLimits( XW_UtilCtxt* uc, XP_U16* min, + XP_U16* max ); +#endif +#ifdef SHOW_PROGRESS +static void ce_util_engineStarting( XW_UtilCtxt* uc ); +static void ce_util_engineStopping( XW_UtilCtxt* uc ); +#endif + +static XP_Bool ceMsgFromStream( CEAppGlobals* globals, XWStreamCtxt* stream, + wchar_t* title, XP_U16 buttons, + XP_Bool destroy ); +static void RECTtoXPR( XP_Rect* dest, const RECT* src ); +static XP_Bool ceDoNewGame( CEAppGlobals* globals ); +static XP_Bool ceSaveCurGame( CEAppGlobals* globals, XP_Bool autoSave ); +static void closeGame( CEAppGlobals* globals ); +static void ceInitPrefs( CEAppGlobals* globals, CEAppPrefs* prefs ); +static void updateForColors( CEAppGlobals* globals ); +static XWStreamCtxt* make_generic_stream( const CEAppGlobals* globals ); +#ifdef XWFEATURE_RELAY +static void ce_send_on_close( XWStreamCtxt* stream, void* closure ); +#endif +static XP_Bool ceSetDictName( const wchar_t* wPath, XP_U16 index, void* ctxt ); +static int messageBoxStream( CEAppGlobals* globals, XWStreamCtxt* stream, + wchar_t* title, XP_U16 buttons ); +static XP_Bool ceQueryFromStream( CEAppGlobals* globals, XWStreamCtxt* stream); +static XP_Bool isDefaultName( CEAppGlobals* globals, const XP_UCHAR* name ); +static void ceSetTitleFromName( CEAppGlobals* globals ); +static void removeScrollbar( CEAppGlobals* globals ); + + +#ifndef _WIN32_WCE +/* Very basic cmdline args meant at first to let me vary the size of the + * screen. Form is of arg=digits, with no spaces and digits having to be an + * integer. Right now only width and height work: e.g. + * "wine obj_win32_dbg/xwords4.exe height=240 width=320" + */ +static XP_Bool +tryIntParam( const char* buf, const char* param, XP_U16* value ) +{ + XP_U16 len = strlen( param ); + XP_Bool found = 0 == strncmp( buf, param, len ); + if ( found ) { + *value = atoi( &buf[len] ); + } + return found; +} + +static void +parseCmdLine( const char* cmdline, XP_U16* width, XP_U16* height ) +{ + XP_U16 ii; + for ( ii = 0; ; ++ii ) { + const char* cmd; + char ch; + char buf[64]; + int len; + for ( cmd = cmdline ; ; ++cmd ) { + ch = *cmd; + if ( ch == '\0' || ch == ' ' ) { + break; + } + } + len = cmd - cmdline; + if ( len < sizeof(buf) ) { + memcpy( buf, cmdline, cmd - cmdline ); + buf[len] = '\0'; + if ( ii > 0 ) { /* skip argv[0] */ + if ( tryIntParam( buf, "width=", width ) ) { + } else if ( tryIntParam( buf, "height=", height ) ) { + } else { + XP_LOGF( "failed to match cmdline arg \"%s\"", buf ); + } + } + } + if ( ch == '\0' ) { + break; + } + cmdline = ++cmd; + } +} +#endif + +// Forward declarations of functions included in this code module: +ATOM MyRegisterClass (HINSTANCE, LPTSTR); +BOOL InitInstance (HINSTANCE, int +#ifndef _WIN32_WCE + , XP_U16, XP_U16 +#endif + ); +LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); +LRESULT CALLBACK ceAbout (HWND, UINT, WPARAM, LPARAM); + +int WINAPI +WinMain( HINSTANCE hInstance, + HINSTANCE XP_UNUSED(hPrevInstance), +#ifdef _WIN32_WCE + LPWSTR XP_UNUSED_CE(lpCmdLine), +#else + LPSTR lpCmdLine, +#endif + int nCmdShow) +{ + MSG msg; + HACCEL hAccelTable; + +#ifndef _WIN32_WCE + XP_U16 width = 320, height = 320; + parseCmdLine( lpCmdLine, &width, &height ); +#endif + + // Perform application initialization: + if (!InitInstance (hInstance, nCmdShow +#ifndef _WIN32_WCE + , width, height +#endif + )) { + return FALSE; + } + + hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_XWORDS4); + + // Main message loop. Return of 0 indicates quit message. Return of -1 + // indicates major error (so we just bail.) + while ( 0 < GetMessage(&msg, NULL, 0, 0) ) { + if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + /* This would be a good place to free up memory, close sockets, etc. */ + + LOG_RETURNF( "%d", msg.wParam ); + return msg.wParam; +} + +#ifdef __GNUC__ +int +main() +{ + LOG_FUNC(); + + return WinMain( GetModuleHandle(NULL), 0, +#ifdef _WIN32_WCE + GetCommandLineW(), +#else + GetCommandLineA(), +#endif + SW_SHOWDEFAULT ); +} +#endif + + +// +// FUNCTION: MyRegisterClass() +// +// PURPOSE: Registers the window class. +// +// COMMENTS: +// +// It is important to call this function so that the application +// will get 'well formed' small icons associated with it. +// +ATOM +MyRegisterClass(HINSTANCE hInstance, LPTSTR szWindowClass) +{ + WNDCLASS wc; + + XP_MEMSET( &wc, 0, sizeof(wc) ); + + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = (WNDPROC) WndProc; + wc.cbWndExtra = sizeof(CEAppGlobals*); + wc.hInstance = hInstance; + wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_XWORDS4)); + wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); +#ifndef _WIN32_WCE + wc.lpszMenuName = (LPCTSTR)IDM_MENU; +#endif + wc.lpszClassName = szWindowClass; + + return RegisterClass(&wc); +} + +static void +ceInitUtilFuncs( CEAppGlobals* globals ) +{ + UtilVtable* vtable = globals->util.vtable = + XP_MALLOC( globals->mpool, sizeof( UtilVtable ) ); + globals->util.closure = (void*)globals; + globals->util.gameInfo = &globals->gameInfo; + + MPASSIGN( globals->util.mpool, globals->mpool ); + + vtable->m_util_getVTManager = ce_util_getVTManager; + vtable->m_util_userError = ce_util_userError; + vtable->m_util_getSquareBonus = ce_util_getSquareBonus; + vtable->m_util_userQuery = ce_util_userQuery; + vtable->m_util_userPickTile = ce_util_userPickTile; + vtable->m_util_askPassword = ce_util_askPassword; + vtable->m_util_trayHiddenChange = ce_util_trayHiddenChange; + vtable->m_util_yOffsetChange = ce_util_yOffsetChange; + vtable->m_util_turnChanged = ce_util_turnChanged; + vtable->m_util_notifyGameOver = ce_util_notifyGameOver; + vtable->m_util_hiliteCell = ce_util_hiliteCell; + vtable->m_util_engineProgressCallback = ce_util_engineProgressCallback; + vtable->m_util_setTimer = ce_util_setTimer; + vtable->m_util_altKeyDown = ce_util_altKeyDown; + vtable->m_util_requestTime = ce_util_requestTime; + vtable->m_util_getCurSeconds = ce_util_getCurSeconds; + vtable->m_util_makeEmptyDict = ce_util_makeEmptyDict; + vtable->m_util_getUserString = ce_util_getUserString; + vtable->m_util_warnIllegalWord = ce_util_warnIllegalWord; + vtable->m_util_remSelected = ce_util_remSelected; +#ifdef XWFEATURE_RELAY + vtable->m_util_addrChange = ce_util_addrChange; + vtable->m_util_makeStreamFromAddr = ce_util_makeStreamFromAddr; +#endif +#ifdef XWFEATURE_SEARCHLIMIT + vtable->m_util_getTraySearchLimits = ce_util_getTraySearchLimits; +#endif +#ifdef SHOW_PROGRESS + vtable->m_util_engineStarting = ce_util_engineStarting; + vtable->m_util_engineStopping = ce_util_engineStopping; +#endif + +} /* ceInitUtilFuncs */ + +#ifdef CEFEATURE_CANSCROLL +# define SCROLL_SHRINK 1 + +static void +updateScrollInfo( CEAppGlobals* globals, XP_U16 nHidden ) +{ + SCROLLINFO sinfo; + + XP_MEMSET( &sinfo, 0, sizeof(sinfo) ); + sinfo.cbSize = sizeof(sinfo); + sinfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE; + sinfo.nMax = model_numRows( globals->game.model ); + sinfo.nPage = sinfo.nMax - nHidden + 1; + + (void)SetScrollInfo( globals->scrollHandle, SB_CTL, &sinfo, TRUE ); +} + +LRESULT CALLBACK +scrollWindowProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) +{ + CEAppGlobals* globals = (CEAppGlobals*)GetWindowLongPtr( hWnd, GWL_USERDATA ); + LRESULT result = 1; + +/* XP_LOGF( "%s: event=%s (%d)", __func__, messageToStr(message), message ); */ + + /* Trap key events. Left and right always shift the focus off. Up and + down shift focus off IFF they're going to be no-ops on the theory that + the user needs to get some visual feedback and on some devices the + scrollbar isn't even drawn differently when focussed. */ + if ( WM_KEYDOWN == message ) { + XP_Bool setFocus = XP_FALSE; + + if ( (VK_RIGHT == wParam) || + (VK_LEFT == wParam) || + (VK_TAB == wParam) ) { + setFocus = XP_TRUE; + } else if ( (VK_UP == wParam) || (VK_DOWN == wParam) ) { + SCROLLINFO sinfo; + sinfo.cbSize = sizeof(sinfo); + sinfo.fMask = SIF_POS | SIF_RANGE | SIF_PAGE; + GetScrollInfo( globals->scrollHandle, SB_CTL, &sinfo ); + + if ( (VK_UP == wParam) && (sinfo.nPos <= sinfo.nMin) ) { + setFocus = XP_TRUE; + } else if ( (VK_DOWN == wParam) + && (sinfo.nPos > (sinfo.nMax - sinfo.nPage)) ) { + setFocus = XP_TRUE; + } + } + + if ( setFocus ) { + SetFocus( globals->hWnd ); + result = 0; + globals->keyDown = XP_TRUE; + } + } + if ( 0 != result ) { + result = CallWindowProc( globals->oldScrollProc, hWnd, message, + wParam, lParam ); + } + return result; +} /* scrollWindowProc */ + +static void +makeScrollbar( CEAppGlobals* globals, XP_U16 nHidden, XP_U16 xx, XP_U16 yy, + XP_U16 width, XP_U16 height ) +{ + HWND hwndSB; +#ifdef _WIN32_WCE + RECT tmp; + XP_U16 rectHt; +#endif + + ++xx; + width -= 2; /* make narrower: on CE will erase cell border */ + +#ifdef _WIN32_WCE + tmp.left = xx; + tmp.right = xx + width; + rectHt = height / 10; /* each focus rect to be 1/10th height */ + + tmp.top = yy; + tmp.bottom = yy + rectHt; + XP_MEMCPY( &globals->scrollRects[0], &tmp, sizeof(globals->scrollRects[0]) ); + + tmp.bottom = yy + height; + tmp.top = tmp.bottom - rectHt; + XP_MEMCPY( &globals->scrollRects[1], &tmp, sizeof(globals->scrollRects[1]) ); + + yy += rectHt; + height -= rectHt * 2; /* above and below */ +#endif + + /* Need to destroy it, or resize it, because board size may be changing + in case where portrait display's been flipped. */ + removeScrollbar( globals ); + + hwndSB = CreateWindow( TEXT("SCROLLBAR"), // Class name + NULL, // Window text + // Window style + SBS_VERT|WS_VISIBLE|WS_CHILD, + xx, yy, width, height, globals->hWnd, + (HMENU)SCROLLBARID, // The control identifier + globals->hInst, // The instance handle + NULL ); // s'pposed to be NULL + + globals->scrollHandle = hwndSB; + + globals->oldScrollProc = (WNDPROC) GetWindowLongPtr( hwndSB, + GWL_WNDPROC ); + XP_ASSERT( 0 == GetWindowLongPtr( hwndSB, GWL_USERDATA ) ); + SetWindowLongPtr( hwndSB, GWL_WNDPROC, (LPARAM)scrollWindowProc ); + SetWindowLongPtr( hwndSB, GWL_USERDATA, (LPARAM)globals ); + + updateScrollInfo( globals, nHidden ); + EnableWindow( hwndSB, nHidden > 0 ); + + ShowWindow( globals->scrollHandle, SW_SHOW ); +} /* makeScrollbar */ + +static void +removeScrollbar( CEAppGlobals* globals ) +{ + if ( !!globals->scrollHandle ) { + DestroyWindow( globals->scrollHandle ); + globals->scrollHandle = NULL; + globals->scrollerHasFocus = XP_FALSE; + } +} /* removeScrollbar */ +#endif + +typedef struct CEBoardParms { + XP_U16 scrnWidth; + XP_U16 scrnHeight; + XP_U16 adjLeft; + XP_U16 adjTop; + + XP_U16 boardHScale; + XP_U16 boardVScale; + XP_U16 boardTop; + XP_U16 trayTop; + + XP_U16 trayHeight; + XP_U16 trayWidth; + + XP_U16 timerLeft, timerTop, timerWidth, timerHeight; + + XP_U16 boardLeft, trayLeft; + XP_U16 scoreWidth; + XP_U16 scoreHeight; + XP_U16 scrollWidth; + XP_Bool horiz; +#ifdef CEFEATURE_CANSCROLL + XP_Bool needsScroller; +#endif +} CEBoardParms; + +static void +figureBoardParms( CEAppGlobals* globals, const XP_U16 nRows, + CEBoardParms* bparms ) +{ + RECT rc; + XP_U16 scrnWidth, scrnHeight; + XP_U16 trayHeight, scoreWidth, scoreHeight; + XP_U16 hScale, vScale, nVisibleRows; + XP_U16 tmp; + XP_Bool horiz; + XP_U16 scrollWidth = 0; + XP_U16 adjLeft, adjTop; + + GetClientRect( globals->hWnd, &rc ); +#ifndef _WIN32_WCE +# if defined FORCE_HEIGHT && defined FORCE_WIDTH + rc.right = rc.left + FORCE_WIDTH; + rc.bottom = rc.top + FORCE_HEIGHT; +# else + if ( !globals->appPrefs.fullScreen ) { + if ( globals->dbWidth != 0 ) { + rc.right = rc.left + globals->dbWidth; + } + if ( globals->dbHeight != 0 ) { + rc.bottom = rc.top + globals->dbHeight; + } + } +# endif +#endif /* #ifndef _WIN32_WCE */ + + scrnWidth = (XP_U16)(rc.right - rc.left); + scrnHeight = (XP_U16)(rc.bottom - rc.top); + + XP_LOGF( "%s: scrnWidth: %d, scrnHeight: %d", __func__, + scrnWidth, scrnHeight ); + + /* Figure layout style based on proportion UNLESS there's just no room + for anything but 15 columns on the board -- because vertically is the + only dimension in which we can scroll. */ + if ( scrnWidth <= (MIN_CELL_WIDTH * (nRows+2)) ) { + horiz = XP_TRUE; + } else { + horiz = scrnHeight >= scrnWidth; + } + + /* vScale. Tray must be a few pixels taller than cells to use same + sized, minimum-sized font. Subtract out that difference here and it's + guaranteed that tray will wind up at least that much taller later + though for the rest of the calculation we reserve only a cell's height + for it. */ + vScale = (scrnHeight - (MIN_TRAY_HEIGHT-MIN_CELL_HEIGHT)) + / (nRows + (horiz?2:1)); /* horiz means scoreboard *and* tray */ + if ( vScale >= MIN_CELL_HEIGHT ) { + nVisibleRows = nRows; /* no scrolling needed */ + } else { + XP_U16 nRowsPossible = (scrnHeight-MIN_TRAY_HEIGHT) / MIN_CELL_HEIGHT; + XP_S16 num2Scroll; + num2Scroll = (nRows + (horiz?1:0)) - nRowsPossible; /* 1: scoreboard */ + XP_ASSERT( num2Scroll > 0 ); + if ( num2Scroll < 0 ) { /* no-scroll case should be caught above */ + num2Scroll = 0; + } + nVisibleRows = nRows - num2Scroll; + vScale = (scrnHeight-MIN_TRAY_HEIGHT) / (nVisibleRows + (horiz? 1:0)); + } + + tmp = nRows + (horiz ? 0 : 2); + hScale = scrnWidth / tmp; + + if ( vScale > hScale ) { + vScale = XP_MAX( MIN_CELL_HEIGHT, hScale ); + } else if ( hScale > vScale ) { + hScale = XP_MAX( MIN_CELL_WIDTH, vScale ); + } + + /* Figure out tray size */ + tmp = vScale * (nVisibleRows + (horiz? 1:0)); + trayHeight = XP_MIN( vScale * 2, scrnHeight - tmp ); + +#ifdef CEFEATURE_CANSCROLL + /* Does this need to be in a loop? */ + while ( nVisibleRows < nRows && hScale > MIN_CELL_WIDTH ) { /* need scroller? (while allows break) */ + scrollWidth = scrnWidth - (tmp * hScale); + if ( scrollWidth >= MIN_SCROLLBAR_WIDTH ) { + break; + } + --hScale; + } + if ( scrollWidth > MAX_SCROLLBAR_WIDTH ) { + scrollWidth = MAX_SCROLLBAR_WIDTH; + } +#endif + + if ( horiz ) { + scoreWidth = scrollWidth + (hScale * nRows); + scoreHeight = vScale - SCORE_TWEAK; + trayHeight += SCORE_TWEAK; + } else { + scoreWidth = XP_MIN( 2*hScale, scrnWidth - (hScale * nRows) ); + scoreHeight = (nVisibleRows * vScale) + trayHeight; + } +/* XP_LOGF( "hScale=%d; vScale=%d; trayHeight=%d", hScale, vScale, trayHeight ); */ + + if ( globals->gameInfo.timerEnabled ) { + if ( horiz ) { + bparms->timerWidth = scoreWidth / 6; /* arbitrarily, one sixth */ + scoreWidth -= bparms->timerWidth; + bparms->timerLeft = scoreWidth; + bparms->timerTop = 0; + bparms->timerHeight = scoreHeight; + } else { + bparms->timerLeft = 0; + bparms->timerHeight = vScale * 2; + bparms->timerTop = scoreHeight - bparms->timerHeight; + bparms->timerWidth = scoreWidth; + + scoreHeight -= bparms->timerHeight; + } + } + + globals->cellHt = vScale; + + /* figure actual width and height */ + tmp = scrollWidth + (hScale * nRows) + (horiz ? 0 : scoreWidth); + adjLeft = (scrnWidth - tmp)/2; + tmp = trayHeight + (vScale * nVisibleRows) + (horiz?scoreHeight:0); + adjTop = (scrnHeight - tmp)/2; + + bparms->scrnWidth = scrnWidth; + bparms->scrnHeight = scrnHeight; + bparms->adjLeft = adjLeft; + bparms->adjTop = adjTop; + bparms->boardHScale = hScale; + bparms->boardVScale = vScale; + bparms->boardTop = adjTop + (horiz? scoreHeight : 0); + bparms->trayTop = bparms->boardTop + (nVisibleRows * vScale) + 1; + bparms->trayHeight = trayHeight - 1; + bparms->trayWidth = (hScale * nRows) + scrollWidth; + bparms->boardLeft = adjLeft + (horiz ? 0 : scoreWidth); + bparms->trayLeft = bparms->boardLeft;//horiz? 0 : scoreWidth; + bparms->scoreWidth = scoreWidth; + bparms->scoreHeight = scoreHeight; + bparms->scrollWidth = scrollWidth; + bparms->horiz = horiz; + +#ifdef CEFEATURE_CANSCROLL + bparms->needsScroller = scrollWidth > 0; + if ( bparms->needsScroller ) { + XP_U16 boardRight; + boardRight = bparms->boardLeft + (nRows * hScale); + makeScrollbar( globals, nRows - nVisibleRows, + boardRight, bparms->boardTop, + scrollWidth, + vScale * nVisibleRows ); + } else { + removeScrollbar( globals ); + } +#endif +} /* figureBoardParms */ + +static void +setOwnedRects( CEAppGlobals* globals, XP_U16 nRows, + const CEBoardParms* bparms ) +{ + RECT tmp; + XP_U16 scrollWidth = bparms->scrollWidth; + + XP_MEMSET( &globals->ownedRects, 0, sizeof(globals->ownedRects) ); + + tmp.top = bparms->adjTop; /* Same for both */ + tmp.bottom = bparms->trayTop + bparms->trayHeight; /* Same for both */ + + tmp.left = 0; + tmp.right = bparms->adjLeft; + XP_MEMCPY( &globals->ownedRects[OWNED_RECT_LEFT], &tmp, + sizeof(globals->ownedRects[OWNED_RECT_LEFT]) ); + + tmp.left = tmp.right + (bparms->boardHScale * nRows) + scrollWidth; + tmp.right = bparms->scrnWidth; + XP_MEMCPY( &globals->ownedRects[OWNED_RECT_RIGHT], &tmp, + sizeof(globals->ownedRects[OWNED_RECT_RIGHT]) ); + + tmp.left = 0; + tmp.top = 0; + tmp.right = bparms->scrnWidth; + tmp.bottom = bparms->adjTop; + XP_MEMCPY( &globals->ownedRects[OWNED_RECT_TOP], &tmp, + sizeof(globals->ownedRects[OWNED_RECT_TOP]) ); + + tmp.top = bparms->trayTop + bparms->trayHeight; + tmp.bottom = bparms->scrnHeight; + XP_MEMCPY( &globals->ownedRects[OWNED_RECT_BOTTOM], &tmp, + sizeof(globals->ownedRects[OWNED_RECT_BOTTOM]) ); +} /* setOwnedRects */ + + +/* PENDING cePositionBoard gets called a lot when the screen size hasn't + changed. It'd be better to cache the size used to do layout and not + repeat those steps (including possibly nuking and rebuilding a + scrollbar). */ +static XP_Bool +cePositionBoard( CEAppGlobals* globals ) +{ + XP_Bool erase = XP_FALSE; + XP_U16 nCols; + CEBoardParms bparms; + + XP_ASSERT( !!globals->game.model ); + nCols = model_numCols( globals->game.model ); + XP_ASSERT( nCols <= CE_MAX_ROWS ); + + figureBoardParms( globals, nCols, &bparms ); + setOwnedRects( globals, nCols, &bparms ); + + if ( globals->gameInfo.timerEnabled ) { + board_setTimerLoc( globals->game.board, + bparms.adjLeft + bparms.timerLeft, + bparms.adjTop + bparms.timerTop, bparms.timerWidth, + bparms.timerHeight ); + } + + board_setPos( globals->game.board, bparms.boardLeft, + bparms.boardTop, XP_FALSE ); + board_setScale( globals->game.board, bparms.boardHScale, + bparms.boardVScale ); + + board_setScoreboardLoc( globals->game.board, bparms.adjLeft, bparms.adjTop, + bparms.scoreWidth, + bparms.scoreHeight, bparms.horiz ); + board_setShowColors( globals->game.board, globals->appPrefs.showColors ); + board_setYOffset( globals->game.board, 0 ); + + board_prefsChanged( globals->game.board, &globals->appPrefs.cp ); + + board_setTrayLoc( globals->game.board, bparms.trayLeft, bparms.trayTop, + bparms.trayWidth, bparms.trayHeight, + bparms.trayWidth/40 ); /* 1/8 of a tile width, roughly */ + + server_prefsChanged( globals->game.server, &globals->appPrefs.cp ); + +#if ! defined _WIN32_WCE && defined DEBUG + ceSetTitleFromName( globals ); +#endif + ceCheckMenus( globals ); + return erase; +} /* cePositionBoard */ + +/* Set the title. If there's a game name, replace the window title with that + * in case both won't fit. If there's no name yet, install the app name as + * title. + */ +static void +ceSetTitleFromName( CEAppGlobals* globals ) +{ + wchar_t widebuf[64]; + const XP_UCHAR* gameName = globals->curGameName; + + /* if default name, remove any current name */ + if ( !gameName || isDefaultName( globals, gameName ) ) { + LoadString( globals->hInst, IDS_APP_TITLE, widebuf, VSIZE(widebuf) ); + } else { + wchar_t* dotPos; + XP_UCHAR* baseStart = 1 + strrchr( gameName, '\\' ); + XP_U16 len = (XP_U16)XP_STRLEN( baseStart ); + + MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, baseStart, len + 1, + widebuf, len + 1 ); + + /* now get rid of the ".xwg" */ + dotPos = wcsrchr( widebuf, '.' ); + if ( dotPos != NULL ) { + *dotPos = 0; + } + } + +#if ! defined _WIN32_WCE && defined DEBUG + swprintf( &widebuf[wcslen(widebuf)], L":%dx%d", + globals->dbWidth, globals->dbHeight ); +#endif + SendMessage( globals->hWnd, WM_SETTEXT, 0, (long)widebuf ); +} /* ceSetTitleFromName */ + +static void +ceInitAndStartBoard( CEAppGlobals* globals, XP_Bool newGame, + const CommsAddrRec* XP_UNUSED_STANDALONE(addr) ) +{ + DictionaryCtxt* dict; + DictionaryCtxt* toBeDestroyed = NULL; + XP_UCHAR* newDictName = globals->gameInfo.dictName; + + /* This needs to happen even when !newGame because it's how the dict + slots in PlayerInfo get loaded. That really ought to happen earlier, + though. */ + XP_ASSERT( !!globals->game.model ); + dict = model_getDictionary( globals->game.model ); + + if ( !!dict ) { + const XP_UCHAR* curDictName = dict_getName( dict ); + if ( !!newDictName && + ( !curDictName || 0 != strcmp( curDictName, newDictName ) ) ) { + toBeDestroyed = dict; + dict = NULL; + } else { + replaceStringIfDifferent( globals->mpool, + &globals->gameInfo.dictName, + curDictName ); + } + } + + if ( !dict ) { +#ifdef STUBBED_DICT + dict = make_stubbed_dict( MPPARM_NOCOMMA(globals->mpool) ); +#else + XP_ASSERT( !!newDictName ); + XP_LOGF( "calling ce_dictionary_make" ); + dict = ce_dictionary_make( globals, newDictName); +#endif + XP_ASSERT( !!dict ); + model_setDictionary( globals->game.model, dict ); + } + + if ( newGame ) { + XP_U16 newGameID = 0; + game_reset( MEMPOOL &globals->game, &globals->gameInfo, &globals->util, + newGameID, &globals->appPrefs.cp, CE_SEND_PROC, + CE_RESET_PROC globals ); + +#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH + if ( !!addr ) { + XP_ASSERT( globals->game.comms != NULL ); + comms_setAddr( globals->game.comms, addr ); + } +#endif + } + + XP_ASSERT( !!globals->game.board ); + ceSizeIfFullscreen( globals, globals->hWnd ); + (void)cePositionBoard( globals ); + + board_invalAll( globals->game.board ); + InvalidateRect( globals->hWnd, NULL, TRUE /* erase */ ); + +#ifdef XWFEATURE_RELAY + if ( newGame && globals->gameInfo.serverRole == SERVER_ISCLIENT ) { + XWStreamCtxt* stream; + XP_ASSERT( !!globals->game.comms ); + stream = make_generic_stream( globals ); + stream_setOnCloseProc( stream, ce_send_on_close ); + server_initClientConnection( globals->game.server, stream ); + } +#endif + + ceSetLeftSoftkey( globals, ID_MOVE_TURNDONE ); + + server_do( globals->game.server ); + + globals->isNewGame = FALSE; + + if ( !!toBeDestroyed ) { + dict_destroy( toBeDestroyed ); + } +} /* ceInitAndStartBoard */ + +static void +ceSavePrefs( CEAppGlobals* globals ) +{ + HANDLE fileH; + wchar_t path[CE_MAX_PATH_LEN]; + + (void)ceGetPath( globals, PREFS_FILE_PATH_L, path, VSIZE(path) ); + fileH = CreateFile( path, GENERIC_WRITE, 0, NULL, + OPEN_ALWAYS, FILE_ATTRIBUTE_HIDDEN, NULL ); + if ( fileH != INVALID_HANDLE_VALUE ) { + XP_U32 nWritten; + XP_U16 nameLen = 0; + XP_UCHAR* name = globals->curGameName; + + if ( name != NULL ) { + nameLen = (XP_U16)XP_STRLEN( name ); + } + + SetFilePointer( fileH, 0, 0, FILE_BEGIN ); + /* write prefs, including version num */ + WriteFile( fileH, &globals->appPrefs, sizeof(globals->appPrefs), + &nWritten, NULL ); + + WriteFile( fileH, &nameLen, sizeof(nameLen), &nWritten, NULL ); + WriteFile( fileH, name, nameLen, &nWritten, NULL ); + + WriteFile( fileH, &globals->flags, sizeof(globals->flags), &nWritten, + NULL ); + + SetEndOfFile( fileH ); /* truncate anything previously there */ + + CloseHandle( fileH ); /* am I not supposed to do this? PENDING */ + XP_DEBUGF( "ceSavePrefs: prefs file written" ); + } else { + logLastError( "failed to create prefs file" ); + } +} /* ceSavePrefs */ + +static XP_Bool +peekVersion( HANDLE fileH, XP_U16* version ) +{ + XP_Bool success = XP_FALSE; + XP_U32 nRead; + success = ReadFile( fileH, version, sizeof(*version), &nRead, NULL ); + if ( success ) { + SetFilePointer( fileH, -nRead, 0, FILE_CURRENT ); + } + return success; +} /* peekVersion */ + +static XP_Bool +canUpdatePrefs( CEAppGlobals* globals, HANDLE fileH, XP_U16 curVersion, + CEAppPrefs* prefs ) +{ + XP_Bool success = XP_FALSE; + LOG_FUNC(); + if ( (curVersion == 0x0002) && (CUR_CE_PREFS_FLAGS == 0x0003) ) { + CEAppPrefs0002 oldPrefs; + XP_U32 nRead; + if ( ReadFile( fileH, &oldPrefs, sizeof(oldPrefs), &nRead, NULL ) ) { + ceInitPrefs( globals, prefs ); + + XP_MEMCPY( &prefs->cp, &oldPrefs.cp, sizeof(prefs->cp) ); + prefs->showColors = oldPrefs.showColors; + + XP_MEMCPY( &prefs->colors[0], &oldPrefs.colors[0], + CE_FOCUS_COLOR*sizeof(prefs->colors[0])); + XP_ASSERT( CE_PLAYER0_COLOR - 1 == CE_FOCUS_COLOR ); + XP_MEMCPY( &prefs->colors[CE_PLAYER0_COLOR], + &oldPrefs.colors[CE_FOCUS_COLOR], + (CE_NUM_COLORS-CE_PLAYER0_COLOR) + * sizeof(prefs->colors[0])); + success = XP_TRUE; + } else { + XP_LOGF( "%s: ReadFile bad", __func__ ); + } + } else { + XP_LOGF( "%s: can't convert from %d to %d", __func__, + curVersion, CUR_CE_PREFS_FLAGS ); + } + LOG_RETURNF( "%d", (int)success ); + return success; +} /* canUpdatePrefs */ + +static XP_Bool +ceLoadPrefs( CEAppGlobals* globals ) +{ + XP_Bool result = XP_FALSE; + HANDLE fileH; + wchar_t path[CE_MAX_PATH_LEN]; + + (void)ceGetPath( globals, PREFS_FILE_PATH_L, path, VSIZE(path) ); + fileH = CreateFile( path, GENERIC_READ, 0, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); + if ( fileH != INVALID_HANDLE_VALUE ) { + XP_U32 fileSize = GetFileSize( fileH, NULL ); + XP_U16 curVersion; + if ( fileSize >= sizeof(curVersion) && peekVersion( fileH, + &curVersion ) ) { + CEAppPrefs tmpPrefs; + if ( curVersion == CUR_CE_PREFS_FLAGS ) { + if ( fileSize >= sizeof( CEAppPrefs ) ) { + XP_U32 bytesRead; + if ( ReadFile( fileH, &tmpPrefs, sizeof(tmpPrefs), + &bytesRead, NULL ) ) { + XP_ASSERT( tmpPrefs.versionFlags == CUR_CE_PREFS_FLAGS ); + result = XP_TRUE; + } + } + } else if ( canUpdatePrefs( globals, fileH, curVersion, + &tmpPrefs ) ) { + result = XP_TRUE; + } else { + XP_LOGF( "%s: old prefs; cannot read.", __func__ ); + } + + if ( result ) { + XP_U16 flags; + XP_U16 nameLen; + XP_UCHAR* name; + XP_U32 nRead; + + XP_MEMCPY( &globals->appPrefs, &tmpPrefs, + sizeof(globals->appPrefs) ); + + ReadFile( fileH, &nameLen, sizeof(nameLen), &nRead, + NULL ); + name = XP_MALLOC( globals->mpool, nameLen + 1 ); + ReadFile( fileH, name, nameLen, &nRead, NULL ); + name[nameLen] = '\0'; + globals->curGameName = name; + + if ( ReadFile( fileH, &flags, sizeof(flags), &nRead, + NULL ) + && nRead == sizeof(flags) ) { + } else { + flags = 0; + } + globals->flags = flags; + } + } + CloseHandle( fileH ); + } else { + XP_LOGF( "ceLoadPrefs: prefs file not found" ); + } + return result; /* none found */ +} /* ceLoadPrefs */ + +static XWStreamCtxt* +make_generic_stream( const CEAppGlobals* globals ) +{ + return mem_stream_make( MPPARM(globals->mpool) globals->vtMgr, + (void*)globals, 0, NULL ); +} /* make_generic_stream */ + +static XWStreamCtxt* +fileToStream( const CEAppGlobals* globals, const XP_UCHAR* path ) +{ + XWStreamCtxt* stream = NULL; + HANDLE fileH; + wchar_t widebuf[257]; + XP_U16 len; + + len = (XP_U16)XP_STRLEN( path ); + MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, path, len + 1, + widebuf, len + 1 ); + + fileH = CreateFile( widebuf, GENERIC_READ, 0, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); + + if ( fileH != INVALID_HANDLE_VALUE ) { + XP_U32 len = GetFileSize( fileH, NULL ); + XP_U32 nRead; + XP_UCHAR* buf = XP_MALLOC( globals->mpool, len ); + + XP_DEBUGF( "fileSize = %ld", len ); + + ReadFile( fileH, buf, len, &nRead, NULL ); + CloseHandle( fileH ); + + stream = make_generic_stream( globals ); + stream_open( stream ); + stream_putBytes( stream, buf, (XP_U16)nRead ); + + XP_FREE( globals->mpool, buf ); + } + + XP_DEBUGF( "fileToStream => 0x%lx", (unsigned long)stream ); + + return stream; +} /* fileToStream */ + +static XP_Bool +ceLoadSavedGame( CEAppGlobals* globals ) +{ + XP_Bool success = XP_FALSE; + XWStreamCtxt* stream; + + LOG_FUNC(); + + stream = fileToStream( globals, globals->curGameName ); + success = stream != NULL; + if ( success ) { + DictionaryCtxt* dict; + XP_U8 flags = stream_getU8( stream ); + XP_Bool hasDict = (flags & 0x01) != 0; + flags >>= 1; + + if ( hasDict ) { +#ifdef STUBBED_DICT + XP_ASSERT(0); /* just don't do this!!!! */ +#else + XP_UCHAR* dictName = stringFromStream( globals->mpool, stream ); + dict = ce_dictionary_make( globals, dictName ); + success = dict != NULL; + if ( !success ) { + XP_UCHAR buf[128]; + snprintf( buf, VSIZE(buf), "Unable to open dictionary: %s", + dictName ); + buf[VSIZE(buf)-1] = '\0'; + ceOops( globals, NULL, buf ); + + } + XP_FREE( globals->mpool, dictName ); +#endif + } else { + dict = NULL; + } + + if ( success ) { + if ( flags >= CE_GAMEFILE_VERSION1 ) { + ce_draw_fromStream( globals->draw, stream, flags ); + } + + XP_DEBUGF( "calling game_makeFromStream" ); + success = game_makeFromStream( MEMPOOL stream, &globals->game, + &globals->gameInfo, dict, + &globals->util, + (DrawCtx*)globals->draw, + &globals->appPrefs.cp, CE_SEND_PROC, + CE_RESET_PROC globals ); + if ( success ) { + ceSetTitleFromName( globals ); + } else { + if ( !!dict ) { + dict_destroy( dict ); + } + ceOops( globals, NULL, "Saved game cannot be opened." ); + } + } + + stream_destroy( stream ); + } + + return success; +} /* ceLoadSavedGame */ + +static void +colorsFromRsrc( const CEAppGlobals* globals, CEAppPrefs* prefs ) +{ + XP_U16 i; + HGLOBAL globH; + HRSRC rsrcH; + XP_U16* ptr; + + rsrcH = FindResource( globals->hInst, MAKEINTRESOURCE(ID_COLORS_RES), + TEXT("CLRS") ); + globH = LoadResource( globals->hInst, rsrcH ); + ptr = (XP_U16*)globH; + + for ( i = 0; i < CE_NUM_COLORS; ++i ) { + XP_U8 r = (XP_U8)*ptr++; + XP_U8 g = (XP_U8)*ptr++; + XP_U8 b = (XP_U8)*ptr++; + prefs->colors[i] = RGB( r, g, b ); + } + + DeleteObject( globH ); +} /* colorsFromRsrc */ + +static void +ceInitPrefs( CEAppGlobals* globals, CEAppPrefs* prefs ) +{ + prefs->versionFlags = CUR_CE_PREFS_FLAGS; + prefs->showColors = XP_TRUE; + prefs->fullScreen = XP_FALSE; + + prefs->cp.showBoardArrow = XP_TRUE; + prefs->cp.showRobotScores = XP_FALSE; + + colorsFromRsrc( globals, prefs ); +} /* ceInitPrefs */ + +#ifdef _WIN32_WCE +static void +getOSInfo( CEAppGlobals* globals ) +{ + OSVERSIONINFO ver = {0}; + TCHAR buf[128]; + XW_WinceVersion winceVersion = WINCE_UNKNOWN; + + if ( GetVersionEx( &ver )) { + XP_LOGF( "version = %ld.%ld", ver.dwMajorVersion, ver.dwMinorVersion ); + } else { + XP_WARNF( "GetVersionEx failed" ); + } + + if ( SystemParametersInfo( SPI_GETPLATFORMTYPE, sizeof(buf), buf, FALSE ) ) { + if ( 0 == lstrcmp( buf, L"PocketPC") ) { + // We are on a Pocket PC, so check the OS version, + // Pocket PC 2003 used WinCE 4.2 + if ( ver.dwMajorVersion < 4 ) { + winceVersion = WINCE_PPC_V1; + } else if ( ver.dwMajorVersion == 4 ) { + winceVersion = WINCE_PPC_2003; + } else if ( ver.dwMajorVersion > 4 ) { + winceVersion = WINCE_PPC_2005; + } + } else if ( 0 == lstrcmp( buf, L"SmartPhone") ) { + if ( ver.dwMajorVersion < 4 ) { + winceVersion = WINCE_SMARTPHONE_V1; + } else if ( ver.dwMajorVersion == 4 ) { + winceVersion = WINCE_SMARTPHONE_2003; + } else if ( ver.dwMajorVersion > 4 ) { + winceVersion = WINCE_SMARTPHONE_2005; + } + } else { + XP_LOGW( "unknown OS type", buf ); + } + } else if ( GetLastError() == ERROR_ACCESS_DENIED ) { + if ( ver.dwMajorVersion < 4 ) { + winceVersion = WINCE_SMARTPHONE_V1; + } else { + winceVersion = WINCE_SMARTPHONE_2003; + } + } + + XP_ASSERT( winceVersion != WINCE_UNKNOWN ); + globals->winceVersion = winceVersion; + XP_LOGF( "%s: set version to %d", __func__, winceVersion ); +} /* getOSInfo */ +#else +#define getOSInfo( g ) +#endif + +// +// FUNCTION: InitInstance(HANDLE, int) +// +// PURPOSE: Saves instance handle and creates main window +// +// COMMENTS: +// +// In this function, we save the instance handle in a global variable and +// create and display the main program window. +// +BOOL +InitInstance(HINSTANCE hInstance, int nCmdShow +#ifndef _WIN32_WCE + ,XP_U16 width, XP_U16 height +#endif + ) +{ + HWND hWnd; + TCHAR szTitle[MAX_LOADSTRING]; // The title bar text + TCHAR szWindowClass[MAX_LOADSTRING]; // The window class name + CEAppGlobals* globals; + BOOL result = TRUE; + XP_Bool oldGameLoaded; + XP_Bool prevStateExists; + XP_Bool newDone = XP_FALSE; + wchar_t path[CE_MAX_PATH_LEN]; + MPSLOT; + +#ifdef XWFEATURE_RELAY + { + WORD wVersionRequested; + WSADATA wsaData; + int err; + wVersionRequested = MAKEWORD( 2, 2 ); + err = WSAStartup( wVersionRequested, &wsaData ); + if ( err != 0 ) { + /* Tell the user that we could not find a usable */ + /* WinSock DLL. */ + XP_LOGF( "unable to load winsock" ); + } + } +#endif + + LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); + LoadString(hInstance, IDC_XWORDS4, szWindowClass, MAX_LOADSTRING); + + // If it is already running, then focus on the window. Skip title in + // search as we change title to include game name + hWnd = FindWindow( szWindowClass, NULL ); + if ( hWnd ) { + SetForegroundWindow( (HWND)((ULONG) hWnd | 0x00000001) ); + result = FALSE; + goto exit; + } + +#ifdef MEM_DEBUG + mpool = mpool_make(); +#endif + + globals = (CEAppGlobals*)XP_MALLOC( mpool, sizeof(*globals) ); + XP_DEBUGF( "globals created: 0x%p", globals ); + XP_MEMSET( globals, 0, sizeof(*globals) ); + MPASSIGN( globals->mpool, mpool ); + +#ifndef _WIN32_WCE + globals->dbWidth = width; + globals->dbHeight = height; +#endif + + (void)ceGetPath( globals, DEFAULT_DIR_PATH_L, path, VSIZE(path) ); + (void)CreateDirectory( path, 0 ); + + getOSInfo( globals ); + + globals->vtMgr = make_vtablemgr( MPPARM_NOCOMMA(mpool) ); + + globals->hInst = hInstance; + // Initialize global strings + MyRegisterClass(hInstance, szWindowClass); + + hWnd = CreateWindow(szWindowClass, szTitle, WS_VISIBLE, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, NULL, NULL, hInstance, globals); + globals->hWnd = hWnd; + + if (!hWnd) { + result = FALSE; + goto exit; + } + +#ifdef _WIN32_WCE + if ( globals->hwndCB && !IS_SMARTPHONE(globals) ) { + RECT rc, rcmb; + + GetWindowRect( hWnd, &rc ); + GetWindowRect( globals->hwndCB, &rcmb ); + rc.bottom -= (rcmb.bottom - rcmb.top); + + MoveWindow( hWnd, rc.left, rc.top, rc.right-rc.left, + rc.bottom-rc.top, FALSE ); + } +#endif + +#ifndef _WIN32_WCE + srand( time(NULL) ); +#endif + + ceInitUtilFuncs( globals ); + + gi_initPlayerInfo( MPPARM(mpool) &globals->gameInfo, NULL ); + + /* choose one. If none found it's an error. */ +#ifndef STUBBED_DICT + result = 1 == ceLocateNDicts( globals, 1, ceSetDictName, + globals ); + if ( !result ) { + wchar_t buf[512]; + (void)LoadString( globals->hInst, (UINT)IDS_DICTLOC, buf, VSIZE(buf) ); + assertOnTop( globals->hWnd ); + MessageBox( globals->hWnd, buf, L"Dictionary Not Found", + MB_OK | MB_ICONHAND ); + result = FALSE; + goto exit; + } +#endif + + /* here's where we want to behave differently if there's saved state. + But that's a long ways off. */ + prevStateExists = ceLoadPrefs( globals ); + if ( !prevStateExists ) { + ceInitPrefs( globals, &globals->appPrefs ); + } + /* must load prefs before creating draw ctxt */ + globals->draw = ce_drawctxt_make( MPPARM(globals->mpool) + hWnd, globals ); + + oldGameLoaded = prevStateExists && ceLoadSavedGame( globals ); + + if ( !oldGameLoaded ) { + XP_U16 gameID = 0; /* good enough until I get networking going */ + game_makeNewGame( MPPARM(mpool) &globals->game, &globals->gameInfo, + &globals->util, (DrawCtx*)globals->draw, gameID, + &globals->appPrefs.cp, + CE_SEND_PROC, CE_RESET_PROC globals ); + + newDone = ceDoNewGame( globals ); /* calls ceInitAndStartBoard */ + if ( !newDone ) { + result = FALSE; + } + } + + trapBackspaceKey( hWnd ); + + ShowWindow(hWnd, nCmdShow); + UpdateWindow(hWnd); +#ifdef _WIN32_WCE + if (globals->hwndCB) { + CommandBar_Show(globals->hwndCB, TRUE); + } +#endif + if ( result && !newDone ) { + ceInitAndStartBoard( globals, !oldGameLoaded, NULL ); + } + + exit: + return result; +} /* InitInstance */ + +static XP_Bool +ceSetDictName( const wchar_t* XP_UNUSED(wPath), XP_U16 XP_UNUSED_DBG(index), + void* XP_UNUSED(ctxt) ) +{ +/* CEAppGlobals* globals = (CEAppGlobals*)ctxt; */ +/* XP_UCHAR* str; */ +/* XP_UCHAR buf[CE_MAX_PATH_LEN]; */ + XP_ASSERT( index == 0 ); /* we said one only! */ + +/* WideCharToMultiByte( CP_ACP, 0, wPath, -1, */ +/* buf, sizeof(buf)-1, NULL, NULL ); */ + +/* XP_LOGF( "%s: got path \"%s\"", __func__, buf ); */ +/* str = copyString( MPPARM(globals->mpool) buf ); */ +/* XP_LOGF( "%s: got %p", __func__, str ); /\* this is leaking *\/ */ +/* XP_ASSERT( NULL == globals->gameInfo.dictName ); */ +/* globals->gameInfo.dictName = str; */ + return XP_FALSE; +} /* ceSetDictName */ + +static XP_Bool +ceHandleHintRequest( CEAppGlobals* globals ) +{ + XP_Bool notDone; + XP_Bool draw; + XP_ASSERT( !!globals->game.board ); + + draw = board_requestHint( globals->game.board, +#ifdef XWFEATURE_SEARCHLIMIT + globals->askTrayLimits, +#endif + ¬Done ); + globals->hintPending = notDone; + if ( draw ) { /* don't turn on if disallowed */ + ceSetLeftSoftkey( globals, ID_MOVE_NEXTHINT ); + } + return draw; +} /* ceHandleHintRequest */ + +static XP_Bool +handleTradeCmd( CEAppGlobals* globals ) +{ + XP_Bool success = board_beginTrade( globals->game.board ); + if ( success ) { + ceSetLeftSoftkey( globals, ID_MOVE_TURNDONE ); + } + return success; +} /* handleTradeCmd */ + +static XP_Bool +handleJuggleCmd( CEAppGlobals* globals ) +{ + ceSetLeftSoftkey( globals, ID_MOVE_JUGGLE ); + return board_juggleTray( globals->game.board ); +} /* handleJuggleCmd */ + +static XP_Bool +handleHidetrayCmd( CEAppGlobals* globals ) +{ + XP_Bool result; + XW_TrayVisState curState = board_getTrayVisState( globals->game.board ); + + if ( curState == TRAY_REVEALED ) { + result = board_hideTray( globals->game.board ); + } else { + result = board_showTray( globals->game.board ); + } + + ceSetLeftSoftkey( globals, ID_MOVE_HIDETRAY ); + return result; +} /* handleHidetrayCmd */ + +static XP_Bool +handleDoneCmd( CEAppGlobals* globals) +{ + return board_commitTurn( globals->game.board ); +} /* handleDoneCmd */ + +static void +ceCountsAndValues( CEAppGlobals* globals ) +{ + if ( !!globals->game.server ) { + XWStreamCtxt* stream = make_generic_stream( globals ); + + server_formatDictCounts( globals->game.server, stream, 3 ); + + (void)ceMsgFromStream( globals, stream, L"Tile Counts and Values", + MB_OK | MB_ICONINFORMATION, XP_TRUE ); + } +} /* ceCountsAndValues */ + +static void +ceTilesLeft( CEAppGlobals* globals ) +{ + if ( !!globals->game.board ) { + XWStreamCtxt* stream = make_generic_stream( globals ); + board_formatRemainingTiles( globals->game.board, stream ); + + (void)ceMsgFromStream( globals, stream, L"Remaining tiles", + MB_OK | MB_ICONINFORMATION, XP_TRUE ); + } +} /* ceTilesLeft */ + +static void +ceDoHistory( CEAppGlobals* globals ) +{ + XP_Bool gameOver = server_getGameIsOver(globals->game.server); + XWStreamCtxt* stream; + + stream = make_generic_stream( globals ); + + model_writeGameHistory( globals->game.model, stream, + globals->game.server, gameOver ); + (void)ceMsgFromStream( globals, stream, L"Game history", + MB_OK | MB_ICONINFORMATION, XP_TRUE ); +} /* ceDoHistory */ + +static void +drawInsidePaint( CEAppGlobals* globals, const RECT* invalR ) +{ + HDC hdc; + + hdc = GetDC( globals->hWnd ); + if ( !hdc ) { + logLastError( __func__ ); + } else { + int oldMode = SetBkMode( hdc, TRANSPARENT ); + HDC prevHDC = globals->hdc; + globals->hdc = hdc; + + if ( !!invalR ) { + XP_U16 ii; + RECT interR; + for ( ii = 0; ii < N_OWNED_RECTS; ++ii ) { + if ( IntersectRect( &interR, invalR, + &globals->ownedRects[ii] ) ) { + ce_draw_erase( globals->draw, &interR ); + } + } +#ifdef _WIN32_WCE + for ( ii = 0; ii < VSIZE(globals->scrollRects); ++ii ) { + if ( IntersectRect( &interR, invalR, + &globals->scrollRects[ii] ) ) { + if ( globals->scrollerHasFocus ) { + ce_draw_focus( globals->draw, &interR ); + } else { + ce_draw_erase( globals->draw, &interR ); + } + } + } +#endif + } + + board_draw( globals->game.board ); + + (void)SetBkMode( hdc, oldMode ); + globals->hdc = prevHDC; + } +} /* drawInsidePaint */ + +static void +ceDisplayFinalScores( CEAppGlobals* globals ) +{ + XWStreamCtxt* stream; + + stream = make_generic_stream( globals ); + server_writeFinalScores( globals->game.server, stream ); + stream_putU8( stream, '\0' ); + + (void)ceMsgFromStream( globals, stream, L"Final scores", + MB_OK | MB_ICONINFORMATION, XP_TRUE ); +} /* ceDisplayFinalScores */ + +static XP_Bool +ceDoNewGame( CEAppGlobals* globals ) +{ + GameInfoState giState; + CommsAddrRec* addr = NULL; + XP_Bool changed = XP_FALSE; + + XP_MEMSET( &giState, 0, sizeof(giState) ); + giState.dlgHdr.globals = globals; + giState.isNewGame = XP_TRUE; + + assertOnTop( globals->hWnd ); + DialogBoxParam( globals->hInst, (LPCTSTR)IDD_GAMEINFO, globals->hWnd, + (DLGPROC)GameInfo, (long)&giState ); + + if ( !giState.userCancelled +#ifndef STUBBED_DICT + && ( giState.newDictName[0] != '\0' ) +#endif + ) { + + if ( globals->curGameName != NULL ) { + XP_FREE( globals->mpool, globals->curGameName ); + globals->curGameName = NULL; + } + + if ( giState.prefsChanged ) { + loadCurPrefsFromState( globals, &globals->appPrefs, + &globals->gameInfo, &giState.prefsPrefs ); + if ( giState.colorsChanged ) { + updateForColors( globals ); + } + } +#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH + if ( giState.addrChanged ) { + addr = &giState.prefsPrefs.addrRec; + } +#endif + + ceInitAndStartBoard( globals, XP_TRUE, addr ); + ceSetTitleFromName( globals ); + changed = XP_TRUE; + } + + return changed; +} /* ceDoNewGame */ + +static void +ceChooseAndOpen( CEAppGlobals* globals ) +{ + assertOnTop( globals->hWnd ); + // Save in case we'll be duplicating it + again: + if ( ceSaveCurGame( globals, XP_FALSE ) ) { + SavedGamesResult choice; + wchar_t newName[256]; + newName[0] = 0; + + ceSetTitleFromName( globals ); /* in case we named it above */ + + choice = ceSavedGamesDlg( globals, globals->curGameName, newName, + VSIZE(newName) ); + if ( CE_SVGAME_CANCEL != choice ) { + XP_UCHAR* name; + XP_U16 len; + + len = wcslen(newName); + name = XP_MALLOC( globals->mpool, len + 1 ); + + WideCharToMultiByte( CP_ACP, 0, newName, len + 1, + name, len + 1, NULL, NULL ); + + if ( globals->curGameName != NULL + && 0 == XP_STRCMP( name, globals->curGameName ) ){ + /* User chose already-open game; no-op */ + XP_FREE( globals->mpool, name ); + } else { + /* Save old name in case fail to open new, e.g. because dict + not there */ + XP_UCHAR* oldName; + + /* Need to save a second time, with auto-save, in case user + wants to overwrite yet chooses a game whose dict is + missing -- since then we'll be re-opening this game! */ + ceSaveCurGame( globals, XP_TRUE ); /* may change curGameName */ + + oldName = globals->curGameName; + globals->curGameName = NULL; /* prevent being destroyed */ + closeGame( globals ); + + if ( CE_SVGAME_RENAME == choice ) { + XP_U16 len = 1 + XP_STRLEN( oldName ); + wchar_t widebuf[len]; + MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, oldName, len, + widebuf, len ); + (void)MoveFile( widebuf, newName ); + } + + globals->curGameName = name; + if ( ceLoadSavedGame( globals ) ) { + XP_FREE( globals->mpool, oldName ); + } else { + XP_ASSERT( CE_SVGAME_RENAME != choice ); + XP_LOGF( "failed to open chosen game" ); + XP_FREE( globals->mpool, globals->curGameName ); + globals->curGameName = oldName; + if ( !ceLoadSavedGame( globals ) ) { + XP_LOGF( "failed to open old game too!!!" ); + } + } + ceInitAndStartBoard( globals, XP_FALSE, NULL ); + if ( CE_SVGAME_RENAME == choice ) { + goto again; + } + } + } else { + XP_LOGF( "GetOpenFileName() failed" ); + } + } +} /* ceChooseAndOpen */ + +static void +updateForColors( CEAppGlobals* globals ) +{ + ce_draw_update( globals->draw ); + if ( !!globals->game.board ) { + board_invalAll( globals->game.board ); + } +} /* updateForColors */ + +static void +ceDoPrefsDlg( CEAppGlobals* globals ) +{ + CePrefsDlgState state; + CePrefsPrefs prefsPrefs; + + loadStateFromCurPrefs( globals, &globals->appPrefs, &globals->gameInfo, + &prefsPrefs ); + + assertOnTop( globals->hWnd ); + (void)WrapPrefsDialog( globals->hWnd, globals, &state, &prefsPrefs, + XP_FALSE ); + + if ( !state.userCancelled ) { + + loadCurPrefsFromState( globals, &globals->appPrefs, &globals->gameInfo, + &prefsPrefs ); + + (void)cePositionBoard( globals ); + + if ( state.colorsChanged ) { + updateForColors( globals ); + } + /* need to reflect vars set in state into globals, and update/inval + as appropriate. */ + } +} /* ceDoPrefsDlg */ + +static void +ceWriteToFile( XWStreamCtxt* stream, void* closure ) +{ + FileWriteState* fwState = (FileWriteState*)closure; +#ifdef MEM_DEBUG + CEAppGlobals* globals = fwState->globals; +#endif + XP_UCHAR* path = fwState->path; + XP_U16 len = (XP_U16)XP_STRLEN( path ) + 1; /* 1: null byte */ + wchar_t widebuf[len]; + HANDLE fileH; + + MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, path, len, widebuf, len ); + + fileH = CreateFile( widebuf, GENERIC_WRITE, 0, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); + + if ( fileH != INVALID_HANDLE_VALUE ) { + XP_UCHAR* buf; + XP_U32 nWritten; + + len = stream_getSize( stream ); + buf = XP_MALLOC( globals->mpool, len ); + stream_getBytes( stream, buf, len ); + + WriteFile( fileH, buf, len, &nWritten, NULL ); + SetEndOfFile( fileH ); + CloseHandle( fileH ); + + XP_FREE( globals->mpool, buf ); + } +} /* ceWriteToFile */ + +static XP_Bool +isDefaultName( CEAppGlobals* globals, const XP_UCHAR* name ) +{ + wchar_t path[CE_MAX_PATH_LEN]; + (void)ceGetPath( globals, DEFAULT_GAME_PATH, path, VSIZE(path) ); + return 0 == XP_STRCMP( path, name ); +} /* isDefaultName */ + +static XP_Bool +ceSaveCurGame( CEAppGlobals* globals, XP_Bool autoSave ) +{ + XP_Bool confirmed = XP_FALSE; + /* If it doesn't yet have a name, get a path at which to save it. User + has a chance to cancel this. But if there is a name go ahead and save + using it and don't bother giving cancel chance -- since there's no + harm in making 'em restart. Not sure how this changes when IR's + involved. */ + XP_UCHAR* name = globals->curGameName; + if ( name == NULL || isDefaultName( globals, name ) ) { + XP_UCHAR* newName = NULL; + + if ( autoSave ) { + XP_U16 len; + wchar_t path[CE_MAX_PATH_LEN]; + len = 1 + ceGetPath( globals, DEFAULT_GAME_PATH, + path, VSIZE(path) ); + newName = XP_MALLOC( globals->mpool, len ); + XP_MEMCPY( newName, path, len ); + + confirmed = XP_TRUE; + } else { + wchar_t nameBuf[MAX_PATH]; + + confirmed = ceConfirmUniqueName( globals, globals->hWnd, IDS_SAVENAME, + nameBuf, VSIZE(nameBuf) ); + if ( confirmed ) { + XP_U16 len = wcslen(nameBuf); + newName = XP_MALLOC( globals->mpool, len + 1 ); + WideCharToMultiByte( CP_ACP, 0, nameBuf, len + 1, + newName, len + 1, NULL, NULL ); + } + } + + if ( confirmed ) { + XP_ASSERT( !!newName ); + if ( !!globals->curGameName ) { + XP_FREE( globals->mpool, globals->curGameName ); + } + globals->curGameName = newName; + } + } else { + confirmed = XP_TRUE; + } + + if ( confirmed ) { + if ( !!globals->game.server ) { + XWStreamCtxt* memStream; + DictionaryCtxt* dict; + FileWriteState fwState; + const char* dictName; + XP_U8 flags; + + fwState.path = globals->curGameName; + fwState.globals = globals; + board_hideTray( globals->game.board ); /* so won't be visible when + next opened */ + memStream = mem_stream_make( MEMPOOL globals->vtMgr, &fwState, 0, + ceWriteToFile ); + stream_open( memStream ); + + /* the dictionary */ + dict = model_getDictionary( globals->game.model ); +#ifdef STUBBED_DICT /* don't check this in!!! */ + dictName = NULL; +#else + dictName = !!dict? dict_getName( dict ) : NULL; +#endif + flags = !!dictName? 0x01 : 0x00; + flags |= CE_GAMEFILE_VERSION << 1; + stream_putU8( memStream, flags ); + + if ( !!dictName ) { + stringToStream( memStream, dictName ); + } + + ce_draw_toStream( globals->draw, memStream ); + + game_saveToStream( &globals->game, &globals->gameInfo, memStream ); + + stream_destroy( memStream ); + } + } + + return confirmed; +} /* ceSaveCurGame */ + +static void +ceSaveAndExit( CEAppGlobals* globals ) +{ + (void)ceSaveCurGame( globals, XP_TRUE ); + ceSavePrefs( globals ); + DestroyWindow(globals->hWnd); +} /* ceSaveAndExit */ + +static void +closeGame( CEAppGlobals* globals ) +{ + game_dispose( &globals->game ); + gi_disposePlayerInfo( MPPARM(globals->mpool) &globals->gameInfo ); + + if ( !!globals->curGameName ) { + XP_FREE( globals->mpool, globals->curGameName ); + } +} + +static void +freeGlobals( CEAppGlobals* globals ) +{ + XP_U16 ii; + MPSLOT; + + MPASSIGN( mpool, globals->mpool ); + + draw_destroyCtxt( (DrawCtx*)globals->draw ); + + closeGame( globals ); + + if ( !!globals->vtMgr ) { + vtmgr_destroy( MPPARM(mpool) globals->vtMgr ); + } + if ( !!globals->util.vtable ) { + XP_FREE( mpool, globals->util.vtable ); + } + for ( ii = 0; ii < N_CACHED_PATHS; ++ii ) { + if ( !!globals->specialDirs[ii] ) { + XP_FREE( mpool, globals->specialDirs[ii] ); + } + } + + XP_FREE( globals->mpool, globals ); + mpool_destroy( mpool ); +} /* freeGlobals */ + +#ifdef _WIN32_WCE +static HWND +makeCommandBar( HWND hwnd, HINSTANCE hInst ) +{ + SHMENUBARINFO mbi; + + XP_MEMSET( &mbi, 0, sizeof(mbi) ); + mbi.cbSize = sizeof(mbi); + mbi.hwndParent = hwnd; + mbi.nToolBarId = IDM_MAIN_MENUBAR; + mbi.hInstRes = hInst; + /* Don't set dwFlags if you want the Wince5 two-button softkey menu. */ +/* mbi.dwFlags = SHCMBF_HMENU; */ + + //mbi.dwFlags = SHCMBF_HIDESIPBUTTON; /* eeh added. Why??? */ + + if (!SHCreateMenuBar(&mbi)) { + /* will want to use this to change menubar: SHEnableSoftkey? */ + XP_LOGF( "SHCreateMenuBar failed" ); + return NULL; + } + + return mbi.hwndMB; +} /* makeCommandBar */ +#endif + +#ifdef CEFEATURE_CANSCROLL +static XP_Bool +handleScroll( CEAppGlobals* globals, XP_S16 pos, /* only valid for THUMB* */ + XP_S16 code, HWND wnd ) +{ + XP_Bool result = XP_FALSE; + + if ( wnd == globals->scrollHandle ) { + XP_U16 curYOffset = board_getYOffset( globals->game.board ); + XP_S16 newOffset = curYOffset; + + XP_ASSERT( !!globals->game.board ); + + switch ( code ) { +/* case SB_BOTTOM: // Scrolls to the lower right */ +/* case SB_ENDSCROLL: // Ends scroll */ + + case SB_LINEUP: // Scrolls one line up + case SB_PAGEUP: // + --newOffset; + break; + + case SB_LINEDOWN: // Scrolls one line down + case SB_PAGEDOWN: // Scrolls one page down + ++newOffset; + break; + + case SB_THUMBTRACK: /* still dragging; don't redraw */ + case SB_THUMBPOSITION: + newOffset = pos; + default: + break; + /* do nothing: leave newOffset == curYOffset */ + } + + result = curYOffset != newOffset + && board_setYOffset( globals->game.board, newOffset ); + } + return result; +} /* handleScroll */ +#endif + +static XP_Bool +ceFireTimer( CEAppGlobals* globals, XWTimerReason why ) +{ + XP_Bool draw = XP_FALSE; + XWTimerProc proc; + void* closure; + + proc = globals->timerProcs[why]; + if ( !!proc ) { + globals->timerProcs[why] = NULL; + closure = globals->timerClosures[why]; + draw = (*proc)( closure, why ); + /* Setting draw after firing timer allows scrolling to happen + while pen is held down. This is a hack. Perhaps having + the timer proc return whether drawing is needed would be + more consistent. */ + } else { + XP_LOGF( "skipped timer; alread fired?" ); + } + return draw; +} /* ceFireTimer */ + +/* WM_TIMER messages are low-priority. Hold a key down and key events will + * crowd it out of the queue so that the app doesn't see it until the key is + * released. There are more reliable timers, but they seem to require + * advanced techniques like semaphores. At least one article recommends + * polling over going to those lengths. This is better that polling. I hope + * it's enough. + */ +static XP_Bool +checkFireLateKeyTimer( CEAppGlobals* globals ) +{ + XP_Bool drop = XP_FALSE; + XWTimerReason whys[] = { TIMER_PENDOWN, TIMER_TIMERTICK +#if defined RELAY_HEARTBEAT || defined COMMS_HEARTBEAT + , TIMER_HEARTBEAT +#endif + }; + XP_U32 now = GetCurrentTime(); + XP_U16 i; + + for ( i = 0; i < sizeof(whys)/sizeof(whys[0]); ++i ) { + XWTimerReason why = whys[i]; + if ( !!globals->timerProcs[why] ) { + if ( now >= globals->timerWhens[why] ) { + (void)ceFireTimer( globals, why ); + drop = XP_TRUE; + } + } + } + + return drop; +} /* checkFireLateKeyTimer */ + +#ifndef XWFEATURE_STANDALONE_ONLY +static XP_Bool +processPacket( CEAppGlobals* globals, XWStreamCtxt* instream ) +{ + XP_Bool draw = XP_FALSE; + + XP_ASSERT( globals->game.comms != NULL ); + + if ( comms_checkIncomingStream( globals->game.comms, + instream, NULL ) ) { + draw = server_receiveMessage( globals->game.server, instream ); + } + stream_destroy( instream ); + ce_util_requestTime( &globals->util ); + + return draw; +} /* processPacket */ +#endif + +static XP_Bool +checkPenDown( CEAppGlobals* globals ) +{ + XP_Bool draw = globals->penDown; + if ( draw ) { + draw = board_handlePenUp( globals->game.board, 0x7FFF, 0x7FFF ); + globals->penDown = XP_FALSE; + } + return draw; +} /* checkPenDown */ + +#ifdef KEYBOARD_NAV + +static XP_Bool +ceCheckHandleFocusKey( CEAppGlobals* globals, WPARAM wParam, LPARAM lParam, + XP_Bool keyDown, XP_Bool* handledP ) +{ + XP_Bool draw = XP_FALSE; + + /* Sometimes, e.g. after a menu is released, we get KEY_UP not preceeded + by KEY_DOWN. Just drop those. */ + if ( !keyDown && !globals->keyDown ) { + XP_LOGF( "%s: keyUp not preceeded by keyDown: dropping", __func__ ); + } else { + XP_Bool isRepeat = keyDown && ((HIWORD(lParam) & KF_REPEAT) != 0); + XP_Key key; + XP_S16 incr = 0; + + switch ( wParam ) { + case VK_UP: + key = XP_CURSOR_KEY_UP; + incr = -1; + break; + case VK_RIGHT: + key = XP_CURSOR_KEY_RIGHT; + incr = 1; + break; + case VK_DOWN: + key = XP_CURSOR_KEY_DOWN; + incr = 1; + break; + case VK_LEFT: + key = XP_CURSOR_KEY_LEFT; + incr = -1; + break; + case 0x0d: + case 0x5d: /* center key on WinMo5 Treo (at least) -- but also ']'*/ + case VK_HOME: + key = XP_RETURN_KEY; + if ( isRepeat ) { + (void)checkFireLateKeyTimer( globals ); + } + break; + + /* Still need to produce these somehow */ + /* XP_CURSOR_KEY_ALTRIGHT, */ + /* XP_CURSOR_KEY_ALTUP, */ + /* XP_CURSOR_KEY_ALTLEFT, */ + /* XP_CURSOR_KEY_ALTDOWN, */ + + default: + key = XP_KEY_NONE; + break; + } + + if ( key != XP_KEY_NONE ) { + BoardCtxt* board = globals->game.board; + + if ( isRepeat ) { + draw = board_handleKeyRepeat( board, key, handledP ); + } else if ( keyDown ) { + draw = board_handleKeyDown( board, key, handledP ); + } else { + draw = board_handleKeyUp( board, key, handledP ); + } + + if ( !*handledP && incr != 0 && !keyDown ) { + BoardObjectType orderScroll[] = { + OBJ_SCORE, OBJ_BOARD, OBJ_NONE, OBJ_TRAY }; + BoardObjectType orderNoScroll[] = { + OBJ_SCORE, OBJ_BOARD, OBJ_TRAY }; + BoardObjectType* order; + XP_U16 orderLen; + BoardObjectType cur = board_getFocusOwner( board ); + XP_U16 index = 0; + + if ( !!globals->scrollHandle ) { + order = orderScroll; + orderLen = VSIZE(orderScroll); + } else { + order = orderNoScroll; + orderLen = VSIZE(orderNoScroll); + } + + if ( !!globals->scrollHandle || (cur != OBJ_NONE) ) { + for ( ; ; ) { + if ( order[index] == cur ) { + break; + } + ++index; + XP_ASSERT( index < orderLen ); + } + index = (index + orderLen + incr) % orderLen; + } + draw = board_focusChanged( board, order[index], XP_TRUE ); + + if ( !!globals->scrollHandle ) { + XP_Bool scrollerHasFocus = globals->scrollerHasFocus; + if ( order[index] == OBJ_NONE ) { + XP_ASSERT( !scrollerHasFocus ); + SetFocus( globals->scrollHandle ); + scrollerHasFocus = XP_TRUE; + } else if ( scrollerHasFocus ) { + SetFocus( globals->hWnd ); + scrollerHasFocus = XP_FALSE; + } + if ( scrollerHasFocus != globals->scrollerHasFocus ) { + globals->scrollerHasFocus = scrollerHasFocus; +#ifdef _WIN32_WCE + InvalidateRect( globals->hWnd, &globals->scrollRects[0], FALSE ); + InvalidateRect( globals->hWnd, &globals->scrollRects[1], FALSE ); +#else + InvalidateRect( globals->scrollHandle, NULL, FALSE ); +#endif + } + } + } + } + } + globals->keyDown = keyDown; + return draw; +} /* ceCheckHandleFocusKey */ +#endif /* KEYBOARD_NAV */ + +static void +ceToggleFullScreen( CEAppGlobals* globals ) +{ + globals->appPrefs.fullScreen = !globals->appPrefs.fullScreen; + + ceSizeIfFullscreen( globals, globals->hWnd ); + + (void)cePositionBoard( globals ); +} /* ceToggleFullScreen */ + +static void +doAbout( CEAppGlobals* globals ) +{ + wchar_t buf[1024]; + (void)LoadString( globals->hInst, (UINT)IDS_ABOUT, buf, VSIZE(buf) ); + assertOnTop( globals->hWnd ); + MessageBox( globals->hWnd, buf, L"About " LCROSSWORDS_DIR, + MB_OK | MB_ICONINFORMATION ); +} + +LRESULT CALLBACK +WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + LRESULT result = 0; + int wmId; + XP_Bool draw = XP_FALSE; + XWTimerReason why; + CEAppGlobals* globals; + XP_Bool handled = XP_FALSE; + XP_Bool callDefault = XP_FALSE; + + if ( message == WM_CREATE ) { + globals = ((CREATESTRUCT*)lParam)->lpCreateParams; + SetWindowLongPtr( hWnd, GWL_USERDATA, (long)globals ); +#ifdef _WIN32_WCE + globals->hwndCB = makeCommandBar( hWnd, globals->hInst ); +#endif + +#ifdef _WIN32_WCE + globals->sai.cbSize = sizeof(globals->sai); +#endif + } else { +/* XP_LOGF( "%s: event=%s (%d)", __func__, messageToStr(message), message ); */ + globals = (CEAppGlobals*)GetWindowLongPtr( hWnd, GWL_USERDATA ); + + switch (message) { + +#ifdef _WIN32_WCE + case WM_ACTIVATE: + // Notify shell of our activate message + SHHandleWMActivate( hWnd, wParam, lParam, &globals->sai, FALSE ); + break; + + case WM_SETTINGCHANGE: + SHHandleWMSettingChange( hWnd, wParam, lParam, &globals->sai ); + if ( !!globals && !!globals->game.model ) { + cePositionBoard( globals ); + board_invalAll( globals->game.board ); + draw = XP_TRUE; + } + break; +#endif + +#ifdef CEFEATURE_CANSCROLL +# ifndef _WIN32_WCE + /* WM_CTLCOLORSCROLLBAR aren't delivered on CE. Some say can + * work around using WM_PAINT or WM_ERASEBKGND but no luck + * yet. */ + case WM_CTLCOLORSCROLLBAR: + if ( (HWND)lParam == globals->scrollHandle ) { + if ( globals->scrollerHasFocus ) { + return (LRESULT)ce_draw_getFocusBrush( globals->draw ); + } + } + break; +# endif + case WM_VSCROLL: + draw = checkPenDown( globals ); + draw = handleScroll( globals, (short int)HIWORD(wParam), + (short int)LOWORD(wParam), + (HWND)lParam ) || draw; + break; +#endif + + case WM_COMMAND: + (void)checkPenDown( globals ); + wmId = LOWORD(wParam); + + // Parse the menu selections: + switch (wmId) { + case ID_FILE_ABOUT: + doAbout( globals ); + break; + case ID_GAME_GAMEINFO: { + GameInfoState state; + + XP_MEMSET( &state, 0, sizeof(state) ); + state.dlgHdr.globals = globals; + + DialogBoxParam(globals->hInst, (LPCTSTR)IDD_GAMEINFO, hWnd, + (DLGPROC)GameInfo, (long)&state ); + + if ( !state.userCancelled ) { + if ( state.prefsChanged ) { + updateForColors( globals ); + } + draw = server_do( globals->game.server ); + } + } + break; + + case ID_FILE_NEWGAME: + XP_LOGF( "ID_FILE_NEWGAME" ); + if ( ceSaveCurGame( globals, XP_FALSE ) + || queryBoxChar( hWnd, "Do you really want to " + "overwrite the current game?" ) ) { + draw = ceDoNewGame( globals ); + } + break; + + case ID_FILE_SAVEDGAMES: + ceChooseAndOpen( globals ); + break; + + case ID_FILE_PREFERENCES: + ceDoPrefsDlg( globals ); + break; + case ID_FILE_FULLSCREEN: + ceToggleFullScreen( globals ); + break; + case ID_GAME_FINALSCORES: + if ( server_getGameIsOver( globals->game.server ) ) { + ceDisplayFinalScores( globals ); + } else if ( queryBoxChar( hWnd, "Are you sure you want to end " + "the game now?" ) ) { + server_endGame( globals->game.server ); + draw = TRUE; + } + break; + + case ID_GAME_TILECOUNTSANDVALUES: + ceCountsAndValues( globals ); + break; + + case ID_GAME_TILESLEFT: + ceTilesLeft( globals ); + break; + + case ID_GAME_HISTORY: + ceDoHistory( globals ); + break; + + case ID_MOVE_TRADE: + draw = handleTradeCmd( globals ); + break; + case ID_MOVE_JUGGLE: + draw = handleJuggleCmd( globals ); + break; + + case ID_MOVE_HIDETRAY: + draw = handleHidetrayCmd( globals ); + break; + case ID_MOVE_TURNDONE: + draw = handleDoneCmd( globals); + break; + + case ID_MOVE_FLIP: + draw = board_flip( globals->game.board ); + ceCheckMenus( globals ); + break; + case ID_MOVE_VALUES: + draw = board_toggle_showValues( globals->game.board ); + ceSetLeftSoftkey( globals, ID_MOVE_VALUES ); + ceCheckMenus( globals ); + break; + + case ID_MOVE_HINT: +#ifdef XWFEATURE_SEARCHLIMIT + case ID_MOVE_LIMITEDHINT: + globals->askTrayLimits = wmId == ID_MOVE_LIMITEDHINT; +#endif + board_resetEngine( globals->game.board ); + /* fallthru */ + case ID_MOVE_NEXTHINT: + draw = ceHandleHintRequest( globals ); + break; + + case ID_FILE_EXIT: + ceSaveAndExit( globals ); /* autosaves; no user interaction */ + break; + + case ID_MOVE_UNDOCURRENT: + draw = board_replaceTiles( globals->game.board ); + break; + + case ID_MOVE_UNDOLAST: + draw = server_handleUndo( globals->game.server ); + break; + + default: + callDefault = XP_TRUE; + } + break; + case WM_PAINT: + if ( !!globals ) { + RECT winrect; + if ( GetUpdateRect( hWnd, &winrect, FALSE ) ) { + if ( !!globals->game.board ) { + XP_Rect xprect; + /* When an obscuring window goes away, the update region + needs to be redrawn. This allows invalidating it. */ + + RECTtoXPR( &xprect, &winrect ); + board_invalRect( globals->game.board, &xprect ); + + XP_ASSERT( globals->hWnd == hWnd ); + drawInsidePaint( globals, &winrect ); + } + if ( !ValidateRect( hWnd, &winrect ) ) { + logLastError( "WM_PAINT:ValidateRect" ); + } + } + } + break; + + case WM_LBUTTONDOWN: + draw = checkPenDown( globals ); + globals->penDown = XP_TRUE; + draw = board_handlePenDown( globals->game.board, LOWORD(lParam), + HIWORD(lParam), &handled ) + || draw; + break; + + case WM_MOUSEMOVE: + if ( globals->penDown ) { + draw = board_handlePenMove( globals->game.board, + LOWORD(lParam), + HIWORD(lParam) ); + } + break; + + case WM_LBUTTONUP: + if ( globals->penDown ) { + draw = board_handlePenUp( globals->game.board, LOWORD(lParam), + HIWORD(lParam) ); + globals->penDown = XP_FALSE; + } + break; + +#ifdef OVERRIDE_BACKKEY + /* Make the back key mean raise focus, but only if dived. + Otherwise allow the OS to do what it wants. Which means + exit? */ + case WM_HOTKEY: + if ( (VK_TBACK == HIWORD(lParam)) && !!globals->game.board ) { + draw = board_handleKey( globals->game.board, + XP_RAISEFOCUS_KEY, &handled ); + if ( !draw && !handled + /* Hack alert. Winders sends two WM_HOTKEY events per + press of the key. (lParam isn't well documented for + this event, but likely they're down and up.) + Unfiltered, the first raises focus and the second + exits the app. Bad. So we'll only raise if the + first was not handled. Note that this may well break + on devices I haven't tested on, later, whenever. */ + && (0 == (BACK_KEY_UP_MAYBE & LOWORD(lParam))) ) { + XP_LOGF( "calling ceSaveAndExit for VK_TBACK" ); + /* I'm actually exiting the app rather than minimize. As + it stands, minimizing means that even if I relaunch + the app and quit properly I can't delete the .exe, + suggesting that the minimized guy isn't getting + reassociated when I relaunch. Until I fix that + exiting is best. + */ + ceSaveAndExit( globals ); + /* SHNavigateBack() is the right way to handle this, but + isn't available via cegcc yet. Others have suggested + this as well as ShowWindow( hWnd, SW_MINIMIZE ); + or SetWindowPos( hWnd, &CWnd::wndBottom, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE ); + */ + } + } + break; +#endif + +#ifdef KEYBOARD_NAV + case WM_KEYDOWN: + case WM_KEYUP: + draw = ceCheckHandleFocusKey( globals, wParam, lParam, + message==WM_KEYDOWN, &handled ); + break; +#endif + case WM_CHAR: + if ( wParam == 0x08 ) { + wParam = XP_CURSOR_KEY_DEL; +#ifdef KEYBOARD_NAV + } else if ( wParam == ' ' ) { + wParam = XP_RAISEFOCUS_KEY; +#endif + } + draw = board_handleKey( globals->game.board, wParam, &handled ) + || board_handleKey( globals->game.board, wParam - ('a'-'A'), + &handled ); + break; + + case WM_TIMER: + why = (XWTimerReason)wParam; + if ( why == TIMER_PENDOWN || why == TIMER_TIMERTICK +#if defined RELAY_HEARTBEAT || defined COMMS_HEARTBEAT + || why == TIMER_HEARTBEAT +#endif + ) { + XP_ASSERT( why < NUM_TIMERS_PLUS_ONE ); + + /* Kill since they otherwise repeat, but kill before firing + as fired proc may set another. */ + (void)KillTimer( hWnd, globals->timerIDs[why] ); + + draw = ceFireTimer( globals, why ); + } + break; + +#ifdef _WIN32_WCE +/* case WM_SETFOCUS: */ +/* hC = ImmGetContext( hWnd ); */ +/* globals->imeWasOpen = ImmGetOpenStatus( hC ); */ +/* ImmSetOpenStatus( hC, TRUE ); */ +/* ImmEscape( NULL, hC, IME_ESC_SET_MODE, (LPVOID)IM_SPELL ); */ +/* break; */ +/* case WM_KILLFOCUS: */ +/* ImmSetOpenStatus( hC, globals->imeWasOpen ); */ +/* break; */ + + /* The code above this point works to turn 12-key->text + translation on, but not to turn it off, so other apps wind up + with it on after Crosswords quits. The recommended code is + below, but includes constants not in the version of cegcc I'm + using. Need to look into upgrading, but that requires a lot + of changes. Post B2.... */ + +/* DWORD dwRes = SendMessage((HWND)wParam, WM_IME_REQUEST, IMR_ISIMEAWARE, 0); */ +/* hC = ImmGetContext( hWnd ); */ +/* if ( (dwRes & IMEAF_AWARE) == IMEAF_AWARE ) { */ +/* ImmEscape( NULL, hC, IME_ESC_RETAIN_MODE_ICON, (LPVOID)TRUE); */ +/* } */ +/* ImmSetOpenStatus( hC, FALSE); */ +/* } */ +/* break; */ +/* case WM_IME_REQUEST: */ +/* if ( wParam == IMR_ISIMEAWARE ) { */ +/* return IMEAF_AWARE; */ +/* } */ +/* break; */ +#endif + + case WM_DESTROY: +#ifdef _WIN32_WCE + CommandBar_Destroy(globals->hwndCB); /* supposedly not needed */ +#endif + PostQuitMessage(0); + freeGlobals( globals ); + break; + + case XWWM_TIME_RQST: + draw = server_do( globals->game.server ); + break; + + case XWWM_REM_SEL: + ceTilesLeft( globals ); + break; + +#ifndef XWFEATURE_STANDALONE_ONLY + case XWWM_PACKET_ARRIVED: + draw = processPacket( globals, (XWStreamCtxt*)lParam ); + break; +#endif + + default: + callDefault = XP_TRUE; + } + } + + if ( callDefault ) { + result = DefWindowProc(hWnd, message, wParam, lParam ); + } else if ( draw ) { + /* This is stupid. We can't just say "draw" because windoze clips + drawing to the inval rect, and the board isn't set up to tell us + what its inval rect is. So we inval everything, and then when the + WM_PAINT message comes we inval the whole board because there's a + huge inval rect. Dumb. Need to figure out how to have the + methods in cedraw.c set the clip region to encompass the object + being drawn -- taking board's word for it -- or the intersection + of that with the actual clip rgn in the case where some window's + gone away and revealed a large rect board didn't know about. That + was the source of some trouble on Palm, and CE's so fast this + works. But it's stupid. */ + RECT r = { 100, 100, 102, 102 }; + InvalidateRect( globals->hWnd, &r, FALSE /* erase */ ); + } + + return result; +} /* WndProc */ + +// Mesage handler for the About box. +LRESULT CALLBACK +ceAbout(HWND hDlg, UINT message, WPARAM wParam, LPARAM XP_UNUSED(lParam)) +{ + switch (message) { + case WM_INITDIALOG: + return TRUE; + + case WM_COMMAND: + if ((LOWORD(wParam) == IDOK) || (LOWORD(wParam) == IDCANCEL)) { + EndDialog(hDlg, LOWORD(wParam)); + return TRUE; + } + break; + } + return FALSE; +} /* ceAbout */ + +static XP_Bool +ceMsgFromStream( CEAppGlobals* globals, XWStreamCtxt* stream, + wchar_t* title, XP_U16 buttons, XP_Bool destroy ) +{ + /* It seems we want to use messagebox for everything on smartphone and + Windows, but not on PPC since it doesn't scroll and doesn't use + softkeys. Well, on Windows since there's no scrolling limit by + size */ + XP_Bool saidYes; + XP_Bool useMB; +#ifdef _WIN32_WCE + useMB = IS_SMARTPHONE(globals); +#else + useMB = stream_getSize(stream) <= 256; /* arbitrary... */ +#endif + if ( useMB ) { + int result = messageBoxStream( globals, stream, title, buttons ); + saidYes = (IDOK == result) | (IDRETRY == result) | (IDYES == result); + } else { + StrBoxState state; + + XP_MEMSET( &state, 0, sizeof(state) ); + + state.title = title; + state.stream = stream; + state.isQuery = (buttons & ~MB_ICONMASK) != MB_OK; + state.dlgHdr.globals = globals; + + assertOnTop( globals->hWnd ); + DialogBoxParam( globals->hInst, (LPCTSTR)IDD_STRBOX, globals->hWnd, + (DLGPROC)StrBox, (long)&state ); + saidYes = state.result == IDOK; + } + + if ( destroy ) { + stream_destroy( stream ); + } + + return saidYes; +} /* ceMsgFromStream */ + +static XP_UCHAR* +ceStreamToStrBuf( MPFORMAL XWStreamCtxt* stream ) +{ + XP_U16 len = stream_getSize( stream ); + XP_UCHAR* buf = XP_MALLOC( mpool, len + 1 ); + stream_getBytes( stream, buf, len ); + buf[len] = '\0'; + + return buf; +} /* ceStreamToStrBuf */ + +static int +messageBoxStream( CEAppGlobals* globals, XWStreamCtxt* stream, wchar_t* title, + XP_U16 buttons ) +{ + XP_UCHAR* buf = ceStreamToStrBuf( MPPARM(globals->mpool) stream ); + int result; + + assertOnTop( globals->hWnd ); + result = ceMessageBoxChar( globals, NULL, buf, title, buttons ); + + XP_FREE( globals->mpool, buf ); + return result; +} /* messageBoxStream */ + +XP_Bool +queryBoxChar( HWND hWnd, const XP_UCHAR* msg ) +{ + wchar_t widebuf[128]; + XP_U16 answer; + + XP_U16 len = MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, msg, -1, + widebuf, VSIZE(widebuf) ); + widebuf[len] = 0; + + answer = MessageBox( hWnd, widebuf, L"Question", + MB_YESNO | MB_ICONQUESTION ); + return answer == IDOK || answer == IDYES; +} /* queryBoxChar */ + +static XP_Bool +ceQueryFromStream( CEAppGlobals* globals, XWStreamCtxt* stream ) +{ + return ceMsgFromStream( globals, stream, L"Question", + MB_OKCANCEL | MB_ICONQUESTION, XP_FALSE ); +} /* ceQueryFromStream */ + +static void +RECTtoXPR( XP_Rect* dest, const RECT* src ) +{ + dest->top = (short)src->top; + dest->left = (short)src->left; + dest->width = (short)(src->right - src->left); + dest->height = (short)(src->bottom - src->top); +} /* RECTtoXPR */ + +void +wince_assert( XP_UCHAR* XP_UNUSED_LOG(s), int XP_UNUSED_LOG(line), + char* XP_UNUSED_LOG(fileName) ) +{ + XP_WARNF( "ASSERTION FAILED %s: file %s, line %d\n", s, fileName, line ); +} /* wince_assert */ + +#ifdef ENABLE_LOGGING +static void +makeTimeStamp( XP_UCHAR* timeStamp, XP_U16 XP_UNUSED_DBG(size) ) +{ + SYSTEMTIME st; + DWORD tid; + + tid = GetCurrentThreadId(); + + GetLocalTime( &st ); + sprintf( timeStamp, "<%lx>%d:%.2d:%.2d ", tid, st.wHour, st.wMinute, + st.wSecond ); + XP_ASSERT( size > strlen(timeStamp) ); +} /* makeTimeStamp */ + +void +wince_warnf(const XP_UCHAR* format, ...) +{ + XP_UCHAR buf[256]; + va_list ap; + XP_U16 slen; + + va_start( ap, format ); + vsnprintf( buf, sizeof(buf), format, ap ); + va_end(ap); + + slen = strlen(buf)+1; + wchar_t widebuf[slen]; + + MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, buf, slen, + widebuf, slen ); + + MessageBox( NULL, widebuf, L"WARNF", MB_OK | MB_ICONHAND ); +} /* wince_warnf */ + +void +wince_debugf(const XP_UCHAR* format, ...) +{ +#ifdef XWFEATURE_RELAY + static HANDLE s_logMutex = NULL; +#endif + XP_UCHAR buf[256]; + XP_UCHAR timeStamp[32]; + XP_U16 nBytes; + XP_U32 nWritten; + HANDLE fileH; + va_list ap; + wchar_t* logFileName; + + va_start( ap, format ); + vsprintf( buf, format, ap ); + va_end(ap); + + /* Create logfile if necessary and write to it in ascii. If there are + multiple threads, protect with mutex. */ + +#ifdef XWFEATURE_RELAY + if ( s_logMutex == NULL ) { + s_logMutex = CreateMutex( NULL, FALSE, NULL ); + } + if ( WaitForSingleObject( s_logMutex, 200L ) == WAIT_OBJECT_0 ) { +#endif + makeTimeStamp(timeStamp, sizeof(timeStamp)); + +#ifdef _WIN32_WCE + logFileName = L"\\My Documents\\" LCROSSWORDS_DIR L"\\xwDbgLog.txt"; +#else + logFileName = L"xwDbgLog.txt"; +#endif + fileH = CreateFile( logFileName, + GENERIC_WRITE, 0, NULL, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); + +#ifdef _WIN32_WCE_EMULATION + strcat( buf, "\n" ); +#else + strcat( buf, XP_CR ); +#endif + SetFilePointer( fileH, 0, 0, FILE_END ); +#ifdef DEBUG_TS + nBytes = strlen( timeStamp ); + WriteFile( fileH, timeStamp, nBytes, &nWritten, NULL ); +#endif + nBytes = strlen( buf ); + WriteFile( fileH, buf, nBytes, &nWritten, NULL ); + CloseHandle( fileH ); +#ifdef XWFEATURE_RELAY + ReleaseMutex( s_logMutex ); + } +#endif +} /* wince_debugf */ +#endif /* ENABLE_LOGGING */ + +XP_U16 +wince_snprintf( XP_UCHAR* buf, XP_U16 len, const XP_UCHAR* format, ... ) +{ + va_list ap; + + va_start( ap, format ); + + _vsnprintf( buf, len, format, ap ); + + /* FormatMessage( */ + /* FORMAT_MESSAGE_FROM_STRING, */ + /* format, */ + /* 0, */ + /* 0, // Default language */ + /* (LPTSTR)buf, */ + /* len, &ap ); */ + + va_end(ap); + + return strlen(buf); +} /* wince_snprintf */ + +#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH +static void +got_data_proc( XP_U8* data, XP_U16 len, void* closure ) +{ + /* Remember that this gets called by the reader thread, not by the one + running the window loop. */ + CEAppGlobals* globals = (CEAppGlobals*)closure; + BOOL posted; + XWStreamCtxt* stream; + + stream = make_generic_stream( globals ); + stream_putBytes( stream, data, len ); + + assertOnTop( globals->hWnd ); + posted = PostMessage( globals->hWnd, XWWM_PACKET_ARRIVED, + 0, (DWORD)stream ); + XP_ASSERT( posted ); +} /* got_data_proc */ +#endif + +#ifdef COMMS_HEARTBEAT +static void +ce_reset_proc( void* closure ) +{ + LOG_FUNC(); +} +#endif + +#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH +static XP_S16 +ce_send_proc( const XP_U8* buf, XP_U16 len, const CommsAddrRec* addr, + void* closure ) +{ + CEAppGlobals* globals = (CEAppGlobals*)closure; + XP_LOGF( "ce_send_proc called" ); + + if ( !globals->socketWrap ) { + globals->socketWrap = ce_sockwrap_new( MPPARM(globals->mpool) + addr->conType, + got_data_proc, globals ); + } + + return ce_sockwrap_send( globals->socketWrap, buf, len, addr ); +} /* ce_send_proc */ + +static void +ce_send_on_close( XWStreamCtxt* stream, void* closure ) +{ + CEAppGlobals* globals = (CEAppGlobals*)closure; + + XP_ASSERT( !!globals->game.comms ); + comms_send( globals->game.comms, stream ); +} +#endif + +static VTableMgr* +ce_util_getVTManager( XW_UtilCtxt* uc ) +{ + CEAppGlobals* globals = (CEAppGlobals*)uc->closure; + return globals->vtMgr; +} /* ce_util_getVTManager */ + +static void +ce_util_userError( XW_UtilCtxt* uc, UtilErrID id ) +{ + XP_UCHAR* message; + CEAppGlobals* globals = (CEAppGlobals*)uc->closure; + + switch( id ) { + case ERR_TILES_NOT_IN_LINE: + message = "All tiles played must be in a line."; + break; + case ERR_NO_EMPTIES_IN_TURN: + message = "Empty squares cannot separate tiles played."; + break; + + case ERR_TWO_TILES_FIRST_MOVE: + message = "Must play two or more pieces on the first move."; + break; + case ERR_TILES_MUST_CONTACT: + message = "New pieces must contact others already in place (or " + "the middle square on the first move)."; + break; + case ERR_NOT_YOUR_TURN: + message = "You can't do that; it's not your turn!"; + break; + case ERR_NO_PEEK_ROBOT_TILES: + message = "No peeking at the robot's tiles!"; + break; + case ERR_CANT_TRADE_MID_MOVE: + message = "Remove played tiles before trading."; + break; + case ERR_TOO_FEW_TILES_LEFT_TO_TRADE: + message = "Too few tiles left to trade."; + break; + case ERR_CANT_UNDO_TILEASSIGN: + message = "Tile assignment can't be undone."; + break; + + case ERR_CANT_HINT_WHILE_DISABLED: + message = "The hint feature is disabled for this game. Enable " + "it for a new game using the Preferences dialog."; + break; + +#ifndef XWFEATURE_STANDALONE_ONLY + case ERR_NO_PEEK_REMOTE_TILES: + message = "No peeking at remote players' tiles!"; + break; + case ERR_REG_UNEXPECTED_USER: + message = "Refused attempt to register unexpected user[s]."; + break; + case ERR_SERVER_DICT_WINS: + message = "Conflict between Host and Guest dictionaries; Host wins."; + XP_WARNF( "GTK may have problems here." ); + break; + case ERR_REG_SERVER_SANS_REMOTE: + message = "At least one player must be marked remote for a game " + "started as Host."; + break; +#endif + +#ifdef XWFEATURE_RELAY + case ERR_RELAY_BASE + XWRELAY_ERROR_TIMEOUT: + message = "The relay timed you out; usually that means " + "the other players didn't show."; + break; + case ERR_RELAY_BASE + XWRELAY_ERROR_HEART_YOU: + message = "You were disconnected from relay because it didn't " + "hear from you in too long."; + break; + case ERR_RELAY_BASE + XWRELAY_ERROR_HEART_OTHER: + case ERR_RELAY_BASE + XWRELAY_ERROR_LOST_OTHER: + message = "The relay has lost contact with a device in this game."; + break; +#endif + + default: + XP_LOGF( "%s(%d)", __func__, id ); + message = "unknown errorcode ID!!!"; + break; + } + + ceOops( globals, NULL, message ); +} /* ce_util_userError */ + +static XP_Bool +ce_util_userQuery( XW_UtilCtxt* uc, UtilQueryID id, XWStreamCtxt* stream ) +{ + char* query = NULL; + CEAppGlobals* globals = (CEAppGlobals*)uc->closure; + + switch( id ) { + case QUERY_COMMIT_TURN: + return ceQueryFromStream( globals, stream ); + + case QUERY_COMMIT_TRADE: + query = "Are you sure you want to trade the selected tiles?"; + assertOnTop( globals->hWnd ); + return queryBoxChar( globals->hWnd, query ); + + case QUERY_ROBOT_MOVE: + return ceMsgFromStream( globals, stream, L"FYI", + MB_OK | MB_ICONINFORMATION, XP_FALSE ); + + case QUERY_ROBOT_TRADE: + messageBoxStream( globals, stream, L"FYI", MB_OK | MB_ICONINFORMATION); + break; + + default: + XP_ASSERT(0); + } + + return XP_FALSE; +} /* ce_util_userQuery */ + +static XWBonusType +ce_util_getSquareBonus( XW_UtilCtxt* uc, const ModelCtxt* XP_UNUSED(model), + XP_U16 col, XP_U16 row ) +{ + XP_U16 index; + + CEAppGlobals* globals = (CEAppGlobals*)uc->closure; + + if ( !globals->bonusInfo ) { + HRSRC rsrcH; + HGLOBAL globH; + + rsrcH = FindResource( globals->hInst, MAKEINTRESOURCE(ID_BONUS_RES), + TEXT("BONS") ); + if ( !!rsrcH ) { + globH = LoadResource( globals->hInst, rsrcH ); + + if ( !!globH ) { + globals->bonusInfo = (XP_U16*)globH; + /* We don't want to call DeleteObject here, but should when + the app closes. Or does Wince free up all memory + associated with a process when it closes? PENDING(eeh) */ + // DeleteObject( globH ); + } + } + } + + if ( col > 7 ) col = 14 - col; + if ( row > 7 ) row = 14 - row; + index = (row*8) + col; + + if ( !globals->bonusInfo || (index >= 8*8) ) { + XP_ASSERT( 0 ); + return (XWBonusType)BONUS_NONE; + } else { + /* This is probably a bit slow. Consider caching the resource in + memory with one bonus value per byte. */ + XP_U16 value = globals->bonusInfo[index/4]; + value >>= ((3 - (index % 4)) * 4); + return value & 0x0F; + } +} /* ce_util_getSquareBonus */ + +static XP_S16 +ce_util_userPickTile( XW_UtilCtxt* uc, const PickInfo* pi, + XP_U16 playerNum, + const XP_UCHAR4* texts, XP_U16 nTiles ) +{ + BlankDialogState state; + CEAppGlobals* globals = (CEAppGlobals*)uc->closure; + XP_MEMSET( &state, 0, sizeof(state) ); + + state.dlgHdr.globals = globals; + state.texts = texts; + state.nTiles = nTiles; + state.playerNum = playerNum; + state.pi = pi; + + assertOnTop( globals->hWnd ); + DialogBoxParam( globals->hInst, (LPCTSTR)IDD_ASKBLANK, globals->hWnd, + (DLGPROC)BlankDlg, (long)&state ); + return state.result; +} /* ce_util_userPickTile */ + +static XP_Bool +ce_util_askPassword( XW_UtilCtxt* uc, const XP_UCHAR* name, + XP_UCHAR* buf, XP_U16* len ) +{ + PasswdDialogState state; + CEAppGlobals* globals = (CEAppGlobals*)uc->closure; + XP_MEMSET( &state, 0, sizeof(state) ); + + state.dlgHdr.globals = globals; + state.name = name; + state.buf = buf; + state.lenp = len; + + assertOnTop( globals->hWnd ); + DialogBoxParam( globals->hInst, (LPCTSTR)IDD_ASKPASS, globals->hWnd, + (DLGPROC)PasswdDlg, (long)&state ); + + return !state.userCancelled; +} /* ce_util_askPassword */ + +static void +ce_util_trayHiddenChange( XW_UtilCtxt* uc, XW_TrayVisState XP_UNUSED(newState), + XP_U16 nVisibleRows ) +{ + CEAppGlobals* globals = (CEAppGlobals*)uc->closure; + XP_U16 nHiddenRows; + +#ifdef CEFEATURE_CANSCROLL + /* If there's a scrollbar, hide/show it. It wants to be + active/visible only when the tray is NOT hidden */ + + if ( !!globals->scrollHandle ) { + nHiddenRows = model_numRows( globals->game.model ) - nVisibleRows; + updateScrollInfo( globals, nHiddenRows ); + } +#endif + ceCheckMenus( globals ); + drawInsidePaint( globals, NULL ); +} /* ce_util_trayHiddenChange */ + +static void +ce_util_yOffsetChange( XW_UtilCtxt* uc, XP_U16 XP_UNUSED(oldOffset), + XP_U16 newOffset ) +{ +#ifdef CEFEATURE_CANSCROLL + CEAppGlobals* globals = (CEAppGlobals*)uc->closure; + if ( !!globals->scrollHandle ) { + (void)SetScrollPos( globals->scrollHandle, SB_CTL, newOffset, XP_TRUE ); + } +#endif +} /* ce_util_yOffsetChange */ + +static void +ce_util_turnChanged( XW_UtilCtxt* uc ) +{ + CEAppGlobals* globals = (CEAppGlobals*)uc->closure; + ceSetLeftSoftkey( globals, ID_MOVE_TURNDONE ); +} + +static void +ce_util_notifyGameOver( XW_UtilCtxt* uc ) +{ + CEAppGlobals* globals = (CEAppGlobals*)uc->closure; + drawInsidePaint( globals, NULL ); + ceDisplayFinalScores( globals ); + + ceSetLeftSoftkey( globals, ID_FILE_NEWGAME ); +} /* ce_util_notifyGameOver */ + +static XP_Bool +ce_util_hiliteCell( XW_UtilCtxt* XP_UNUSED(uc), XP_U16 XP_UNUSED(col), + XP_U16 XP_UNUSED(row) ) +{ + return XP_TRUE; +} /* ce_util_hiliteCell */ + +static XP_Bool +ce_util_engineProgressCallback( XW_UtilCtxt* XP_UNUSED(uc) ) +{ + return XP_TRUE; +} /* ce_util_engineProgressCallback */ + +static void +ce_util_setTimer( XW_UtilCtxt* uc, XWTimerReason why, + XP_U16 XP_UNUSED_STANDALONE(when), XWTimerProc proc, + void* closure ) +{ + CEAppGlobals* globals = (CEAppGlobals*)uc->closure; + XP_U32 timerID; + XP_U32 howLong; + + XP_ASSERT( why < NUM_TIMERS_PLUS_ONE ); + globals->timerProcs[why] = proc; + globals->timerClosures[why] = closure; + + switch ( why ) { + case TIMER_PENDOWN: + howLong = 400; + break; + case TIMER_TIMERTICK: + howLong = 1000; /* 1 second */ + break; +#if defined RELAY_HEARTBEAT || defined COMMS_HEARTBEAT + case TIMER_HEARTBEAT: + howLong = when * 1000; + break; +#endif + default: + XP_ASSERT(0); + return; + } + + globals->timerWhens[why] = GetCurrentTime() + howLong; + + timerID = SetTimer( globals->hWnd, why, howLong, NULL); + + globals->timerIDs[why] = timerID; +} /* ce_util_setTimer */ + +static XP_Bool +ce_util_altKeyDown( XW_UtilCtxt* XP_UNUSED(uc) ) +{ + return GetKeyState(VK_LSHIFT) < 0 + || GetKeyState(VK_RSHIFT) < 0; +} + +static void +ce_util_requestTime( XW_UtilCtxt* uc ) +{ + CEAppGlobals* globals = (CEAppGlobals*)uc->closure; + + PostMessage( globals->hWnd, XWWM_TIME_RQST, 0, 0 ); +} /* palm_util_requestTime */ + +static XP_U32 +ce_util_getCurSeconds( XW_UtilCtxt* XP_UNUSED(uc) ) +{ + /* This function is never called! */ + XP_U32 ticks = GetCurrentTime(); + ticks /= 1000; + LOG_RETURNF( "%ld", ticks ); + return ticks; +/* return 0L; */ +} /* ce_util_getCurSeconds */ + +static DictionaryCtxt* +ce_util_makeEmptyDict( XW_UtilCtxt* uc ) +{ + CEAppGlobals* globals = (CEAppGlobals*)uc->closure; +#ifdef STUBBED_DICT + return make_stubbed_dict( MPPARM_NOCOMMA(globals->mpool) ); +#else + return ce_dictionary_make_empty( globals ); +#endif +} /* ce_util_makeEmptyDict */ + +#ifdef XWFEATURE_RELAY +static XWStreamCtxt* +ce_util_makeStreamFromAddr( XW_UtilCtxt* uc, XP_PlayerAddr channelNo ) +{ + XWStreamCtxt* stream; + CEAppGlobals* globals = (CEAppGlobals*)uc->closure; + + stream = make_generic_stream( globals ); + stream_setOnCloseProc( stream, ce_send_on_close ); + stream_setAddress( stream, channelNo ); + + return stream; +} /* ce_util_makeStreamFromAddr */ +#endif + +static const XP_UCHAR* +ce_util_getUserString( XW_UtilCtxt* XP_UNUSED(uc), XP_U16 stringCode ) +{ + switch( stringCode ) { + case STRD_REMAINING_TILES_ADD: + return (XP_UCHAR*)"+ %d [all remaining tiles]"; + case STRD_UNUSED_TILES_SUB: + return (XP_UCHAR*)"- %d [unused tiles]"; + case STR_BONUS_ALL: + return (XP_UCHAR*)"Bonus for using all tiles: 50" XP_CR; + case STRD_TURN_SCORE: + return (XP_UCHAR*)"Score for turn: %d" XP_CR; + case STR_COMMIT_CONFIRM: + return (XP_UCHAR*)"Commit the current move?" XP_CR; + case STR_LOCAL_NAME: + return (XP_UCHAR*)"%s"; + case STR_NONLOCAL_NAME: + return (XP_UCHAR*)"%s (remote)"; + case STRD_TIME_PENALTY_SUB: + return (XP_UCHAR*)" - %d [time]"; + + case STRD_CUMULATIVE_SCORE: + return (XP_UCHAR*)"Cumulative score: %d" XP_CR; + case STRS_MOVE_ACROSS: + return (XP_UCHAR*)"move (from %s across)" XP_CR; + case STRS_MOVE_DOWN: + return (XP_UCHAR*)"move (from %s down)" XP_CR; + case STRS_TRAY_AT_START: + return (XP_UCHAR*)"Tray at start: %s" XP_CR; + + case STRS_NEW_TILES: + return (XP_UCHAR*)"New tiles: %s" XP_CR; + case STRSS_TRADED_FOR: + return (XP_UCHAR*)"Traded %s for %s."; + case STR_PASS: + return (XP_UCHAR*)"pass" XP_CR; + case STR_PHONY_REJECTED: + return (XP_UCHAR*)"Illegal word in move; turn lost!" XP_CR; + + case STRD_ROBOT_TRADED: + return (XP_UCHAR*)"Robot traded tiles %d this turn."; + case STR_ROBOT_MOVED: + return (XP_UCHAR*)"The robot made this move:" XP_CR; + case STR_REMOTE_MOVED: + return (XP_UCHAR*)"Remote player made this move:" XP_CR; + + case STR_PASSED: + return (XP_UCHAR*)"Passed"; + case STRSD_SUMMARYSCORED: + return (XP_UCHAR*)"%s:%d"; + case STRD_TRADED: + return (XP_UCHAR*)"Traded %d"; + case STR_LOSTTURN: + return (XP_UCHAR*)"Lost turn"; + +#ifndef XWFEATURE_STANDALONE_ONLY + case STR_LOCALPLAYERS: + return (XP_UCHAR*)"Locl playrs:"; +#endif + case STR_TOTALPLAYERS: + return (XP_UCHAR*)"Player count:"; + + case STRS_VALUES_HEADER: + return (XP_UCHAR*)"%s counts/values:" XP_CR; + + default: + XP_LOGF( "stringCode=%d", stringCode ); + return (XP_UCHAR*)"unknown code"; + } +} /* ce_util_getUserString */ + +static void +ce_formatBadWords( BadWordInfo* bwi, XP_UCHAR buf[], XP_U16 bufsiz ) +{ + XP_U16 i; + + for ( i = 0, buf[0] = '\0'; ; ) { + XP_UCHAR wordBuf[18]; + sprintf( wordBuf, "\"%s\"", bwi->words[i] ); + XP_ASSERT( strlen(wordBuf) < sizeof(wordBuf)-1 ); + strncat( buf, wordBuf, bufsiz - 1 ); + if ( ++i == bwi->nWords ) { + break; + } + bufsiz -= strlen( wordBuf ); + strncat( buf, ", ", bufsiz - 1 ); + bufsiz -= 2; + } +} /* ce_formatBadWords */ + +static XP_Bool +ce_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi, + XP_U16 XP_UNUSED(turn), XP_Bool turnLost ) +{ + CEAppGlobals* globals = (CEAppGlobals*)uc->closure; + XP_UCHAR wordsBuf[256]; + XP_UCHAR msgBuf[256]; + XP_Bool isOk; + + ce_formatBadWords( bwi, wordsBuf, sizeof(wordsBuf) ); + sprintf( msgBuf, "Word[s] %s not found in dictionary.", wordsBuf ); + + if ( turnLost ) { + ceMessageBoxChar( globals, NULL, msgBuf, L"Illegal word", + MB_OK | MB_ICONHAND ); + isOk = XP_TRUE; + } else { + strcat( msgBuf, " Use it anyway?" ); + assertOnTop( globals->hWnd ); + isOk = queryBoxChar( globals->hWnd, msgBuf ); + } + + return isOk; +} /* ce_util_warnIllegalWord */ + +static void +ce_util_remSelected( XW_UtilCtxt* uc ) +{ + CEAppGlobals* globals = (CEAppGlobals*)uc->closure; + PostMessage( globals->hWnd, XWWM_REM_SEL, 0, 0 ); +} + +#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH +static void +ce_util_addrChange( XW_UtilCtxt* XP_UNUSED(uc), + const CommsAddrRec* XP_UNUSED(oldAddr), + const CommsAddrRec* XP_UNUSED(newAddr) ) +{ + XP_LOGF( "ce_util_addrChange called; DO SOMETHING." ); +} /* ce_util_addrChange */ +#endif + +#ifdef XWFEATURE_SEARCHLIMIT +static XP_Bool +ce_util_getTraySearchLimits( XW_UtilCtxt* uc, XP_U16* min, XP_U16* max ) +{ + CEAppGlobals* globals = (CEAppGlobals*)uc->closure; + HintLimitsState hls; + + XP_MEMSET( &hls, 0, sizeof(hls) ); + + hls.dlgHdr.globals = globals; + hls.min = *min; + hls.max = *max; + + assertOnTop( globals->hWnd ); + DialogBoxParam( globals->hInst, (LPCTSTR)IDD_ASKHINTLIMTS, globals->hWnd, + (DLGPROC)HintLimitsDlg, (long)&hls ); + + if ( !hls.cancelled ) { + *min = hls.min; + *max = hls.max; + } + + return !hls.cancelled; +} /* ce_util_getTraySearchLimits */ +#endif + +#ifdef SHOW_PROGRESS +blah blah -- these are not in use +static void +ce_util_engineStarting( XW_UtilCtxt* uc ) +{ +} /* ce_util_engineStarting */ + +static void +ce_util_engineStopping( XW_UtilCtxt* uc ) +{ +} /* ce_util_engineStopping */ +#endif diff --git a/xwords4/wince/cemain.h b/xwords4/wince/cemain.h new file mode 100755 index 000000000..885c2b71b --- /dev/null +++ b/xwords4/wince/cemain.h @@ -0,0 +1,215 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4;-*- */ +/* + * Copyright 2000-2008 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CEMAIN_H_ +#define _CEMAIN_H_ + +#ifdef _WIN32_WCE +# include +#endif +#include "draw.h" +#include "game.h" +#include "util.h" +#include "mempool.h" +#include "cesockwr.h" + +#define LCROSSWORDS_DIR_NODBG L"Crosswords" +#define CE_GAMEFILE_VERSION1 0x01 /* means draw gets to save/restore */ +#define CE_GAMEFILE_VERSION2 0x02 /* save/restore includes width */ +#define CE_GAMEFILE_VERSION CE_GAMEFILE_VERSION2 +#ifdef DEBUG +# define CROSSWORDS_DIR "Cross_dbg" +# define LCROSSWORDS_DIR L"Cross_dbg" +#else +# define CROSSWORDS_DIR "Crosswords" +# define LCROSSWORDS_DIR LCROSSWORDS_DIR_NODBG +#endif + +#ifdef _WIN32_WCE +typedef enum { + WINCE_UNKNOWN + , WINCE_PPC_V1 + , WINCE_PPC_2003 + , WINCE_PPC_2005 + , _LAST_PPC /* so can test for PPC */ + , WINCE_SMARTPHONE_V1 + , WINCE_SMARTPHONE_2003 + , WINCE_SMARTPHONE_2005 +} XW_WinceVersion; + +# define IS_SMARTPHONE(g) ((g)->winceVersion > _LAST_PPC) +#else +# define IS_SMARTPHONE(g) ((g) != (g)) /* make compiler warnings go away */ +#endif + +enum { CE_BONUS0_COLOR, + CE_BONUS1_COLOR, + CE_BONUS2_COLOR, + CE_BONUS3_COLOR, + + CE_BKG_COLOR, + CE_TILEBACK_COLOR, + + CE_FOCUS_COLOR, + + CE_PLAYER0_COLOR, + CE_PLAYER1_COLOR, + CE_PLAYER2_COLOR, + CE_PLAYER3_COLOR, + + CE_BLACK_COLOR, /* not editable by users */ + CE_WHITE_COLOR, + + CE_NUM_COLORS /* last */ +}; + +#define CUR_CE_PREFS_FLAGS 0x0003 /* adds CE_FOCUS_COLOR */ + +/* This is what CEAppPrefs looked like for CUR_CE_PREFS_FLAGS == 0x0002 */ +typedef struct CEAppPrefs0002 { + XP_U16 versionFlags; + CommonPrefs cp; + COLORREF colors[12]; /* CE_FOCUS_COLOR wasn't there */ + XP_Bool showColors; +} CEAppPrefs0002; + +typedef struct CEAppPrefs { + XP_U16 versionFlags; + CommonPrefs cp; + COLORREF colors[CE_NUM_COLORS]; + XP_Bool showColors; + XP_Bool fullScreen; +} CEAppPrefs; + +enum { OWNED_RECT_LEFT + ,OWNED_RECT_RIGHT + ,OWNED_RECT_TOP + ,OWNED_RECT_BOTTOM + ,N_OWNED_RECTS +}; + +enum { + MY_DOCS_CACHE, + PROGFILES_CACHE, + N_CACHED_PATHS +}; + +typedef struct CEAppGlobals { + HINSTANCE hInst; + HDC hdc; /* to pass drawing ctxt to draw code */ + HWND hWnd; +#ifdef _WIN32_WCE + HWND hwndCB; + SHACTIVATEINFO sai; + XW_WinceVersion winceVersion; +#else + /* Store location of dummy button */ + HMENU dummyMenu; + XP_U16 dummyPos; +#endif + + XP_U16 softKeyId; /* id of item now on left button */ +#ifndef _WIN32_WCE + HMENU softKeyMenu; /* so can check/uncheck duplicated items */ +#endif + + struct CEDrawCtx* draw; + XWGame game; + CurGameInfo gameInfo; + XP_UCHAR* curGameName; /* path to storage for current game */ + XW_UtilCtxt util; + VTableMgr* vtMgr; + XP_U16* bonusInfo; + + XP_U32 timerIDs[NUM_TIMERS_PLUS_ONE]; + XWTimerProc timerProcs[NUM_TIMERS_PLUS_ONE]; + void* timerClosures[NUM_TIMERS_PLUS_ONE]; + XP_U32 timerWhens[NUM_TIMERS_PLUS_ONE]; + + RECT ownedRects[N_OWNED_RECTS]; + + XP_U16 flags; /* bits defined below */ + XP_U16 cellHt; /* how tall is a cell given current layout */ + +#ifdef CEFEATURE_CANSCROLL + HWND scrollHandle; + WNDPROC oldScrollProc; +#ifdef _WIN32_WCE + RECT scrollRects[2]; /* above and below the scroller */ +#endif + XP_Bool scrollerHasFocus; +#endif +#ifdef KEYBOARD_NAV + XP_Bool keyDown; +#endif + CeSocketWrapper* socketWrap; + + CEAppPrefs appPrefs; + + XP_Bool isNewGame; + XP_Bool penDown; + XP_Bool hintPending; + XP_Bool doGlobalPrefs; + +#ifndef _WIN32_WCE + XP_U16 dbWidth, dbHeight; +#endif + + wchar_t* specialDirs[N_CACHED_PATHS]; /* reserved for ceGetPath() */ + +#ifdef XWFEATURE_SEARCHLIMIT + XP_Bool askTrayLimits; +#endif + MPSLOT + +} CEAppGlobals; + +/* No longer used, but may need to keep set for backwards compatibility */ +# define FLAGS_BIT_SHOWN_NEWDICTLOC 0x0001 + +#define GAME_IN_PROGRESS(g) ((g)->gameInfo.dictName != 0) + +enum { + XWWM_TIME_RQST = WM_APP + ,XWWM_REM_SEL + ,XWWM_PACKET_ARRIVED + +}; + +#define CE_NUM_EDITABLE_COLORS CE_BLACK_COLOR + + +XP_Bool queryBoxChar( HWND hWnd, const XP_UCHAR* msg ); + +/* These allow LISTBOX and COMBOBOX to be used by the same code */ + +#define INSERTSTRING(g) (IS_SMARTPHONE(g)?LB_INSERTSTRING:CB_INSERTSTRING) +#define SETCURSEL(g) (IS_SMARTPHONE(g)?LB_SETCURSEL:CB_SETCURSEL) +#define GETCURSEL(g) (IS_SMARTPHONE(g)?LB_GETCURSEL:CB_GETCURSEL) +#define ADDSTRING(g) (IS_SMARTPHONE(g)?LB_ADDSTRING:CB_ADDSTRING) +#define GETLBTEXT(g) (IS_SMARTPHONE(g)?LB_GETTEXT:CB_GETLBTEXT) +#define GETLBTEXTLEN(g) (IS_SMARTPHONE(g)?LB_GETTEXTLEN:CB_GETLBTEXTLEN) +#define FINDSTRINGEXACT(g) \ + (IS_SMARTPHONE(g)?LB_FINDSTRINGEXACT:CB_FINDSTRINGEXACT) +#define LB_IF_PPC(g,id) (IS_SMARTPHONE(g)?id:(id+2)) + + +#define BACK_KEY_UP_MAYBE 0x1000 +#define CE_MAX_PATH_LEN 256 +#endif /* _CEMAIN_H_ */ diff --git a/xwords4/wince/ceprefs.c b/xwords4/wince/ceprefs.c new file mode 100755 index 000000000..e6b2ac8d4 --- /dev/null +++ b/xwords4/wince/ceprefs.c @@ -0,0 +1,383 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include + +#include "ceprefs.h" +#include "cemain.h" +#include "ceclrsel.h" +#include "ceutil.h" +#include "debhacks.h" +#include "cedebug.h" +#include "cefonts.h" + +/* Stuff the strings for phonies. Why can't I put this in the resource? + */ +static void +stuffPhoniesList( CePrefsDlgState* state ) +{ + HWND hDlg = state->dlgHdr.hDlg; + CEAppGlobals* globals = state->dlgHdr.globals; + XP_U16 ii; + wchar_t* strings[] = { + L"Ignore", + L"Warn", + L"Disallow" + }; + + for ( ii = 0; ii < 3; ++ii ) { + SendDlgItemMessage( hDlg, state->phonComboId, + ADDSTRING(globals), ii, (long)strings[ii] ); + } +} /* stuffPhoniesList */ + +static void +turnOnOff( HWND hDlg, XP_U16* idList, XP_U16 idCount, + XP_Bool turnOn ) +{ + XP_U16 i; + for ( i = 0; i < idCount; ++i ) { + ceShowOrHide( hDlg, *idList++, turnOn ); + } +} /* turnOff */ + +static void +setTimerCtls( HWND hDlg, XP_Bool checked ) +{ + ceShowOrHide( hDlg, TIMER_EDIT, checked ); + + SendDlgItemMessage( hDlg, TIMER_CHECK, BM_SETCHECK, + checked? BST_CHECKED:BST_UNCHECKED, 0 ); +} /* setTimerCtls */ + +static void +adjustForChoice( CePrefsDlgState* state ) +{ + HWND hDlg = state->dlgHdr.hDlg; + XP_U16 goesWithGlobal[] = {IDC_CHECKCOLORPLAYED, IDC_LEFTYCHECK, + IDC_CHECKSHOWCURSOR, IDC_CHECKROBOTSCORES, + IDC_HIDETILEVALUES, IDC_PREFCOLORS +#ifdef ALLOW_CHOOSE_FONTS + ,IDC_PREFFONTS +#endif + + }; + XP_U16 goesWithLocal[] = {IDC_CHECKSMARTROBOT, IDC_CHECKHINTSOK, + TIMER_CHECK, TIMER_EDIT, PHONIES_LABEL, + PHONIES_COMBO, IDC_PHONIESUPDOWN, + PHONIES_COMBO_PPC, + IDC_PICKTILES +#ifdef XWFEATURE_SEARCHLIMIT + ,IDC_CHECKHINTSLIMITS +#endif + }; + XP_U16 resID; + XP_Bool doGlobalPrefs = state->dlgHdr.globals->doGlobalPrefs; + + resID = doGlobalPrefs? IDC_RADIOGLOBAL:IDC_RADIOLOCAL; + SendDlgItemMessage( hDlg, resID, BM_SETCHECK, BST_CHECKED, 0L ); + resID = doGlobalPrefs? IDC_RADIOLOCAL:IDC_RADIOGLOBAL; + SendDlgItemMessage( hDlg, resID, BM_SETCHECK, BST_UNCHECKED, 0L ); + + if ( doGlobalPrefs ) { + turnOnOff( hDlg, goesWithLocal, VSIZE(goesWithLocal), XP_FALSE ); + turnOnOff( hDlg, goesWithGlobal, VSIZE(goesWithGlobal), XP_TRUE); + } else { + turnOnOff( hDlg, goesWithGlobal, VSIZE(goesWithGlobal), XP_FALSE ); + turnOnOff( hDlg, goesWithLocal, VSIZE(goesWithLocal), XP_TRUE); + } + + if ( !doGlobalPrefs ) { + setTimerCtls( hDlg, ceGetChecked( hDlg, TIMER_CHECK ) ); +#ifdef XWFEATURE_SEARCHLIMIT + ceShowOrHide( hDlg, IDC_CHECKHINTSLIMITS, + ceGetChecked( hDlg, IDC_CHECKHINTSOK) ); +#endif + ceDlgComboShowHide( &state->dlgHdr, PHONIES_COMBO ); + } +} /* adjustForChoice */ + +/* Copy global state into a local copy that can be changed without + * committing should user cancel. + */ +void +loadStateFromCurPrefs( CEAppGlobals* XP_UNUSED_STANDALONE(globals), + const CEAppPrefs* appPrefs, + const CurGameInfo* gi, CePrefsPrefs* prefsPrefs ) +{ + prefsPrefs->gp.hintsNotAllowed = gi->hintsNotAllowed; + prefsPrefs->gp.robotSmartness = gi->robotSmartness; + prefsPrefs->gp.timerEnabled = gi->timerEnabled; + prefsPrefs->gp.gameSeconds = gi->gameSeconds; + prefsPrefs->gp.phoniesAction = gi->phoniesAction; +#ifdef FEATURE_TRAY_EDIT + prefsPrefs->gp.allowPickTiles = gi->allowPickTiles; +#endif +#ifdef XWFEATURE_SEARCHLIMIT + prefsPrefs->gp.allowHintRect = gi->allowHintRect; +#endif + prefsPrefs->showColors = appPrefs->showColors; + + XP_MEMCPY( &prefsPrefs->cp, &appPrefs->cp, sizeof(prefsPrefs->cp) ); + XP_MEMCPY( &prefsPrefs->colors, &appPrefs->colors, + sizeof(prefsPrefs->colors) ); + +#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH + if ( globals->game.comms != NULL ) { + comms_getAddr( globals->game.comms, &prefsPrefs->addrRec ); + } else { + comms_getInitialAddr( &prefsPrefs->addrRec ); + } +#endif +} /* loadStateFromCurPrefs */ + +void +loadCurPrefsFromState( CEAppGlobals* XP_UNUSED_STANDALONE(globals), + CEAppPrefs* appPrefs, + CurGameInfo* gi, const CePrefsPrefs* prefsPrefs ) +{ + gi->hintsNotAllowed = prefsPrefs->gp.hintsNotAllowed; + gi->robotSmartness = prefsPrefs->gp.robotSmartness; + gi->timerEnabled = prefsPrefs->gp.timerEnabled; + gi->gameSeconds = prefsPrefs->gp.gameSeconds; + gi->phoniesAction = prefsPrefs->gp.phoniesAction; +#ifdef FEATURE_TRAY_EDIT + gi->allowPickTiles = prefsPrefs->gp.allowPickTiles; +#endif +#ifdef XWFEATURE_SEARCHLIMIT + gi->allowHintRect = prefsPrefs->gp.allowHintRect; +#endif + appPrefs->showColors = prefsPrefs->showColors; + + XP_MEMCPY( &appPrefs->cp, &prefsPrefs->cp, sizeof(appPrefs->cp) ); + XP_MEMCPY( &appPrefs->colors, &prefsPrefs->colors, + sizeof(prefsPrefs->colors) ); + +#ifndef XWFEATURE_STANDALONE_ONLY + /* I don't think this'll work... */ + if ( globals->game.comms != NULL ) { + comms_setAddr( globals->game.comms, &prefsPrefs->addrRec ); + } else { + XP_LOGF( "no comms to set addr on!!!" ); + } +#endif +} /* loadCurPrefsFromState */ + +/* Reflect local state into the controls user will see. + */ +static void +loadControlsFromState( CePrefsDlgState* pState ) +{ + HWND hDlg = pState->dlgHdr.hDlg; + CEAppGlobals* globals = pState->dlgHdr.globals; + CePrefsPrefs* prefsPrefs = &pState->prefsPrefs; + + ceSetChecked( hDlg, IDC_CHECKCOLORPLAYED, prefsPrefs->showColors ); + ceSetChecked( hDlg, IDC_CHECKSMARTROBOT, + prefsPrefs->gp.robotSmartness > 0 ); + ceSetChecked( hDlg, IDC_CHECKHINTSOK, !prefsPrefs->gp.hintsNotAllowed ); + + ceSetChecked( hDlg, IDC_CHECKSHOWCURSOR, prefsPrefs->cp.showBoardArrow ); + ceSetChecked( hDlg, IDC_CHECKROBOTSCORES, prefsPrefs->cp.showRobotScores ); + ceSetChecked( hDlg, IDC_HIDETILEVALUES, prefsPrefs->cp.hideTileValues ); + +#ifdef FEATURE_TRAY_EDIT + ceSetChecked( hDlg, IDC_PICKTILES, prefsPrefs->gp.allowPickTiles ); +#endif +#ifdef XWFEATURE_SEARCHLIMIT + if ( !IS_SMARTPHONE(globals) ) { + ceSetChecked( hDlg, IDC_CHECKHINTSLIMITS, prefsPrefs->gp.allowHintRect ); + } +#endif + /* timer */ + ceSetDlgItemNum( hDlg, TIMER_EDIT, prefsPrefs->gp.gameSeconds / 60 ); + + SendDlgItemMessage( hDlg, pState->phonComboId, SETCURSEL(globals), + prefsPrefs->gp.phoniesAction, 0L ); + + if ( !pState->isNewGame ) { + XP_U16 unavail[] = { TIMER_CHECK, TIMER_EDIT, IDC_CHECKHINTSOK +#ifdef FEATURE_TRAY_EDIT + ,IDC_PICKTILES +#endif + }; + XP_U16 i; + for ( i = 0; i < VSIZE(unavail); ++i ) { + ceEnOrDisable( hDlg, unavail[i], XP_FALSE ); + } + } +} /* loadControlsFromState */ + +/* Save the new choices into state so caller can do what it wants with + * the values. + */ +static void +ceControlsToPrefs( CePrefsDlgState* state ) +{ + XP_S16 selIndex; + CePrefsPrefs* prefsPrefs = &state->prefsPrefs; + HWND hDlg = state->dlgHdr.hDlg; + CEAppGlobals* globals = state->dlgHdr.globals; + + prefsPrefs->showColors = ceGetChecked( hDlg, IDC_CHECKCOLORPLAYED ); + prefsPrefs->gp.robotSmartness + = ceGetChecked( hDlg, IDC_CHECKSMARTROBOT ) ? 1 : 0; + prefsPrefs->gp.hintsNotAllowed = !ceGetChecked( hDlg, IDC_CHECKHINTSOK ); + + selIndex = (XP_U16)SendDlgItemMessage( hDlg, state->phonComboId, + GETCURSEL(globals), + 0, 0 ); + if ( selIndex != LB_ERR ) { + prefsPrefs->gp.phoniesAction = (XWPhoniesChoice)selIndex; + } + + prefsPrefs->cp.showBoardArrow = ceGetChecked( hDlg, IDC_CHECKSHOWCURSOR ); + prefsPrefs->cp.showRobotScores = ceGetChecked( hDlg, IDC_CHECKROBOTSCORES ); + prefsPrefs->cp.hideTileValues = ceGetChecked( hDlg, IDC_HIDETILEVALUES ); + prefsPrefs->gp.timerEnabled = ceGetChecked( hDlg, TIMER_CHECK ); + + if ( prefsPrefs->gp.timerEnabled ) { + XP_U16 minutes; + + minutes = ceGetDlgItemNum( hDlg, TIMER_EDIT ); + + prefsPrefs->gp.gameSeconds = minutes * 60; + } +#ifdef FEATURE_TRAY_EDIT + prefsPrefs->gp.allowPickTiles = ceGetChecked( hDlg, IDC_PICKTILES ); +#endif +#ifdef XWFEATURE_SEARCHLIMIT + if ( !IS_SMARTPHONE(globals) ) { + prefsPrefs->gp.allowHintRect = ceGetChecked( hDlg, IDC_CHECKHINTSLIMITS ); + } +#endif +} /* ceControlsToPrefs */ + +LRESULT CALLBACK +PrefsDlg(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + XP_U16 id; + CePrefsDlgState* pState; + + if ( message == WM_INITDIALOG ) { + SetWindowLongPtr( hDlg, GWL_USERDATA, lParam ); + pState = (CePrefsDlgState*)lParam; + + pState->phonComboId = LB_IF_PPC(pState->dlgHdr.globals,PHONIES_COMBO); + + ceDlgSetup( &pState->dlgHdr, hDlg, DLG_STATE_TRAPBACK ); + ceDlgComboShowHide( &pState->dlgHdr, PHONIES_COMBO ); + + stuffPhoniesList( pState ); + + loadControlsFromState( pState ); + + adjustForChoice( pState ); + return TRUE; + + } else { + pState = (CePrefsDlgState*)GetWindowLongPtr( hDlg, GWL_USERDATA ); + if ( !!pState ) { + if ( !ceDoDlgHandle( &pState->dlgHdr, message, wParam, lParam ) ) { + CEAppGlobals* globals = pState->dlgHdr.globals; + XP_Bool timerOn; + + switch (message) { + case WM_COMMAND: + id = LOWORD(wParam); + switch( id ) { + + case IDC_RADIOGLOBAL: + case IDC_RADIOLOCAL: + globals->doGlobalPrefs = id == IDC_RADIOGLOBAL; + adjustForChoice( pState ); + break; + + case TIMER_CHECK: + timerOn = SendDlgItemMessage( hDlg, TIMER_CHECK, + BM_GETCHECK, 0, 0 ); + setTimerCtls( hDlg, timerOn ); + break; + case IDC_PREFCOLORS: + pState->colorsChanged = + ceDoColorsEdit( hDlg, globals, + pState->prefsPrefs.colors ); + break; +#ifdef ALLOW_CHOOSE_FONTS + case IDC_PREFFONTS: + ceShowFonts( hDlg, globals ); + break; +#endif + +#ifdef XWFEATURE_SEARCHLIMIT + case IDC_CHECKHINTSOK: + timerOn = SendDlgItemMessage( hDlg, IDC_CHECKHINTSOK, + BM_GETCHECK, 0, 0 ); + ceShowOrHide( hDlg, IDC_CHECKHINTSLIMITS, timerOn ); + break; + case IDC_CHECKHINTSLIMITS: + if ( IS_SMARTPHONE(globals) ) { + ceMessageBoxChar( globals, hDlg, "This feature " + "requires a touch screen.", + L"Sorry", MB_OK | MB_ICONHAND ); + ceSetChecked( hDlg, IDC_CHECKHINTSLIMITS, XP_FALSE ); + } + break; +#endif + + case IDOK: + ceControlsToPrefs( pState ); + case IDCANCEL: + EndDialog(hDlg, id); + pState->userCancelled = id == IDCANCEL; + return TRUE; + } + } + } + } + } + + return FALSE; +} /* PrefsDlg */ + +/* Using state in prefsPrefs, and initing and then storing dialog state in + state, put up the dialog and return whether it was cancelled. + */ +XP_Bool +WrapPrefsDialog( HWND hDlg, CEAppGlobals* globals, CePrefsDlgState* state, + CePrefsPrefs* prefsPrefs, XP_Bool isNewGame ) +{ + XP_Bool result; + XP_MEMSET( state, 0, sizeof(*state) ); + + state->dlgHdr.globals = globals; + state->isNewGame = isNewGame; + XP_MEMCPY( &state->prefsPrefs, prefsPrefs, sizeof( state->prefsPrefs ) ); + + DialogBoxParam( globals->hInst, (LPCTSTR)IDD_OPTIONSDLG, hDlg, + (DLGPROC)PrefsDlg, (long)state ); + + result = !state->userCancelled; + + if ( result ) { + XP_MEMCPY( prefsPrefs, &state->prefsPrefs, sizeof( *prefsPrefs ) ); + } + + return result; +} /* WrapPrefsDialog */ diff --git a/xwords4/wince/ceprefs.h b/xwords4/wince/ceprefs.h new file mode 100755 index 000000000..cba925746 --- /dev/null +++ b/xwords4/wince/ceprefs.h @@ -0,0 +1,80 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2002-2007 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CEPREFS_H_ +#define _CEPREFS_H_ + +#include "stdafx.h" +#include "cemain.h" +#include "ceutil.h" + +typedef struct CeGamePrefs { + XP_U16 gameSeconds; + XP_Bool hintsNotAllowed; + XP_U8 robotSmartness; + XP_Bool timerEnabled; +#ifdef FEATURE_TRAY_EDIT + XP_Bool allowPickTiles; +#endif +#ifdef XWFEATURE_SEARCHLIMIT + XP_Bool allowHintRect; +#endif + + XWPhoniesChoice phoniesAction; + /* phonies something */ +} CeGamePrefs; + +typedef struct CePrefsPrefs { + /* per-game */ + CeGamePrefs gp; + +#ifndef XWFEATURE_STANDALONE_ONLY + CommsAddrRec addrRec; +#endif + + /* global */ + CommonPrefs cp; + XP_Bool showColors; + + COLORREF colors[CE_NUM_EDITABLE_COLORS]; +} CePrefsPrefs; + +typedef struct CePrefsDlgState { + CeDlgHdr dlgHdr; + CePrefsPrefs prefsPrefs; + + XP_U16 phonComboId; + + XP_Bool userCancelled; + //XP_Bool doGlobalPrefs; /* state of the radio */ + XP_Bool isNewGame; + XP_Bool colorsChanged; +} CePrefsDlgState; + +XP_Bool WrapPrefsDialog( HWND hDlg, CEAppGlobals* globals, + CePrefsDlgState* state, CePrefsPrefs* prefsPrefs, + XP_Bool isNewGame ); +void loadStateFromCurPrefs( CEAppGlobals* globals, const CEAppPrefs* appPrefs, + const CurGameInfo* gi, CePrefsPrefs* prefsPrefs ); +void loadCurPrefsFromState( CEAppGlobals* globals, CEAppPrefs* appPrefs, + CurGameInfo* gi, const CePrefsPrefs* prefsPrefs ); + +LRESULT CALLBACK PrefsDlg(HWND, UINT, WPARAM, LPARAM); + +#endif diff --git a/xwords4/wince/cesockwr.c b/xwords4/wince/cesockwr.c new file mode 100755 index 000000000..a13fb93ed --- /dev/null +++ b/xwords4/wince/cesockwr.c @@ -0,0 +1,441 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef XWFEATURE_STANDALONE_ONLY + +#include "cesockwr.h" +#include "cemain.h" +#include "debhacks.h" + +#include + +/* This object owns all network activity: sending and receiving packets. It + maintains two threads, one to send and the other to listen. Incoming + packets are passed out via a proc passed into the "constructor". Outgoing + packets are passed in directly. Uses TCP, and the relay framing protocol + wherein each packet is proceeded by its length in two bytes, network byte + order. +*/ + +enum { WRITER_THREAD, + READER_THREAD, + N_THREADS }; + +typedef enum { + CE_IP_NONE, /* shouldn't be used */ + CE_IP_UNCONNECTED, + CE_IP_CONNECTED +} CE_CONNSTATE; + +#define MAX_QUEUE_SIZE 3 + + struct CeSocketWrapper { + DataRecvProc dataProc; + void* dataClosure; + + XP_U8* packets[MAX_QUEUE_SIZE]; + XP_U16 lens[MAX_QUEUE_SIZE]; + XP_U16 nPackets; + + CommsAddrRec addrRec; + + SOCKET socket; + CE_CONNSTATE connState; + + HANDLE queueAddEvent; + HANDLE socketConnEvent; + + HANDLE queueMutex; + HANDLE threads[N_THREADS]; + +#ifdef DEBUG + XP_U16 nSent; +#endif + + MPSLOT +}; + +/* queue_packet: Place packet on queue using semaphore. Return false + * if no room or fail for some other reason. + */ +static XP_Bool +queue_packet( CeSocketWrapper* self, XP_U8* packet, XP_U16 len ) +{ + DWORD wres; + XP_Bool success = XP_FALSE; + + // 2/5 second time-out interval. This is called from the UI thread, so + // long pauses are unacceptable. comms will have to try again if for + // some reason the queue is locked for that long. + wres = WaitForSingleObject( self->queueMutex, 200L ); + + if ( wres == WAIT_OBJECT_0 ) { + if ( self->nPackets < MAX_QUEUE_SIZE - 1 ) { + /* add it to the queue */ + self->packets[self->nPackets] = packet; + self->lens[self->nPackets] = len; + ++self->nPackets; + XP_LOGF( "there are now %d packets on send queue", self->nPackets ); + + /* signal the writer thread */ + DH(SetEvent)( self->queueAddEvent ); + success = XP_TRUE; + } + + if ( !ReleaseMutex( self->queueMutex ) ) { + logLastError( "ReleaseMutex" ); + } + } else { + XP_LOGF( "timed out" ); + } + + return success; +} + +static XP_Bool +get_packet( CeSocketWrapper* self, XP_U8** packet, XP_U16* len ) +{ + DWORD wres = WaitForSingleObject( self->queueMutex, INFINITE ); + XP_Bool success = wres == WAIT_OBJECT_0; + + if ( success ) { + success = self->nPackets > 0; + if ( success ) { + *packet = self->packets[0]; + *len = self->lens[0]; + } + if ( !ReleaseMutex( self->queueMutex ) ) { + logLastError( "ReleaseMutex" ); + } + } + + return success; +} /* get_packet */ + +static void +remove_packet( CeSocketWrapper* self ) +{ + DWORD wres = WaitForSingleObject( self->queueMutex, INFINITE ); + if ( wres == WAIT_OBJECT_0 ) { + XP_ASSERT( self->nPackets > 0 ); + if ( --self->nPackets > 0 ) { + XP_MEMCPY( &self->packets[0], &self->packets[1], + self->nPackets * sizeof(self->packets[0]) ); + XP_MEMCPY( &self->lens[0], &self->lens[1], + self->nPackets * sizeof(self->lens[0]) ); + } else { + XP_ASSERT( self->nPackets == 0 ); + } + if ( !ReleaseMutex( self->queueMutex ) ) { + logLastError( "ReleaseMutex" ); + } + } + XP_LOGF( "%d packets left on queue", self->nPackets ); +} /* remove_packet */ + +static XP_Bool +sendAll( CeSocketWrapper* self, XP_U8* buf, XP_U16 len ) +{ + for ( ; ; ) { + int nSent = MS(send)( self->socket, buf, len, 0 ); /* flags? */ + if ( nSent == SOCKET_ERROR ) { + return XP_FALSE; + } else if ( nSent == len ) { + XP_LOGF( "sent %d bytes", nSent ); + return XP_TRUE; + } else { + XP_LOGF( "sent %d bytes", nSent ); + XP_ASSERT( nSent < len ); + len -= nSent; + buf += nSent; + } + } +} /* sendAll */ + +static XP_Bool +sendLenAndData( CeSocketWrapper* self, XP_U8* packet, XP_U16 len ) +{ + XP_Bool success = XP_FALSE; + XP_U16 lenData; + XP_ASSERT( self->socket != -1 ); + + lenData = XP_HTONS( len ); + if ( sendAll( self, (XP_U8*)&lenData, sizeof(lenData) ) ) { + success = sendAll( self, packet, len ); + } + return success; +} /* sendLenAndData */ + +static XP_Bool +connectSocket( CeSocketWrapper* self ) +{ + SOCKET sock; + + /* first look up the ip address */ + if ( self->addrRec.u.ip_relay.ipAddr == 0 ) { + struct hostent* ent; + ent = MS(gethostbyname)( self->addrRec.u.ip_relay.hostName ); + if ( ent != NULL ) { + XP_U32 tmp; + XP_MEMCPY( &tmp, &ent->h_addr_list[0][0], + sizeof(self->addrRec.u.ip_relay.ipAddr) ); + self->addrRec.u.ip_relay.ipAddr = XP_NTOHL( tmp ); + } else { + logLastError( "gethostbyname" ); + } + } + + if ( self->addrRec.u.ip_relay.ipAddr != 0 ) { + sock = MS(socket)( AF_INET, SOCK_STREAM, IPPROTO_IP ); + XP_LOGF( "got socket %d", sock ); + + if ( sock != INVALID_SOCKET ) { + struct sockaddr_in name; + + name.sin_family = AF_INET; + name.sin_port = XP_HTONS( self->addrRec.u.ip_relay.port ); + name.sin_addr.S_un.S_addr = XP_HTONL(self->addrRec.u.ip_relay.ipAddr); + + if ( SOCKET_ERROR != MS(connect)( sock, (struct sockaddr *)&name, + sizeof(name) ) ) { + self->connState = CE_IP_CONNECTED; + self->socket = sock; + + /* Let the reader thread know there's now a socket to listen on */ + DH(SetEvent)( self->socketConnEvent ); + + } else { + logLastError( "connect" ); + } + } else { + logLastError( "socket" ); + } + } + + return self->connState == CE_IP_CONNECTED; +} /* connectSocket */ + +static XP_Bool +connectIfNot( CeSocketWrapper* self ) +{ + XP_Bool success = self->connState == CE_IP_CONNECTED; + + if ( !success ) { + success = connectSocket( self ); + } + return success; +} /* connectIfNot */ + +static void +closeConnection( CeSocketWrapper* self ) +{ + if ( self->connState >= CE_IP_UNCONNECTED ) { + + if ( self->socket != -1 ) { + MS(closesocket)( self->socket ); + } + + self->socket = -1; + self->connState = CE_IP_UNCONNECTED; + } +} /* closeConnection */ + +static DWORD +WriterThreadProc( LPVOID lpParameter ) +{ + CeSocketWrapper* self = (CeSocketWrapper*)lpParameter; + + connectSocket( self ); + + /* Then loop waiting for packets to write to it. */ + for ( ; ; ) { + XP_U8* packet; + XP_U16 len; + + WaitForSingleObject( self->queueAddEvent, INFINITE ); + + if ( get_packet( self, &packet, &len ) && connectIfNot( self ) ) { + if ( sendLenAndData( self, packet, len ) ) { + + /* successful send. Remove our copy */ + remove_packet( self ); + XP_FREE( self->mpool, packet ); + } + } + + /* Should this happen sooner? What if other thread signals in the + meantime? */ + DH(ResetEvent)( self->queueAddEvent ); + } + + ExitThread(0); /* docs say to exit this way */ + return 0; +} /* WriterThreadProc */ + +/* Read until we get the number of bytes sought or until an error's + received. */ +static XP_Bool +read_bytes_blocking( CeSocketWrapper* self, XP_U8* buf, XP_U16 len ) +{ + while ( len > 0 ) { + fd_set readSet; + int sres; + + FD_ZERO( &readSet ); + /* There also needs to be a pipe in here for interrupting */ + FD_SET( self->socket, &readSet ); + + sres = MS(select)( 0, /* nFds is ignored on wince */ + &readSet, NULL, NULL, /* others not interesting */ + NULL ); /* no timeout */ + XP_LOGF( "back from select: got %d", sres ); + if ( sres == 0 ) { + break; + } else if ( sres == 1 && FD_ISSET( self->socket, &readSet ) ) { + int nRead = MS(recv)( self->socket, buf, len, 0 ); + if ( nRead > 0 ) { + XP_LOGF( "read %d bytes", nRead ); + XP_ASSERT( nRead <= len ); + buf += nRead; + len -= nRead; + } else { + break; + } + } else { + XP_ASSERT(0); + break; + } + } + + /* We probably want to close the socket if something's wrong here. Once + we get out of sync somehow we'll never get the framing right again. */ + XP_ASSERT( len == 0 ); + return len == 0; +} /* read_bytes_blocking */ + +static DWORD +ReaderThreadProc( LPVOID lpParameter ) +{ + XP_U8 buf[MAX_MSG_LEN]; + CeSocketWrapper* self = (CeSocketWrapper*)lpParameter; + + for ( ; ; ) { + WaitForSingleObject( self->socketConnEvent, INFINITE ); + + for ( ; ; ) { + XP_U16 len; + XP_LOGF( "ReaderThreadProc running" ); + + /* This will block in select */ + if ( !read_bytes_blocking( self, (XP_U8*)&len, sizeof(len) ) ) { + break; /* bad socket. Go back to waiting new + one. */ + } + len = XP_NTOHS( len ); + if ( !read_bytes_blocking( self, buf, len ) ) { + break; /* bad socket */ + } + + (*self->dataProc)( buf, len, self->dataClosure ); + } + } + + ExitThread(0); /* docs say to exit this way */ + return 0; +} /* ReaderThreadProc */ + + +CeSocketWrapper* +ce_sockwrap_new( MPFORMAL CommsConnType conType, DataRecvProc proc, + void* closure ) +{ + CeSocketWrapper* self = XP_MALLOC( mpool, sizeof(*self) ); + XP_MEMSET( self, 0, sizeof(*self) ); + + self->dataProc = proc; + self->dataClosure = closure; + MPASSIGN(self->mpool, mpool ); + self->socket = -1; + + self->queueMutex = CreateMutex( NULL, FALSE, NULL ); + XP_ASSERT( self->queueMutex != NULL ); + + self->queueAddEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + self->socketConnEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + + /* I have no idea why these casts are necessary to prevent warnings. All + sigs look right to me. */ + self->threads[WRITER_THREAD] = + CreateThread( NULL, 0, + (LPTHREAD_START_ROUTINE)WriterThreadProc, + self, 0, NULL ); + self->threads[READER_THREAD] = + CreateThread( NULL, 0, + (LPTHREAD_START_ROUTINE)ReaderThreadProc, + self, 0, NULL ); + return self; +} /* ce_sockwrap_new */ + +void +ce_sockwrap_delete( CeSocketWrapper* self ) +{ + /* This isn't a good thing to do. Better to signal them to exit + some other way */ + TerminateThread( self->threads[WRITER_THREAD], 0 ); + TerminateThread( self->threads[READER_THREAD], 0 ); + + WaitForMultipleObjects( N_THREADS, self->threads, TRUE, INFINITE ); + + closeConnection( self ); + + CloseHandle( self->threads[WRITER_THREAD] ); + CloseHandle( self->threads[READER_THREAD] ); + CloseHandle( self->queueMutex ); + + CloseHandle( self->queueAddEvent ); + CloseHandle( self->socketConnEvent ); + + XP_FREE( self->mpool, self ); +} /* ce_sockwrap_delete */ + +XP_U16 +ce_sockwrap_send( CeSocketWrapper* self, const XP_U8* buf, XP_U16 len, + const CommsAddrRec* addr ) +{ + XP_U8* packet; + + /* If the address has changed, we need to close the connection. Send + thread will take care of opening it again. */ + XP_ASSERT( addr->conType == COMMS_CONN_RELAY ); + if ( 0 != XP_STRCMP( addr->u.ip_relay.hostName, self->addrRec.u.ip_relay.hostName ) + || 0 != XP_STRCMP( addr->u.ip_relay.cookie, self->addrRec.u.ip_relay.cookie ) + || addr->u.ip_relay.port != self->addrRec.u.ip_relay.port ) { + closeConnection( self ); + XP_MEMCPY( &self->addrRec, addr, sizeof(self->addrRec) ); + } + + packet = XP_MALLOC( self->mpool, len ); + XP_MEMCPY( packet, buf, len ); + if ( !queue_packet( self, packet, len ) ) { + len = 0; /* error */ + } + + return len; +} /* ce_sockwrap_send */ + +#endif diff --git a/xwords4/wince/cesockwr.h b/xwords4/wince/cesockwr.h new file mode 100755 index 000000000..068599589 --- /dev/null +++ b/xwords4/wince/cesockwr.h @@ -0,0 +1,36 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2005 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CESOCKWR_H_ +#define _CESOCKWR_H_ + +#include "comms.h" +#include "mempool.h" + +typedef struct CeSocketWrapper CeSocketWrapper; /* forward */ +typedef void (*DataRecvProc)( XP_U8* data, XP_U16 len, void* closure ); + + +CeSocketWrapper* ce_sockwrap_new( MPFORMAL CommsConnType conType, DataRecvProc proc, void* closure ); +void ce_sockwrap_delete( CeSocketWrapper* self ); + +XP_U16 ce_sockwrap_send( CeSocketWrapper* self, const XP_U8* buf, XP_U16 len, + const CommsAddrRec* addr ); + +#endif diff --git a/xwords4/wince/cestrbx.c b/xwords4/wince/cestrbx.c new file mode 100755 index 000000000..d53853a70 --- /dev/null +++ b/xwords4/wince/cestrbx.c @@ -0,0 +1,117 @@ +/* -*- fill-column: 77; c-basic-offset: 4; compile-command: "make TARGET_OS=wince DEBUG=TRUE" -*- */ +/* + * Copyright 2002-2006 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "cestrbx.h" +#include "cemain.h" +#include "ceutil.h" + +static void +stuffTextInField( HWND hDlg, StrBoxState* state ) +{ + XP_U16 nBytes = stream_getSize(state->stream); + XP_U16 len, crlen; + XP_UCHAR* sbuf; + wchar_t* wbuf; +#ifdef MEM_DEBUG + CEAppGlobals* globals = state->dlgHdr.globals; +#endif + + sbuf = XP_MALLOC( globals->mpool, nBytes + 1 ); + stream_getBytes( state->stream, sbuf, nBytes ); + + crlen = strlen(XP_CR); + if ( 0 == strncmp( XP_CR, &sbuf[nBytes-crlen], crlen ) ) { + nBytes -= crlen; + } + sbuf[nBytes] = '\0'; + + len = MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, sbuf, nBytes, + NULL, 0 ); + wbuf = XP_MALLOC( globals->mpool, (len+1) * sizeof(*wbuf) ); + MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, sbuf, nBytes, + wbuf, len ); + XP_FREE( globals->mpool, sbuf ); + wbuf[len] = 0; + + SetDlgItemText( hDlg, ID_EDITTEXT, wbuf ); + XP_FREE( globals->mpool, wbuf ); +} /* stuffTextInField */ + +LRESULT CALLBACK +StrBox(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + LRESULT handled = FALSE; + StrBoxState* state; + XP_U16 id; + + if ( message == WM_INITDIALOG ) { + SetWindowLongPtr( hDlg, GWL_USERDATA, (long)lParam ); + state = (StrBoxState*)lParam; + + if ( !!state->title ) { + SendMessage( hDlg, WM_SETTEXT, 0, (long)state->title ); + } + + if ( !state->isQuery ) { + ceShowOrHide( hDlg, IDCANCEL, XP_FALSE ); + /* also want to expand the text box to the bottom */ + /* ceIsLandscape() is going away.... */ +/* if ( !ceIsLandscape( state->dlgHdr.globals ) ) { */ +/* ceCenterCtl( hDlg, IDOK ); */ +/* } */ + } + + ceDlgSetup( &state->dlgHdr, hDlg, + state->isQuery? DLG_STATE_NONE : DLG_STATE_OKONLY ); + + handled = TRUE; + } else { + state = (StrBoxState*)GetWindowLongPtr( hDlg, GWL_USERDATA ); + + if ( !!state ) { + if ( ceDoDlgHandle( &state->dlgHdr, message, wParam, lParam) ) { + handled = TRUE; + } else { + switch (message) { + + case WM_COMMAND: + + /* If I add the text above in the WM_INITDIALOG section it + shows up selected though selStart and selEnd are 0. */ + if ( !state->textIsSet ) { + state->textIsSet = XP_TRUE; + stuffTextInField( hDlg, state ); + } + + id = LOWORD(wParam); + switch( id ) { + + case IDOK: + case IDCANCEL: + state->result = id; + EndDialog(hDlg, id); + handled = TRUE; + } + break; + } + } + } + } + return handled; +} /* StrBox */ diff --git a/xwords4/wince/cestrbx.h b/xwords4/wince/cestrbx.h new file mode 100755 index 000000000..74eae0a09 --- /dev/null +++ b/xwords4/wince/cestrbx.h @@ -0,0 +1,37 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2002 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "stdafx.h" + +#include "xwstream.h" +#include "cemain.h" +#include "ceutil.h" + +LRESULT CALLBACK StrBox(HWND hDlg, UINT message, WPARAM wParam, + LPARAM lParam); + + +typedef struct StrBoxState { + CeDlgHdr dlgHdr; + wchar_t* title; + XWStreamCtxt* stream; + XP_U16 result; + XP_Bool isQuery; + XP_Bool textIsSet; +} StrBoxState; diff --git a/xwords4/wince/cesvdgms.c b/xwords4/wince/cesvdgms.c new file mode 100644 index 000000000..0d6e655cd --- /dev/null +++ b/xwords4/wince/cesvdgms.c @@ -0,0 +1,512 @@ +/* -*- fill-column: 77; compile-command: "make TARGET_OS=wince DEBUG=TRUE" -*- */ +/* + * Copyright 2004-2008 by Eric House (xwords@eehouse.org). All rights + * reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include "stdafx.h" +#include +#include +#ifdef _win32_wce +#include +#endif + +#include "cemain.h" +#include "cesvdgms.h" +#include "ceutil.h" +#include "cedebug.h" +#include "debhacks.h" + +typedef struct CeSaveGameNameState { + CeDlgHdr dlgHdr; + wchar_t* buf; + XP_U16 buflen; + XP_U16 lableTextId; + XP_Bool cancelled; + XP_Bool inited; +} CeSaveGameNameState; + +static XP_Bool +ceFileExists( CEAppGlobals* globals, const wchar_t* name ) +{ + wchar_t buf[CE_MAX_PATH_LEN]; + DWORD attributes; + XP_U16 len; + + len = ceGetPath( globals, DEFAULT_DIR_PATH_L, buf, VSIZE(buf) ); + swprintf( &buf[len], L"%s.xwg", name ); + + attributes = GetFileAttributes( buf ); + return attributes != 0xFFFFFFFF; +} + +static void +makeUniqueName( CEAppGlobals* globals, wchar_t* buf, + XP_U16 XP_UNUSED_DBG(bufLen) ) +{ + XP_U16 ii; + for ( ii = 1; ii < 100; ++ii ) { +#ifdef DEBUG + int len = +#endif + swprintf( buf, L"Untitled%d", ii ); + XP_ASSERT( len < bufLen ); + if ( !ceFileExists( globals, buf ) ) { + break; + } + } + /* If we fall out of the loop, the user will be asked to confirm delete + of Untitled99 or somesuch. That's ok.... */ +} /* makeUniqueName */ + +static LRESULT CALLBACK +SaveNameDlg( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam ) +{ + CeSaveGameNameState* state; + XP_U16 wid; + BOOL result = FALSE; + + if ( message == WM_INITDIALOG ) { + SetWindowLongPtr( hDlg, GWL_USERDATA, lParam ); + + state = (CeSaveGameNameState*)lParam; + state->cancelled = XP_TRUE; + state->inited = XP_FALSE; + + wchar_t buf[128]; + LoadString( state->dlgHdr.globals->hInst, state->lableTextId, + buf, VSIZE(buf) ); + (void)SetDlgItemText( hDlg, IDC_SVGN_SELLAB, buf ); + + ceDlgSetup( &state->dlgHdr, hDlg, DLG_STATE_TRAPBACK ); + + result = TRUE; + } else { + state = (CeSaveGameNameState*)GetWindowLongPtr( hDlg, GWL_USERDATA ); + if ( !!state ) { + CEAppGlobals* globals = state->dlgHdr.globals; + if ( !state->inited ) { + state->inited = XP_TRUE; + (void)SetDlgItemText( hDlg, IDC_SVGN_EDIT, state->buf ); + } + + if ( ceDoDlgHandle( &state->dlgHdr, message, wParam, lParam) ) { + result = TRUE; + } else { + + switch (message) { + case WM_COMMAND: + wid = LOWORD(wParam); + switch( wid ) { + case IDOK: { + wchar_t buf[128]; + XP_U16 len; + (void)GetDlgItemText( hDlg, IDC_SVGN_EDIT, buf, + VSIZE(buf) ); + if ( ceFileExists( globals, buf ) ) { + wchar_t widebuf[128]; + snwprintf( widebuf, VSIZE(widebuf), + L"File \"%s\" already exists.", buf ); + result = MessageBox( hDlg, widebuf, L"Oops!", + MB_OK | MB_ICONHAND ); + (void)SetDlgItemText( hDlg, IDC_SVGN_EDIT, state->buf ); + break; + } + len = ceGetPath( globals, DEFAULT_DIR_PATH_L, + state->buf, state->buflen ); + swprintf( &state->buf[len], L"%s.xwg", buf ); + XP_LOGW( __func__, state->buf ); + /* fallthru */ + state->cancelled = XP_FALSE; + } + case IDCANCEL: + EndDialog(hDlg, wid); + result = TRUE; + break; + } + } + } + } + } + return result; +} /* SaveNameDlg */ + +XP_Bool +ceConfirmUniqueName( CEAppGlobals* globals, HWND hWnd, XP_U16 strId, + wchar_t* buf, XP_U16 buflen ) +{ + CeSaveGameNameState state; + + LOG_FUNC(); + + makeUniqueName( globals, buf, buflen ); + + XP_MEMSET( &state, 0, sizeof(state) ); + state.dlgHdr.globals = globals; + state.buf = buf; + state.buflen = buflen; + state.lableTextId = strId; + (void)DialogBoxParam( globals->hInst, (LPCTSTR)IDD_SAVENAMEDLG, + hWnd, (DLGPROC)SaveNameDlg, (long)&state ); + XP_LOGW( __func__, buf ); + return !state.cancelled; +} /* ceConfirmUniqueName */ + +typedef struct CeSavedGamesState { + CeDlgHdr dlgHdr; + wchar_t* buf; + XP_U16 buflen; + XP_S16 sel; /* index of game name currently selected */ + XP_U16 openGameIndex; /* index of game that's open */ + wchar_t openNameW[128]; + wchar_t newNameW[MAX_PATH]; + XP_U16 nItems; + + XP_U16 gameListId; + XP_Bool inited; + XP_Bool relaunch; + SavedGamesResult result; +} CeSavedGamesState; + +static void +ceBasename( wchar_t* buf, const wchar_t* path ) +{ + const wchar_t* ptr = path + wcslen(path); + const wchar_t* dot = NULL; + + for ( ; ; ) { + if ( ptr == path ) { + break; + } else if ( *ptr == L'\\' ) { + ++ptr; + break; + } else if ( !dot && *ptr == L'.' ) { + dot = ptr; + } + --ptr; + } + lstrcpy( buf, ptr ); + if ( !!dot ) { + buf[dot-ptr] = 0; /* nuke extension */ + } +} /* ceBasename */ + +/* Probably belongs as a utility */ +static void +getComboText( CeSavedGamesState* state, wchar_t* buf, XP_U16* lenp ) +{ + HWND hDlg = state->dlgHdr.hDlg; + CEAppGlobals* globals = state->dlgHdr.globals; + XP_U16 id = state->gameListId; + XP_U16 sel = state->sel; + XP_U16 len; + + len = SendDlgItemMessage( hDlg, id, GETLBTEXTLEN(globals), sel, 0L ); + + if ( len < *lenp ) { + (void)SendDlgItemMessage( hDlg, id, GETLBTEXT(globals), sel, + (LPARAM)buf ); + } else { + XP_ASSERT( 0 ); + } + *lenp = len; +} /* getComboText */ + +static void +getFullSelPath( CeSavedGamesState* state, wchar_t* buf, XP_U16 buflen ) +{ + XP_U16 len = ceGetPath( state->dlgHdr.globals, + DEFAULT_DIR_PATH_L, buf, buflen ); + buflen -= len; + getComboText( state, &buf[len], &buflen ); + lstrcat( buf, L".xwg" ); +} + +static void +setButtons( CeSavedGamesState* state ) +{ + XP_Bool curSelected = state->openGameIndex == state->sel; + XP_Bool haveItem = state->nItems > 0; + HWND hDlg = state->dlgHdr.hDlg; + + ceEnOrDisable( hDlg, IDC_SVGM_OPEN, haveItem && !curSelected ); + ceEnOrDisable( hDlg, IDC_SVGM_DUP, haveItem ); + ceEnOrDisable( hDlg, IDC_SVGM_DEL, haveItem && !curSelected ); + ceEnOrDisable( hDlg, IDC_SVGM_CHANGE, haveItem ); +} + +static void +initSavedGamesData( CeSavedGamesState* state ) +{ + HANDLE fileH; + HWND hDlg = state->dlgHdr.hDlg; + CEAppGlobals* globals = state->dlgHdr.globals; + WIN32_FIND_DATA data; + wchar_t path[CE_MAX_PATH_LEN]; + XP_S16 sel; + XP_U16 ii; + XP_U16 nItems = 0; + + XP_MEMSET( &data, 0, sizeof(data) ); + ceGetPath( globals, DEFAULT_DIR_PATH_L, path, VSIZE(path) ); + lstrcat( path, L"*.xwg" ); + + fileH = FindFirstFile( path, &data ); + for ( ii = 0; fileH != INVALID_HANDLE_VALUE; ++ii ) { + XP_U16 len = wcslen( data.cFileName ); + + XP_ASSERT( data.cFileName[len-4] == L'.'); + data.cFileName[len-4] = 0; + + (void)SendDlgItemMessage( hDlg, state->gameListId, + ADDSTRING(globals), + 0, (LPARAM)data.cFileName ); + ++nItems; + + if ( !FindNextFile( fileH, &data ) ) { + XP_ASSERT( GetLastError() == ERROR_NO_MORE_FILES ); + break; + } + } + state->nItems = nItems; + + /* Now locate the open game and game we should select (which may + differ) */ + sel = SendDlgItemMessage( hDlg, state->gameListId, FINDSTRINGEXACT(globals), + -1, (LPARAM)state->openNameW ); + XP_ASSERT( sel >= 0 ); /* should always have this */ + state->openGameIndex = sel; + + sel = SendDlgItemMessage( hDlg,state->gameListId, FINDSTRINGEXACT(globals), + -1, (LPARAM)state->newNameW ); + if ( sel < 0 ) { + sel = state->openGameIndex; + } + + SendDlgItemMessage( hDlg, state->gameListId, SETCURSEL(globals), sel, 0 ); + state->sel = sel; + + setButtons( state ); +} /* initSavedGamesData */ + +static XP_Bool +renameSelected( CeSavedGamesState* state ) +{ + wchar_t newPath[MAX_PATH]; + XP_Bool confirmed = ceConfirmUniqueName( state->dlgHdr.globals, state->dlgHdr.hDlg, + IDS_RENAME, newPath, VSIZE(newPath) ); + if ( confirmed ) { + /* If we're renaming the current game, we have to exit and let + calling code handle it. If we're renaming any other game, we can + do it here. */ + if ( state->openGameIndex == state->sel ) { + swprintf( state->buf, L"%s", newPath ); + state->result = CE_SVGAME_RENAME; + } else { + wchar_t curPath[MAX_PATH]; + getFullSelPath( state, curPath, VSIZE(curPath) ); + confirmed = MoveFile( curPath, newPath ); + } + } + + if ( confirmed ) { + ceBasename( state->newNameW, newPath ); + } else { + state->newNameW[0] = 0; + } + return confirmed; +} /* renameSelected */ + +static XP_Bool +duplicateSelected( CeSavedGamesState* state ) +{ + wchar_t newPath[MAX_PATH]; + XP_Bool confirmed; + + confirmed = ceConfirmUniqueName( state->dlgHdr.globals, state->dlgHdr.hDlg, + IDS_DUPENAME, newPath, VSIZE(newPath) ); + if ( confirmed ) { + wchar_t curPath[MAX_PATH]; + getFullSelPath( state, curPath, VSIZE(curPath) ); + confirmed = CopyFile( curPath, newPath, TRUE ); /* TRUE is what??? */ + } + + if ( confirmed ) { + ceBasename( state->newNameW, newPath ); + } else { + state->newNameW[0] = 0; + } + + return confirmed; +} /* duplicateSelected */ + +static XP_Bool +deleteSelected( CeSavedGamesState* state ) +{ + /* confirm first!!!! */ + XP_Bool confirmed = queryBoxChar( state->dlgHdr.hDlg, + "Are you certain you want to delete the " + "selected game? This action cannot be " + "undone."); + if ( confirmed ) { + wchar_t pathW[CE_MAX_PATH_LEN]; + XP_U16 len = ceGetPath( state->dlgHdr.globals, + DEFAULT_DIR_PATH_L, pathW, VSIZE(pathW) ); + XP_U16 remLen = VSIZE(pathW) - len; + getComboText( state, &pathW[len], &remLen ); + wcscat( pathW, L".xwg" ); + confirmed = DeleteFile( pathW ); + if ( confirmed ) { + state->sel = -1; + } + } + return confirmed; +} /* deleteSelected */ + +static XP_Bool +tryGameChanged( CeSavedGamesState* state ) +{ + XP_S16 sel = SendDlgItemMessage( state->dlgHdr.hDlg, state->gameListId, + GETCURSEL(state->dlgHdr.globals), 0, 0L); + XP_Bool changing = sel >= 0 && state->sel != sel; + if ( changing ) { + state->sel = sel; + setButtons( state ); + } + return changing; +} /* tryGameChanged */ + +static LRESULT CALLBACK +SavedGamesDlg( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam ) +{ + CeSavedGamesState* state; + BOOL result = FALSE; + + if ( message == WM_INITDIALOG ) { + SetWindowLongPtr( hDlg, GWL_USERDATA, lParam ); + + state = (CeSavedGamesState*)lParam; + state->inited = XP_FALSE; + state->gameListId = LB_IF_PPC(state->dlgHdr.globals,IDC_SVGM_GAMELIST); + + ceDlgSetup( &state->dlgHdr, hDlg, DLG_STATE_DONEONLY ); + ceDlgComboShowHide( &state->dlgHdr, IDC_SVGM_GAMELIST ); + + result = TRUE; + } else { + state = (CeSavedGamesState*)GetWindowLongPtr( hDlg, GWL_USERDATA ); + if ( !!state ) { + + if ( !state->inited ) { + state->inited = XP_TRUE; + initSavedGamesData( state ); + } + + if ( ceDoDlgHandle( &state->dlgHdr, message, wParam, lParam) ) { + result = TRUE; + } else if ( WM_NOTIFY == message ) { + result = tryGameChanged( state ); + } else if ( message == WM_COMMAND ) { + XP_U16 wid = LOWORD(wParam); + + if ( CBN_SELCHANGE == HIWORD(wParam) ) { + if (state->gameListId == wid ) { + result = tryGameChanged( state ); + } + } else if ( BN_CLICKED == HIWORD(wParam) ) { + switch( wid ) { + case IDC_SVGM_DUP: + state->relaunch = duplicateSelected( state ); + break; + case IDC_SVGM_CHANGE: + state->relaunch = renameSelected( state ); + break; + case IDC_SVGM_DEL: + state->relaunch = deleteSelected( state ); + break; + + case IDC_SVGM_OPEN: { + wchar_t buf[128]; + XP_U16 len = VSIZE(buf); + getComboText( state, buf, &len ); + len = ceGetPath( state->dlgHdr.globals, + DEFAULT_DIR_PATH_L, state->buf, + state->buflen ); + swprintf( &state->buf[len], L"%s.xwg", buf ); + XP_LOGW( "returning", state->buf ); + state->result = CE_SVGAME_OPEN; + } + /* fallthrough */ + case IDOK: /* Done button */ + EndDialog(hDlg, wid); + result = TRUE; + break; + } + + if ( state->relaunch ) { + EndDialog( hDlg, wid ); + result = TRUE; + } + } + } + } + } + + return result; +} /* SavedGamesDlg */ + +SavedGamesResult +ceSavedGamesDlg( CEAppGlobals* globals, const XP_UCHAR* curPath, + wchar_t* buf, XP_U16 buflen ) +{ + CeSavedGamesState state; + + LOG_FUNC(); + + XP_MEMSET( &state, 0, sizeof(state) ); /* sets cancelled */ + state.dlgHdr.globals = globals; + state.buf = buf; + state.buflen = buflen; + state.sel = -1; + + if ( !!curPath ) { + wchar_t widebuf[MAX_PATH]; + XP_U16 len; + len = (XP_U16)XP_STRLEN( curPath ); + MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, curPath, len + 1, + widebuf, len + 1 ); + ceBasename( state.openNameW, widebuf ); + } + + for ( ; ; ) { + state.relaunch = XP_FALSE; + state.result = CE_SVGAME_CANCEL; + + assertOnTop( globals->hWnd ); + (void)DialogBoxParam( globals->hInst, (LPCTSTR)IDD_SAVEDGAMESDLG, + globals->hWnd, + (DLGPROC)SavedGamesDlg, (long)&state ); + + if ( !state.relaunch || (state.result == CE_SVGAME_RENAME) ) { + break; + } + } + XP_LOGW( __func__, buf ); + + return state.result; +} /* ceSavedGamesDlg */ diff --git a/xwords4/wince/cesvdgms.h b/xwords4/wince/cesvdgms.h new file mode 100644 index 000000000..be3685610 --- /dev/null +++ b/xwords4/wince/cesvdgms.h @@ -0,0 +1,37 @@ +/* -*- fill-column: 77; c-basic-offset: 4; compile-command: "make TARGET_OS=wince DEBUG=TRUE" -*- */ +/* + * Copyright 2008 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CESVDGMS_H_ +#define _CESVDGMS_H_ + +#include "xptypes.h" +#include "cemain.h" + +typedef enum { + CE_SVGAME_CANCEL + ,CE_SVGAME_RENAME + ,CE_SVGAME_OPEN +} SavedGamesResult; + +SavedGamesResult ceSavedGamesDlg( CEAppGlobals* globals, + const XP_UCHAR* curPath, + wchar_t* buf, XP_U16 buflen ); +XP_Bool ceConfirmUniqueName( CEAppGlobals* globals, HWND hWnd, XP_U16 strId, + wchar_t* buf, XP_U16 buflen ); +#endif diff --git a/xwords4/wince/ceutil.c b/xwords4/wince/ceutil.c new file mode 100755 index 000000000..26ca397a3 --- /dev/null +++ b/xwords4/wince/ceutil.c @@ -0,0 +1,907 @@ +/* -*- fill-column: 77; c-basic-offset: 4; compile-command: "make TARGET_OS=wince DEBUG=TRUE" -*- */ +/* + * Copyright 2002-2008 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "stdafx.h" +#include +#include + +#include "ceutil.h" +#include "cedefines.h" +#include "cedebug.h" +#include "debhacks.h" + +#define BUF_SIZE 128 +#define VPADDING 4 +#define HPADDING_L 2 +#define HPADDING_R 3 + +static XP_Bool ceDoDlgScroll( CeDlgHdr* dlgHdr, WPARAM wParam ); +static void ceDoDlgFocusScroll( CeDlgHdr* dlgHdr, HWND nextCtrl ); + +void +ceSetDlgItemText( HWND hDlg, XP_U16 id, const XP_UCHAR* buf ) +{ + wchar_t widebuf[BUF_SIZE]; + XP_U16 len; + + XP_ASSERT( buf != NULL ); + + len = (XP_U16)XP_STRLEN( buf ); + + if ( len >= BUF_SIZE ) { + len = BUF_SIZE - 1; + } + + MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, buf, len, widebuf, len ); + widebuf[len] = 0; + SendDlgItemMessage( hDlg, id, WM_SETTEXT, 0, (long)widebuf ); +} /* ceSetDlgItemText */ + +void +ceSetDlgItemFileName( HWND hDlg, XP_U16 id, XP_UCHAR* str ) +{ + XP_UCHAR* stripstart; + XP_UCHAR buf[BUF_SIZE]; + XP_U16 len = XP_STRLEN(str); + + if ( len >= BUF_SIZE ) { + len = BUF_SIZE - 1; + } + + XP_MEMCPY( buf, str, len ); + buf[len] = '\0'; + + stripstart = strrchr( (const char*)buf, '.' ); + if ( !!stripstart ) { + *stripstart = '\0'; + } + + ceSetDlgItemText( hDlg, id, buf ); +} /* ceSetDlgItemFileName */ + +void +ceGetDlgItemText( HWND hDlg, XP_U16 id, XP_UCHAR* buf, XP_U16* bLen ) +{ + XP_U16 len = *bLen; + XP_U16 gotLen; + wchar_t wbuf[BUF_SIZE]; + + XP_ASSERT( len <= BUF_SIZE ); + + gotLen = (XP_U16)SendDlgItemMessage( hDlg, id, WM_GETTEXT, len, + (long)wbuf ); + if ( gotLen > 0 ) { + XP_ASSERT( gotLen < len ); + if ( gotLen >= len ) { + gotLen = len - 1; + } + gotLen = WideCharToMultiByte( CP_ACP, 0, wbuf, gotLen, + buf, len, NULL, NULL ); + *bLen = gotLen; + buf[gotLen] = '\0'; + } else { + buf[0] = '\0'; + *bLen = 0; + } +} /* ceGetDlgItemText */ + +void +ceSetDlgItemNum( HWND hDlg, XP_U16 id, XP_S32 num ) +{ + XP_UCHAR buf[20]; + XP_SNPRINTF( buf, sizeof(buf), "%ld", num ); + ceSetDlgItemText( hDlg, id, buf ); +} /* ceSetDlgItemNum */ + +XP_S32 +ceGetDlgItemNum( HWND hDlg, XP_U16 id ) +{ + XP_S32 result = 0; + XP_UCHAR buf[24]; + XP_U16 len = sizeof(buf); + ceGetDlgItemText( hDlg, id, buf, &len ); + + result = atoi( buf ); + return result; +} /* ceGetDlgItemNum */ + +void +ceShowOrHide( HWND hDlg, XP_U16 resID, XP_Bool visible ) +{ + HWND itemH = GetDlgItem( hDlg, resID ); + if ( !!itemH ) { + ShowWindow( itemH, visible? SW_SHOW: SW_HIDE ); + } +} /* ceShowOrHide */ + +void +ceEnOrDisable( HWND hDlg, XP_U16 resID, XP_Bool enable ) +{ + HWND itemH = GetDlgItem( hDlg, resID ); + if ( !!itemH ) { + EnableWindow( itemH, enable ); + } +} /* ceShowOrHide */ + +void +ceSetChecked( HWND hDlg, XP_U16 resID, XP_Bool check ) +{ + SendDlgItemMessage( hDlg, resID, BM_SETCHECK, + check? BST_CHECKED:BST_UNCHECKED, 0L ); +} /* ceSetBoolCheck */ + +XP_Bool +ceGetChecked( HWND hDlg, XP_U16 resID ) +{ + XP_U16 checked; + checked = (XP_U16)SendDlgItemMessage( hDlg, resID, BM_GETCHECK, 0, 0L ); + return checked == BST_CHECKED; +} /* ceGetChecked */ + +/* Return dlg-relative rect. + */ +void +ceGetItemRect( HWND hDlg, XP_U16 resID, RECT* rect ) +{ + HWND itemH; + POINT pt = { .x = 0, .y = 0 }; + ScreenToClient( hDlg, &pt ); + + itemH = GetDlgItem( hDlg, resID ); + GetWindowRect( itemH, rect ); + (void)OffsetRect( rect, pt.x, pt.y ); +} /* ceGetItemRect */ + +void +ceMoveItem( HWND hDlg, XP_U16 resID, XP_S16 byX, XP_S16 byY ) +{ + RECT rect; + HWND itemH = GetDlgItem( hDlg, resID ); + ceGetItemRect( hDlg, resID, &rect ); + if ( !MoveWindow( itemH, rect.left + byX, rect.top + byY, + rect.right - rect.left, rect.bottom - rect.top, + TRUE ) ) { + XP_LOGF( "MoveWindow=>%ld", GetLastError() ); + } +} /* ceMoveItem */ + +#if 0 +/* This has not been tested with ceMoveItem... */ +void +ceCenterCtl( HWND hDlg, XP_U16 resID ) +{ + RECT buttonR, dlgR; + XP_U16 buttonWidth; + XP_S16 byX; + + GetClientRect( hDlg, &dlgR ); + XP_ASSERT( dlgR.left == 0 && dlgR.top == 0 ); + + ceGetItemRect( hDlg, resID, &buttonR ); + + buttonWidth = buttonR.right - buttonR.left; + byX = buttonR.left - ((dlgR.right - buttonWidth) / 2); + + ceMoveItem( hDlg, resID, byX, 0 ); +} /* ceCenterCtl */ +#endif + +/* XP_Bool */ +/* ceIsLandscape( CEAppGlobals* globals ) */ +/* { */ +/* XP_U16 width, height; */ +/* XP_Bool landscape; */ + +/* XP_ASSERT( !!globals ); */ +/* XP_ASSERT( !!globals->hWnd ); */ + +/* if ( 0 ) { */ +/* #if defined DEBUG && !defined _WIN32_WCE */ +/* } else if ( globals->dbWidth != 0 ) { */ +/* width = globals->dbWidth; */ +/* height = globals->dbHeight; */ +/* #endif */ +/* } else { */ +/* RECT rect; */ +/* GetClientRect( globals->hWnd, &rect ); */ +/* width = (XP_U16)(rect.right - rect.left); */ +/* height = (XP_U16)(rect.bottom - rect.top); */ +/* } */ + +/* landscape = (height - CE_SCORE_HEIGHT) */ +/* < (width - CE_MIN_SCORE_WIDTH); */ +/* return landscape; */ +/* } /\* ceIsLandscape *\/ */ + +#ifdef _WIN32_WCE +static XP_Bool +ceIsFullScreen( CEAppGlobals* globals, HWND hWnd ) +{ + XP_S16 screenHt; + XP_U16 winHt; + RECT rect; + + GetClientRect( hWnd, &rect ); + winHt = rect.bottom - rect.top; /* top should always be 0 */ + + screenHt = GetSystemMetrics( SM_CYSCREEN ); + XP_ASSERT( screenHt >= winHt ); + + screenHt -= winHt; + + if ( !!globals->hwndCB ) { + RECT rect; + GetWindowRect( globals->hwndCB, &rect ); + screenHt -= rect.bottom - rect.top; + } + + XP_ASSERT( screenHt >= 0 ); + return screenHt == 0; +} /* ceIsFullScreen */ + +static void +ceSize( CEAppGlobals* globals, HWND hWnd, XP_Bool fullScreen ) +{ + RECT rect; + XP_U16 cbHeight = 0; + if ( !!globals->hwndCB ) { + GetWindowRect( globals->hwndCB, &rect ); + cbHeight = rect.bottom - rect.top; + } + + /* I'm leaving the SIP/cmdbar in place until I can figure out how to + get menu events with it hidden -- and also the UI for making sure + users don't get stuck in fullscreen mode not knowing how to reach + menus to get out. Later, add SHFS_SHOWSIPBUTTON and + SHFS_HIDESIPBUTTON to the sets shown and hidden below.*/ + if ( fullScreen ) { + SHFullScreen( hWnd, SHFS_HIDETASKBAR | SHFS_HIDESTARTICON ); + + SetRect( &rect, 0, 0, GetSystemMetrics(SM_CXSCREEN), + GetSystemMetrics(SM_CYSCREEN) ); + + } else { + SHFullScreen( hWnd, SHFS_SHOWTASKBAR | SHFS_SHOWSTARTICON ); + SystemParametersInfo( SPI_GETWORKAREA, 0, &rect, FALSE ); + if ( IS_SMARTPHONE(globals) ) { + cbHeight = 0; + } + } + + rect.bottom -= cbHeight; + MoveWindow( hWnd, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE ); +} /* ceSize */ + +void +ceSizeIfFullscreen( CEAppGlobals* globals, HWND hWnd ) +{ + if ( globals->appPrefs.fullScreen != ceIsFullScreen(globals, hWnd) ) { + ceSize( globals, hWnd, globals->appPrefs.fullScreen ); + } +} + +static XP_Bool +mkFullscreenWithSoftkeys( CEAppGlobals* globals, HWND hDlg, XP_U16 curHt, + DlgStateTask doWhat ) +{ + XP_Bool success = XP_FALSE; + + SHMENUBARINFO mbi; + XP_MEMSET( &mbi, 0, sizeof(mbi) ); + mbi.cbSize = sizeof(mbi); + mbi.hwndParent = hDlg; + if ( 0 != (doWhat & DLG_STATE_DONEONLY) ) { + mbi.nToolBarId = IDM_DONE_MENUBAR; + } else if ( 0 != (doWhat & DLG_STATE_OKONLY) ) { + mbi.nToolBarId = IDM_OK_MENUBAR; + } else { + mbi.nToolBarId = IDM_OKCANCEL_MENUBAR; + } + mbi.hInstRes = globals->hInst; + success = SHCreateMenuBar( &mbi ); + if ( !success ) { + XP_LOGF( "SHCreateMenuBar failed: %ld", GetLastError() ); + } else { + + if ( IS_SMARTPHONE(globals) ) { + SHINITDLGINFO info; + XP_MEMSET( &info, 0, sizeof(info) ); + info.dwMask = SHIDIM_FLAGS; + info.dwFlags = SHIDIF_SIZEDLGFULLSCREEN; + info.hDlg = hDlg; + success = SHInitDialog( &info ); + if ( !success ) { + XP_LOGF( "SHInitDialog failed: %ld", GetLastError() ); + } + } else { + XP_U16 screenHt = GetSystemMetrics(SM_CYFULLSCREEN); + RECT rect; + GetWindowRect( mbi.hwndMB, &rect ); + screenHt -= (rect.bottom - rect.top); + if ( screenHt < curHt ) { + ceSize( globals, hDlg, XP_TRUE ); + } + } + } + + return success; +} /* mkFullscreenWithSoftkeys */ +#endif + +#define TITLE_HT 20 /* Need to get this from the OS */ +void +ceDlgSetup( CeDlgHdr* dlgHdr, HWND hDlg, DlgStateTask doWhat ) +{ + RECT rect; + XP_U16 fullHeight; + CEAppGlobals* globals = dlgHdr->globals; + + dlgHdr->hDlg = hDlg; + + XP_ASSERT( !!globals ); + XP_ASSERT( !!hDlg ); + /* at most one of these two should be set */ + XP_ASSERT( (doWhat & (DLG_STATE_OKONLY|DLG_STATE_DONEONLY)) + != (DLG_STATE_OKONLY|DLG_STATE_DONEONLY)); + + GetClientRect( hDlg, &rect ); + XP_ASSERT( rect.top == 0 ); + fullHeight = rect.bottom; /* This is before we've resized it */ + +#ifdef _WIN32_WCE + (void)mkFullscreenWithSoftkeys( globals, hDlg, fullHeight, doWhat); +#elif defined DEBUG + /* Force it to be small so we can test scrolling etc. */ + if ( globals->dbWidth > 0 && globals->dbHeight > 0) { + MoveWindow( hDlg, 0, 0, globals->dbWidth, globals->dbHeight, TRUE ); + rect.bottom = globals->dbHeight; + } +#endif + + /* Measure again post-resize */ + GetClientRect( hDlg, &rect ); + + /* Set up the scrollbar if we're on PPC */ + if ( !IS_SMARTPHONE(globals) ) { + SCROLLINFO sinfo; + + XP_MEMSET( &sinfo, 0, sizeof(sinfo) ); + sinfo.cbSize = sizeof(sinfo); + + sinfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE; + if ( rect.bottom < fullHeight ) { + sinfo.nMax = fullHeight; + dlgHdr->nPage = sinfo.nPage = rect.bottom - 1; + } + + (void)SetScrollInfo( hDlg, SB_VERT, &sinfo, FALSE ); + } + dlgHdr->doWhat = doWhat; + +#ifdef _WIN32_WCE + /* Need to trap this for all dialogs, even if they don't have edit + controls. The need goes away if the main window stops trapping it, + but I don't understand why: trapping here is still required. */ + if ( IS_SMARTPHONE(globals) ) { + trapBackspaceKey( hDlg ); + } +#endif +} /* ceDlgSetup */ + +void +ceDlgComboShowHide( CeDlgHdr* dlgHdr, XP_U16 baseId ) +{ + HWND hDlg = dlgHdr->hDlg; + + if ( IS_SMARTPHONE(dlgHdr->globals) ) { + ceShowOrHide( hDlg, baseId+2, XP_FALSE ); + } else { + ceShowOrHide( hDlg, baseId, XP_FALSE ); + ceShowOrHide( hDlg, baseId+1, XP_FALSE ); + } +} + +#ifdef OVERRIDE_BACKKEY +static XP_Bool +editHasFocus( void ) +{ + HWND focus = GetFocus(); + wchar_t buf[32]; + XP_Bool isEdit = !!focus + && ( 0 != GetClassName( focus, buf, VSIZE(buf) ) ) + && !wcscmp( L"Edit", buf ); + return isEdit; +} /* editHasFocus */ +#endif + +XP_Bool +ceDoDlgHandle( CeDlgHdr* dlgHdr, UINT message, WPARAM wParam, LPARAM lParam ) +{ + XP_Bool handled = XP_FALSE; + + switch( message ) { +#ifdef OVERRIDE_BACKKEY + case WM_HOTKEY: + if ( VK_TBACK == HIWORD(lParam) ) { + if ( editHasFocus() ) { + SHSendBackToFocusWindow( message, wParam, lParam ); + } else if ( 0 != (BACK_KEY_UP_MAYBE & LOWORD(lParam) ) ) { + WPARAM cmd = (0 != (dlgHdr->doWhat + & (DLG_STATE_DONEONLY|DLG_STATE_OKONLY))) ? + IDOK : IDCANCEL; + SendMessage( dlgHdr->hDlg, WM_COMMAND, cmd, 0L ); + } + handled = TRUE; + } + break; +#endif + case WM_VSCROLL: + handled = ceDoDlgScroll( dlgHdr, wParam ); + break; + + case WM_COMMAND: + if ( BN_SETFOCUS == HIWORD(wParam) ) { + ceDoDlgFocusScroll( dlgHdr, (HWND)lParam ); + handled = TRUE; + } else if ( BN_KILLFOCUS == HIWORD(wParam) ) { /* dialogs shouldn't have to handle this */ + handled = TRUE; + } + break; + } + return handled; +} + +static void +setScrollPos( HWND hDlg, XP_S16 newPos ) +{ + SCROLLINFO sinfo; + XP_S16 vertChange; + + XP_MEMSET( &sinfo, 0, sizeof(sinfo) ); + sinfo.cbSize = sizeof(sinfo); + sinfo.fMask = SIF_POS; + GetScrollInfo( hDlg, SB_VERT, &sinfo ); + + if ( sinfo.nPos != newPos ) { + XP_U16 oldPos = sinfo.nPos; + sinfo.nPos = newPos; + SetScrollInfo( hDlg, SB_VERT, &sinfo, XP_TRUE ); + + GetScrollInfo( hDlg, SB_VERT, &sinfo ); + vertChange = oldPos - sinfo.nPos; + if ( 0 != vertChange ) { + RECT updateR; + ScrollWindowEx( hDlg, 0, vertChange, NULL, NULL, NULL, + &updateR, SW_SCROLLCHILDREN|SW_ERASE); + InvalidateRect( hDlg, &updateR, TRUE ); + (void)UpdateWindow( hDlg ); + } else { + XP_LOGF( "%s: change dropped", __func__ ); + } + } +} /* setScrollPos */ + +static void +adjustScrollPos( HWND hDlg, XP_S16 vertChange ) +{ + if ( vertChange != 0 ) { + SCROLLINFO sinfo; + + XP_MEMSET( &sinfo, 0, sizeof(sinfo) ); + sinfo.cbSize = sizeof(sinfo); + sinfo.fMask = SIF_POS; + GetScrollInfo( hDlg, SB_VERT, &sinfo ); + + setScrollPos( hDlg, sinfo.nPos + vertChange ); + } + LOG_RETURN_VOID(); +} /* adjustScrollPos */ + +static XP_Bool +ceDoDlgScroll( CeDlgHdr* dlgHdr, WPARAM wParam ) +{ + XP_Bool handled = !IS_SMARTPHONE(dlgHdr->globals); + if ( handled ) { + XP_S16 vertChange = 0; + + switch ( LOWORD(wParam) ) { + + case SB_LINEUP: // Scrolls one line up + vertChange = -1; + break; + case SB_PAGEUP: // + vertChange = -dlgHdr->nPage; + break; + + case SB_LINEDOWN: // Scrolls one line down + vertChange = 1; + break; + case SB_PAGEDOWN: // Scrolls one page down + vertChange = dlgHdr->nPage; + break; + + case SB_THUMBTRACK: /* still dragging; don't redraw */ + case SB_THUMBPOSITION: + setScrollPos( dlgHdr->hDlg, HIWORD(wParam) ); + break; + } + + if ( 0 != vertChange ) { + adjustScrollPos( dlgHdr->hDlg, vertChange ); + } + } + return handled; +} /* ceDoDlgScroll */ + + +/* wParam */ +/* If lParam is TRUE, this parameter identifies the control that + receives the focus. If lParam is FALSE, this parameter indicates + whether the next or previous control with the WS_TABSTOP style + receives the focus. If wParam is zero, the next control receives + the focus; otherwise, the previous control with the WS_TABSTOP + style receives the focus. */ +/* lParam */ +/* The low-order word indicates how the system uses wParam. If the + low-order word is TRUE, wParam is a handle associated with the + control that receives the focus; otherwise, wParam is a flag that + indicates whether the next or previous control with the WS_TABSTOP + style receives the focus. */ + +static void +ceDoDlgFocusScroll( CeDlgHdr* dlgHdr, HWND nextCtrl ) +{ + /* Scroll the current focus owner into view. + * + * There's nothing passed in to tell us who it is, so look it up. + * + * What's in view? First, a window has a scroll position, nPos, that + * tells how many pixels are scrolled out of view above the window. Then + * a control has an offset within the containing rect (which shifts as + * it's scrolled.) Finally, all rects are relative to the screen, so we + * need to get the containing rect to figure out what the control's + * position is. + * + * The first question, which can be answered without reference to + * scrolling, is "Are we in view?" If we're not, then we need to look at + * scrolling to see how to fix it. + */ + + if ( !IS_SMARTPHONE(dlgHdr->globals) ) { + HWND hDlg = dlgHdr->hDlg; + + if ( !!nextCtrl ) { + RECT rect; + XP_U16 dlgHeight, ctrlHeight, dlgTop; + XP_S16 ctrlPos; + + GetClientRect( hDlg, &rect ); + dlgHeight = rect.bottom - rect.top; + XP_LOGF( "dlgHeight: %d", dlgHeight ); + + GetWindowRect( hDlg, &rect ); + dlgTop = rect.top; + + GetWindowRect( nextCtrl, &rect ); + ctrlPos = rect.top - dlgTop - TITLE_HT; + ctrlHeight = rect.bottom - rect.top; + + if ( ctrlPos < 0 ) { + XP_LOGF( "need to scroll it DOWN into view" ); + adjustScrollPos( hDlg, ctrlPos ); + } else if ( (ctrlPos + ctrlHeight) > dlgHeight ) { + XP_LOGF( "need to scroll it UP into view" ); + setScrollPos( hDlg, ctrlPos - ctrlHeight ); + } + } + } +} /* ceDoDlgFocusScroll */ + +static XP_Bool +ceFindMenu( HMENU menu, XP_U16 id, +#ifndef _WIN32_WCE + HMENU* foundMenu, XP_U16* foundPos, +#endif + wchar_t* foundBuf, XP_U16 bufLen ) +{ + XP_Bool found = XP_FALSE; + XP_U16 pos; + MENUITEMINFO minfo; + + XP_MEMSET( &minfo, 0, sizeof(minfo) ); + minfo.cbSize = sizeof(minfo); + + for ( pos = 0; !found; ++pos ) { + /* Set these each time through loop. GetMenuItemInfo can change + some of 'em. */ + minfo.fMask = MIIM_SUBMENU | MFT_STRING | MIIM_ID | MIIM_TYPE; + minfo.dwTypeData = foundBuf; + minfo.fType = MFT_STRING; + minfo.cch = bufLen; + + if ( !GetMenuItemInfo( menu, pos, TRUE, &minfo ) ) { + break; /* pos is too big */ + } else if ( NULL != minfo.hSubMenu ) { + found = ceFindMenu( minfo.hSubMenu, id, +#ifndef _WIN32_WCE + foundMenu, foundPos, +#endif + foundBuf, bufLen ); + } else if ( MFT_SEPARATOR == minfo.fType ) { + continue; + } else if ( minfo.wID == id ) { + found = XP_TRUE; +#ifndef _WIN32_WCE + *foundPos = pos; + *foundMenu = menu; +#endif + } + } + return found; +} /* ceFindMenu */ + +#ifndef _WIN32_WCE +static void +setW32DummyMenu( CEAppGlobals* globals, HMENU menu, XP_U16 id, wchar_t* oldNm ) +{ + XP_LOGW( __func__, oldNm ); + if ( globals->dummyMenu == NULL ) { + HMENU tmenu; + XP_U16 tpos; + wchar_t ignore[32]; + if ( ceFindMenu( menu, W32_DUMMY_ID, &tmenu, &tpos, ignore, + VSIZE(ignore) ) ) { + globals->dummyMenu = tmenu; + globals->dummyPos = tpos; + } + } + + if ( globals->dummyMenu != NULL ) { + MENUITEMINFO minfo; + XP_MEMSET( &minfo, 0, sizeof(minfo) ); + minfo.cbSize = sizeof(minfo); + minfo.fMask = MFT_STRING | MIIM_TYPE | MIIM_ID; + minfo.fType = MFT_STRING; + minfo.dwTypeData = oldNm; + minfo.cch = wcslen( oldNm ); + minfo.wID = id; + + if ( !SetMenuItemInfo( globals->dummyMenu, globals->dummyPos, + TRUE, &minfo ) ) { + XP_LOGF( "SetMenuItemInfo failed" ); + } + } +} +#endif + +static HMENU +ceGetMenu( const CEAppGlobals* globals ) +{ +#ifdef _WIN32_WCE + TBBUTTONINFO info; + XP_MEMSET( &info, 0, sizeof(info) ); + info.cbSize = sizeof(info); + info.dwMask = TBIF_LPARAM; + SendMessage( globals->hwndCB, TB_GETBUTTONINFO, IDM_MENU, + (LPARAM)&info ); + return (HMENU)info.lParam; +#else + return GetMenu( globals->hWnd ); +#endif +} + +void +ceSetLeftSoftkey( CEAppGlobals* globals, XP_U16 newId ) +{ + if ( newId != globals->softKeyId ) { + wchar_t menuTxt[32]; /* text of newId menu */ + HMENU menu = ceGetMenu( globals ); +#ifdef _WIN32_WCE + TBBUTTONINFO info; +#else + HMENU prevMenu; + XP_U16 prevPos; +#endif + XP_U16 oldId = globals->softKeyId; + if ( 0 == oldId ) { + oldId = ID_INITIAL_SOFTID; + } + + /* Look up the text... */ + if ( ceFindMenu( menu, newId, +#ifndef _WIN32_WCE + &prevMenu, &prevPos, +#endif + menuTxt, VSIZE(menuTxt) ) ) { + globals->softKeyId = newId; +#ifndef _WIN32_WCE + globals->softKeyMenu = prevMenu; +#endif + } else { + XP_LOGF( "%s: ceFindMenu failed", __func__ ); + } + + /* Make it the button */ +#ifdef _WIN32_WCE + XP_MEMSET( &info, 0, sizeof(info) ); + info.cbSize = sizeof(info); + info.dwMask = TBIF_TEXT | TBIF_COMMAND; + info.idCommand = newId; + info.pszText = menuTxt; + SendMessage( globals->hwndCB, TB_SETBUTTONINFO, oldId, (LPARAM)&info ); +#else + setW32DummyMenu( globals, menu, newId, menuTxt ); +#endif + } + ceCheckMenus( globals ); /* in case left key was or should be checked */ +} /* ceSetLeftSoftkey */ + +static void +checkOneItem( const CEAppGlobals* globals, XP_U16 id, XP_Bool check ) +{ + UINT uCheck = check ? MF_CHECKED : MF_UNCHECKED; + HMENU menu = ceGetMenu( globals ); + + (void)CheckMenuItem( menu, id, uCheck ); +#ifndef _WIN32_WCE + if ( id == globals->softKeyId ) { + (void)CheckMenuItem( globals->softKeyMenu, id, uCheck ); + } +#endif +} + +void +ceCheckMenus( const CEAppGlobals* globals ) +{ + const BoardCtxt* board = globals->game.board; + + checkOneItem( globals, ID_MOVE_VALUES, board_get_showValues( board )); + checkOneItem( globals, ID_MOVE_FLIP, board_get_flipped( board ) ); + checkOneItem( globals, ID_FILE_FULLSCREEN, globals->appPrefs.fullScreen ); + checkOneItem( globals, ID_MOVE_HIDETRAY, + TRAY_REVEALED != board_getTrayVisState( board ) ); +} /* ceCheckMenus */ + +#ifdef OVERRIDE_BACKKEY +void +trapBackspaceKey( HWND hDlg ) +{ + /* Override back key so we can pass it to edit controls */ + SendMessage( SHFindMenuBar(hDlg), SHCMBM_OVERRIDEKEY, VK_TBACK, + MAKELPARAM (SHMBOF_NODEFAULT | SHMBOF_NOTIFY, + SHMBOF_NODEFAULT | SHMBOF_NOTIFY)); + /* To undo the above + SendMessage( SHFindMenuBar(hDlg), SHCMBM_OVERRIDEKEY, VK_TBACK, + MAKELPARAM( SHMBOF_NODEFAULT | SHMBOF_NOTIFY, + 0 ) ); + */ +} +#endif + +/* Bugs in mingw32ce headers force defining _WIN32_IE, which causes + * SHGetSpecialFolderPath to be defined as SHGetSpecialFolderPathW which + * is not on Wince. Once I turn off _WIN32_IE this can go away. */ +#ifdef _WIN32_IE +# ifdef SHGetSpecialFolderPath +# undef SHGetSpecialFolderPath +# endif +BOOL SHGetSpecialFolderPath( HWND hwndOwner, + LPTSTR lpszPath, + int nFolder, + BOOL fCreate ); +#endif + +static void +lookupSpecialDir( wchar_t* bufW, XP_U16 indx ) +{ + bufW[0] = 0; +#ifdef _WIN32_WCE + SHGetSpecialFolderPath( NULL, bufW, + (indx == MY_DOCS_CACHE)? + CSIDL_PERSONAL : CSIDL_PROGRAM_FILES, + TRUE ); + if ( 0 == bufW[0] ) { + XP_WARNF( "SHGetSpecialFolderPath failed" ); + wcscpy( bufW, L"\\My Documents" ); + } +#else + wcscat( bufW, L"." ); +#endif + if ( indx == PROGFILES_CACHE ) { + wcscat( bufW, L"\\" LCROSSWORDS_DIR_NODBG ); + } else { + wcscat( bufW, L"\\" LCROSSWORDS_DIR L"\\" ); + } +} + +XP_U16 +ceGetPath( CEAppGlobals* globals, CePathType typ, + void* bufOut, XP_U16 bufLen ) +{ + XP_U16 len; + wchar_t bufW[CE_MAX_PATH_LEN]; + XP_U16 cacheIndx = typ == PROGFILES_PATH ? PROGFILES_CACHE : MY_DOCS_CACHE; + wchar_t* specialDir = globals->specialDirs[cacheIndx]; + XP_Bool asAscii = XP_FALSE; + + if ( !specialDir ) { + wchar_t buf[128]; + XP_U16 len; + lookupSpecialDir( buf, cacheIndx ); + len = 1 + wcslen( buf ); + specialDir = XP_MALLOC( globals->mpool, len * sizeof(specialDir[0]) ); + wcscpy( specialDir, buf ); + globals->specialDirs[cacheIndx] = specialDir; + } + + wcscpy( bufW, specialDir ); + + switch( typ ) { + case PREFS_FILE_PATH_L: + wcscat( bufW, L"xwprefs" ); + break; + case DEFAULT_DIR_PATH_L: + /* nothing to do */ + break; + case DEFAULT_GAME_PATH: + asAscii = XP_TRUE; + wcscat( bufW, L"_newgame" ); + break; + + case PROGFILES_PATH: + /* nothing to do */ + break; + } + + len = wcslen( bufW ); + if ( asAscii ) { + (void)WideCharToMultiByte( CP_ACP, 0, bufW, len + 1, + (char*)bufOut, bufLen, NULL, NULL ); + } else { + wcscpy( (wchar_t*)bufOut, bufW ); + } + return len; +} /* ceGetPath */ + +int +ceMessageBoxChar( CEAppGlobals* globals, HWND parent, const XP_UCHAR* str, + const wchar_t* title, XP_U16 buttons ) +{ + /* Get the length required, then alloc and go. This is technically + correct, but everywhere else I assume a 2:1 ratio for wchar_t:char. */ + XP_U16 clen = 1 + strlen(str); + XP_U32 wlen = 1 + MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, str, + clen, NULL, 0 ); + wchar_t widebuf[wlen]; + + MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, str, clen, widebuf, wlen ); + + if ( !parent ) { + parent = globals->hWnd; + } + return MessageBox( parent, widebuf, title, buttons ); +} /* ceMessageBoxChar */ + +int +ceOops( CEAppGlobals* globals, HWND parent, const XP_UCHAR* str ) +{ + return ceMessageBoxChar( globals, parent, str, L"Oops!", + MB_OK | MB_ICONHAND ); +} diff --git a/xwords4/wince/ceutil.h b/xwords4/wince/ceutil.h new file mode 100755 index 000000000..e01c42027 --- /dev/null +++ b/xwords4/wince/ceutil.h @@ -0,0 +1,99 @@ +/* -*-mode: C; fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2002-2004 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _CEUTIL_H_ +#define _CEUTIL_H_ + +#include "stdafx.h" +#include "cemain.h" + +void ceSetDlgItemText( HWND hDlg, XP_U16 id, const XP_UCHAR* buf ); +void ceGetDlgItemText( HWND hDlg, XP_U16 id, XP_UCHAR* buf, XP_U16* bLen ); + +void ceSetDlgItemNum( HWND hDlg, XP_U16 id, XP_S32 num ); +XP_S32 ceGetDlgItemNum( HWND hDlg, XP_U16 id ); + +void ceSetDlgItemFileName( HWND hDlg, XP_U16 id, XP_UCHAR* buf ); + +void positionDlg( HWND hDlg ); + +void ce_selectAndShow( HWND hDlg, XP_U16 resID, XP_U16 index ); + +void ceShowOrHide( HWND hDlg, XP_U16 resID, XP_Bool visible ); +void ceEnOrDisable( HWND hDlg, XP_U16 resID, XP_Bool visible ); + +XP_Bool ceGetChecked( HWND hDlg, XP_U16 resID ); +void ceSetChecked( HWND hDlg, XP_U16 resID, XP_Bool check ); + +void ceCenterCtl( HWND hDlg, XP_U16 resID ); +void ceCheckMenus( const CEAppGlobals* globals ); + +void ceGetItemRect( HWND hDlg, XP_U16 resID, RECT* rect ); +void ceMoveItem( HWND hDlg, XP_U16 resID, XP_S16 byX, XP_S16 byY ); + +int ceMessageBoxChar( CEAppGlobals* globals, HWND parent, const XP_UCHAR* str, + const wchar_t* title, XP_U16 buttons ); +int ceOops( CEAppGlobals* globals, HWND parent, const XP_UCHAR* str ); + +typedef enum { + PREFS_FILE_PATH_L + ,DEFAULT_DIR_PATH_L + ,DEFAULT_GAME_PATH + ,PROGFILES_PATH +} CePathType; +XP_U16 ceGetPath( CEAppGlobals* globals, CePathType typ, + void* buf, XP_U16 bufLen ); + +/* set vHeight to 0 to turn off scrolling */ +typedef enum { DLG_STATE_NONE = 0 + , DLG_STATE_TRAPBACK = 1 + , DLG_STATE_OKONLY = 2 + , DLG_STATE_DONEONLY = 4 +} DlgStateTask; +typedef struct CeDlgHdr { + CEAppGlobals* globals; + HWND hDlg; + + /* Below this line is private to ceutil.c */ + DlgStateTask doWhat; + XP_U16 nPage; +} CeDlgHdr; +void ceDlgSetup( CeDlgHdr* dlgHdr, HWND hDlg, DlgStateTask doWhat ); +void ceDlgComboShowHide( CeDlgHdr* dlgHdr, XP_U16 baseId ); +XP_Bool ceDoDlgHandle( CeDlgHdr* dlgHdr, UINT message, WPARAM wParam, LPARAM lParam); + +/* Are we drawing things in landscape mode? */ +XP_Bool ceIsLandscape( CEAppGlobals* globals ); + +void ceSetLeftSoftkey( CEAppGlobals* globals, XP_U16 id ); + +#if defined _WIN32_WCE +void ceSizeIfFullscreen( CEAppGlobals* globals, HWND hWnd ); +#else +# define ceSizeIfFullscreen( globals, hWnd ) +#endif + +#ifdef OVERRIDE_BACKKEY +void trapBackspaceKey( HWND hDlg ); +#else +# define trapBackspaceKey( hDlg ) +#endif + + +#endif diff --git a/xwords4/wince/debhacks.c b/xwords4/wince/debhacks.c new file mode 100644 index 000000000..522bf4e3b --- /dev/null +++ b/xwords4/wince/debhacks.c @@ -0,0 +1,102 @@ +/* -*- fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2006 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#if defined _WIN32_WCE && defined USE_RAW_MINGW + +#include +#include + +/* #include "xptypes.h" */ +/* #include "debhacks.h" */ + +/* These should eventually be replaced by implementations moved into + * mingw or the Debian pocketpc-sdk + */ + +/* These belong in mingw headers */ +wchar_t* wcscat( wchar_t *, const wchar_t * ); +wchar_t *wcscpy( wchar_t*,const wchar_t* ); + +wchar_t* +lstrcatW( wchar_t *dest, const wchar_t* src ) +{ + return wcscat( dest, src ); +} + +wchar_t* +lstrcpyW( wchar_t* dest, const wchar_t* src ) +{ + return wcscpy( dest, src ); +} + +int +DialogBoxParamW( HINSTANCE hinst, LPCWSTR name, HWND hwnd, + DLGPROC proc, LPARAM lparam ) +{ + HRSRC resstr = FindResource( hinst, name, RT_DIALOG ); + HGLOBAL lr = LoadResource( hinst, resstr ); + return DialogBoxIndirectParamW(hinst, lr, hwnd, proc, lparam ); +} + +BOOL +GetTextExtentPoint32W( HDC hdc, LPCWSTR str, int i, LPSIZE siz ) +{ + return GetTextExtentExPointW(hdc, str, i, 0, NULL, NULL, siz ); +} + +/* +see +http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wcehardware5/html/wce50lrfCeLogImportTable.asp +for how to implement SetEvent and ResetEvent in terms of EventModify + +SetEvent(h)->pEventModify(h, EVENT_SET) + +ResetEvent(h)->pEventModify(h, EVENT_RESET) + +PulseEvent(h)->pEventModify(h, EVENT_PULSE) + +http://www.opennetcf.org/forums/topic.asp?TOPIC_ID=257 defines the constants, +which are verified if this all works. :-) +*/ + +enum { + EVENT_PULSE = 1, + EVENT_RESET = 2, + EVENT_SET = 3 +}; + +BOOL +debhack_SetEvent(HANDLE h) +{ + return EventModify(h, EVENT_SET); +} + +BOOL +debhack_ResetEvent(HANDLE h) +{ + return EventModify(h, EVENT_RESET); +} + +DWORD +GetCurrentThreadId(void) +{ + return 0; +} + +#endif /* #ifdef _WIN32_WCE */ diff --git a/xwords4/wince/debhacks.h b/xwords4/wince/debhacks.h new file mode 100644 index 000000000..680e757f4 --- /dev/null +++ b/xwords4/wince/debhacks.h @@ -0,0 +1,94 @@ +/* -*- fill-column: 77; c-basic-offset: 4; -*- */ +/* + * Copyright 2006 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* This file should eventually go away as changes move up into the linux dev + * tools + */ + +#ifndef _DEBHACKS_H_ +#define _DEBHACKS_H_ + +#if defined USE_RAW_MINGW + +#ifdef USE_DEB_HACKS + +#define DH(func) debhack_##func + +typedef struct DH(WIN32_FIND_DATA) { + DWORD dwFileAttributes; + FILETIME dh_ftCreationTime; + FILETIME dh_ftLastAccessTime; + FILETIME dh_ftLastWriteTime; + DWORD dh_nFileSizeHigh; + DWORD dh_nFileSizeLow; + DWORD dh_dwReserved1; + WCHAR cFileName[MAX_PATH]; +} DH(WIN32_FIND_DATA); + +DWORD DH(GetCurrentThreadId)(void); +BOOL DH(SetEvent)(HANDLE); +BOOL DH(ResetEvent)(HANDLE); + +#else + +#define DH(func) func + +#endif + +/* these are apparently defined in aygshell.h. I got the values by googling + for 'define SHFS_SHOWTASKBAR' etc. They should eventually move into the + mingw project's headers .*/ + +#define SHFS_SHOWTASKBAR 0x0001 +#define SHFS_HIDETASKBAR 0x0002 +#define SHFS_SHOWSIPBUTTON 0x0004 +#define SHFS_HIDESIPBUTTON 0x0008 +#define SHFS_SHOWSTARTICON 0x0010 +#define SHFS_HIDESTARTICON 0x0020 + +/* got this somewhere else via google */ +#define SHCMBF_HMENU 0x0010 + +#endif /* USE_RAW_MINGW */ + +#ifndef IME_ESC_SET_MODE +# define IME_ESC_SET_MODE 0x0800 +#endif +#ifndef IM_SPELL +# define IM_SPELL 0 +#endif + +#if 0 + /* http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1591512&SiteID=1 */ +#define IM_SPELL 0 +#define IME_ESC_SET_MODE 0x0800 +/* http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1476620&SiteID=1 */ +#define EIM_SPELL IM_SPELL +/* http://wolfpack.twu.net/docs/gtkwin32/gdkprivate-win32.h */ +#define WM_IME_REQUEST 0x0288 + +/* http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1476620&SiteID=1 */ +#define EM_SETINPUTMODE 0x00DE + +/* #define IMR_ISIMEAWARE 0 */ +/* #define IMEAF_AWARE 0 */ +/* #define IME_ESC_RETAIN_MODE_ICON 0 */ +#endif + +#endif diff --git a/xwords4/wince/exe2cab.pl b/xwords4/wince/exe2cab.pl new file mode 100755 index 000000000..8a43bf8e3 --- /dev/null +++ b/xwords4/wince/exe2cab.pl @@ -0,0 +1,76 @@ +#!/usr/bin/perl + +# Script for turning the Crosswords executable into a cab, along with +# a shortcut in the games menu + +use strict; + +my $userName = "Crosswords.exe"; + +sub main() { + my $provider = "\"Crosswords project\""; + + usage() if 1 != @ARGV; + + my $path = shift @ARGV; + + usage() unless $path =~ m|.exe$|; + + die "$path not found\n" unless -f $path; + + my $cabname = `basename $path`; + chomp $cabname; + + # Create a link. The format, says Shaun, is + # #command line + + $userName = $cabname unless $userName; + my $cmdline = "\"\\Program Files\\Crosswords\\" . $userName . "\""; + my $cmdlen = length( $cmdline ); + + $cabname =~ s/.exe$//; + my $linkName = "Crosswords.lnk"; + open LINKFILE, "> $linkName"; + print LINKFILE $cmdlen, "#", $cmdline; + close LINKFILE; + + my $fname = "/tmp/file$$.list"; + +# see this url for %CE5% and other definitions: +# http://msdn.microsoft.com/library/default.asp?url=/library/en-us/DevGuideSP/html/sp_wce51consmartphonewindowscestringsozup.asp + + open FILE, "> $fname"; + + my $tmpfile = "/tmp/$userName"; + `cp $path $tmpfile`; + print FILE "$tmpfile "; + print FILE '%CE1%\\Crosswords', "\n"; + + print FILE "../dawg/English/BasEnglish2to8.xwd "; + print FILE '%CE1%\\Crosswords', "\n"; + +# print FILE "$ENV{'CEOPT_ROOT'}/opt/mingw32ce/arm-wince-mingw32ce/bin/mingwm10.dll "; +# print FILE '%CE2%\\mingwm10.dll', "\n"; + + print FILE "$linkName "; + print FILE '%CE14%', "\n"; + + close FILE; + + my $appname = $cabname; + $cabname .= ".cab"; + + my $cmd = "pocketpc-cab -p $provider -a $appname " + . "$fname $cabname"; + print( STDERR $cmd, "\n"); + `$cmd`; + + unlink $linkName, $tmpfile; +} + +sub usage() { + print STDERR "usage: $0 path/to/xwords4.exe\n"; + exit 2; +} + +main(); diff --git a/xwords4/wince/newres.h b/xwords4/wince/newres.h new file mode 100755 index 000000000..ac5edb7bc --- /dev/null +++ b/xwords4/wince/newres.h @@ -0,0 +1,39 @@ +#ifndef __NEWRES_H__ +#define __NEWRES_H__ + +#if !defined(UNDER_CE) + #define UNDER_CE _WIN32_WCE +#endif + +#if defined(_WIN32_WCE) + #if !defined(WCEOLE_ENABLE_DIALOGEX) + #define DIALOGEX DIALOG DISCARDABLE + #endif + #include + #define SHMENUBAR RCDATA + #if defined(WIN32_PLATFORM_PSPC) && (_WIN32_WCE >= 300) + #include + #else + #define I_IMAGENONE (-2) + #define NOMENU 0xFFFF + #define IDS_SHNEW 1 + + #define IDM_SHAREDNEW 10 + #define IDM_SHAREDNEWDEFAULT 11 + #endif +#endif // _WIN32_WCE + + +#ifdef RC_INVOKED +#ifndef _INC_WINDOWS +#define _INC_WINDOWS + #include "winuser.h" // extract from windows header +#endif +#endif + +#ifdef IDC_STATIC +#undef IDC_STATIC +#endif +#define IDC_STATIC (-1) + +#endif //__NEWRES_H__ diff --git a/xwords4/wince/resource.h b/xwords4/wince/resource.h new file mode 100755 index 000000000..dbc1c7012 --- /dev/null +++ b/xwords4/wince/resource.h @@ -0,0 +1,284 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by xwords4.rc +// +#define IDS_APP_TITLE 1 +#define IDC_XWORDS4 3 +#define IDI_XWORDS4 101 +#define IDM_MENU 102 +#define IDD_SAVEDGAMESDLG 103 +#define IDD_SAVENAMEDLG 107 +#define IDD_GAMEINFO 104 +#define IDD_STRBOX 106 +#define IDB_RIGHTARROW 111 +#define IDB_DOWNARROW 112 +#define IDD_ASKBLANK 113 +#define IDD_ASKPASS 116 +#define IDD_OPTIONSDLG 117 +#define IDD_COLORSDLG 118 +#define IDD_COLOREDITDLG 119 +#define IDM_MAIN_MENUBAR 120 +#define IDM_OKCANCEL_MENUBAR 121 +#define IDM_DONE_MENUBAR 122 +#define IDM_OK_MENUBAR 123 +#define IDB_ORIGIN 124 +#ifdef XWFEATURE_SEARCHLIMIT +# define IDD_ASKHINTLIMTS 125 +#endif +#ifndef XWFEATURE_STANDALONE_ONLY +# define IDD_CONNSSDLG 126 +#endif +#ifdef ALLOW_CHOOSE_FONTS +# define IDD_FONTSSDLG 127 +#endif + +#define REMOTE_CHECK1 1005 +#define NAME_EDIT1 1006 +#define ROBOT_CHECK1 1007 +#define PASS_EDIT1 1008 + +#define REMOTE_CHECK2 1009 +#define NAME_EDIT2 1010 +#define ROBOT_CHECK2 1011 +#define PASS_EDIT2 1012 + +#define REMOTE_CHECK3 1013 +#define NAME_EDIT3 1014 +#define ROBOT_CHECK3 1015 +#define PASS_EDIT3 1016 + +#define REMOTE_CHECK4 1017 +#define NAME_EDIT4 1018 +#define ROBOT_CHECK4 1019 +#define PASS_EDIT4 1020 + +#define IDC_COMBO1 1021 +#define PLAYERNUM_COMBO 1022 +#define TIMER_CHECK 1024 +#define NAME_EDIT5 1025 +#define TIMER_EDIT 1026 +#define BLANKFACE_COMBO 1029 +#define IDC_PWDLABEL 1031 +#define PASS_EDIT 1032 +#define IDC_NPLAYERSLIST 1034 +#define OPTIONS_BUTTON 1035 +#define IDC_RADIOGLOBAL 1036 +#define IDC_RADIOLOCAL 1037 +#define IDC_LEFTYCHECK 1038 +#define IDC_CHECKCOLORPLAYED 1039 +#define IDC_CHECKSMARTROBOT 1040 +#define IDC_CHECKHINTSOK 1041 +#define IDC_CHECKSHOWCURSOR 1042 +#define IDC_CHECKROBOTSCORES 1043 +#define IDC_HIDETILEVALUES 1044 +#define IDC_PREFCOLORS 1045 +#define IDC_PREFFONTS 1046 +#define PHONIES_LABEL 1048 +#define IDC_ROLECOMBO 1049 +#define GIJUGGLE_BUTTON 1050 +#define IDC_TOTAL_LABEL 1051 +#define IDC_REMOTE_LABEL 1052 +#define IDC_PICKTILES 1053 +#define IDC_BPICK 1054 +#define IDC_PICKMSG 1055 +#ifdef FEATURE_TRAY_EDIT +# define IDC_CPICK 1100 +# define IDC_BACKUP 1056 +#endif +#ifdef XWFEATURE_SEARCHLIMIT +# define IDC_CHECKHINTSLIMITS 1057 +#endif + +/* buttons and lables must be parallel arrays so CLRSEL_LABEL_OFFSET + works. */ +#define DLBLTR_BUTTON 1058 +#define DBLWRD_BUTTON 1059 +#define TPLLTR_BUTTON 1060 +#define TPLWRD_BUTTON 1061 +#define EMPCELL_BUTTON 1062 +#define TBACK_BUTTON 1063 +#define FOCUSCLR_BUTTON 1064 +#define PLAYER1_BUTTON 1065 +#define PLAYER2_BUTTON 1066 +#define PLAYER3_BUTTON 1067 +#define PLAYER4_BUTTON 1068 + +#define DLBLTR_LABEL 1069 +#define DBLWRD_LABEL 1070 +#define TPLLTR_LABEL 1071 +#define TPLWRD_LABEL 1072 +#define EMPCELL_LABEL 1073 +#define TBACK_LABEL 1074 +#define FOCUSCLR_LABEL 1075 +#define PLAYER1_LABEL 1076 +#define PLAYER2_LABEL 1077 +#define PLAYER3_LABEL 1078 +#define PLAYER4_LABEL 1079 + +#define DLBLTR_SAMPLE 1080 +#define DBLWRD_SAMPLE 1081 +#define TPLLTR_SAMPLE 1082 +#define TPLWRD_SAMPLE 1083 +#define EMPCELL_SAMPLE 1084 +#define TBACK_SAMPLE 1085 +#define FOCUSCLR_SAMPLE 1086 +#define PLAYER1_SAMPLE 1087 +#define PLAYER2_SAMPLE 1088 +#define PLAYER3_SAMPLE 1089 +#define PLAYER4_SAMPLE 1090 + +#define CLRSEL_LABEL_OFFSET (DLBLTR_LABEL-DLBLTR_BUTTON) + +/* editor dlg: assumption is that the edit field's ID is one more + than the corresponding slider's */ +#ifdef MY_COLOR_SEL +# define CLREDT_SLIDER1 1091 +# define RED_EDIT 1092 +# define CLREDT_SLIDER2 1093 +# define GREEN_EDIT 1094 +# define CLREDT_SLIDER3 1095 +# define BLUE_EDIT 1096 + +# define RED_LABEL 1097 +# define GREEN_LABEL 1098 +# define BLUE_LABEL 1099 +# define CLSAMPLE_BUTTON_ID 1123 +#endif // MY_COLOR_SEL + +#ifdef ALLOW_CHOOSE_FONTS +# define FONTS_LABEL 1123 +# define FONTS_COMBO 1124 +# define IDC_FONTSUPDOWN 1125 +# define FONTS_COMBO_PPC 1126 +# define FONTSIZE_LABEL 1127 +# define FONTSIZE_COMBO 1128 +# define IDC_FONTSIZEUPDOWN 1129 +# define FONTSIZE_COMBO_PPC 1130 +#endif + +#define IDC_CCONVIA_LAB 1106 + +#define IDC_COOKIE_LAB 1107 +#ifdef XWFEATURE_RELAY +# define IDC_CRELAYNAME_LAB 1108 +# define IDC_CRELAYPORT_LAB 1109 +# define IDC_CRELAYHINT_LAB 1110 + +# define IDC_CONNECTCOMBO 1111 +# define RELAYNAME_EDIT 1112 +# define RELAYPORT_EDIT 1113 +# define COOKIE_EDIT 1114 + +#endif + +#define IDC_BLUET_ADDR_LAB 1115 +#ifdef XWFEATURE_BLUETOOTH +# define IDC_BLUET_ADDR_EDIT 1116 +# define IDC_BLUET_ADDR_BROWSE 1117 +#endif +/* #define IDS_UPDOWN 1118 */ + + +#define IDC_SVGM_SELLAB 1127 +/* Let's remove these until they're implemented */ +#define IDC_SVGM_EDITLAB 1131 +#define IDC_SVGM_CHANGE 1130 +#define IDC_SVGM_DUP 1129 +#define IDC_SVGM_DEL 1128 +#define IDC_SVGM_OPEN 1120 + +#define IDC_SVGN_SELLAB 1125 +#define IDC_SVGN_EDIT 1122 + + +#define ID_FILE_EXIT 40002 +#define IDM_HELP_ABOUT 40003 +#define ID_FILE_ABOUT 40004 +#define ID_GAME_GAMEINFO 40005 +#define ID_GAME_HISTORY 40006 +#define ID_GAME_FINALSCORES 40007 +#define ID_GAME_TILECOUNTSANDVALUES 40008 +#define ID_GAME_TILESLEFT 40009 +#define ID_MOVE_HINT 40010 +#ifdef XWFEATURE_SEARCHLIMIT +# define ID_MOVE_LIMITEDHINT 40011 +#endif +#define ID_MOVE_NEXTHINT 40012 +#define ID_MOVE_UNDOCURRENT 40013 +#define ID_MOVE_UNDOLAST 40014 +#define ID_MOVE_TRADE 40015 +#define ID_MOVE_JUGGLE 40016 +#define ID_MOVE_HIDETRAY 40017 +#define ID_MOVE_TURNDONE 40018 +#define ID_MOVE_FLIP 40019 +#define ID_MOVE_VALUES 40027 +#define ID_FILE_NEWGAME 40020 +#define ID_FILE_SAVEDGAMES 40021 +#define ID_EDITTEXT 40022 +#define ID_FILE_PREFERENCES 40023 +#define ID_GAME_RESENDMSGS 40025 +#define ID_FILE_FULLSCREEN 40026 + +#define ID_INITIAL_SOFTID ID_MOVE_TURNDONE + +#ifndef _WIN32_WCE +# define W32_DUMMY_ID 40028 +#endif + +#define ID_COLORS_RES 9999 +#define ID_BONUS_RES 9998 + +#define IDM_MAIN_COMMAND1 40001 +#define IDS_MENU 40002 +#define IDS_DUMMY 40003 +#define IDS_CANCEL 40004 +#define IDS_OK 40005 +#define IDS_ABOUT 40006 +#define IDS_DONE 40007 +#define IDS_DICTLOC 40029 +#define IDS_SAVENAME 40030 +#define IDS_DUPENAME 40031 +#define IDS_RENAME 40032 + +// These are in sets of three, and must be consecutive and in the right order within each set +#define PHONIES_COMBO 1200 +#define IDC_PHONIESUPDOWN 1201 +#define PHONIES_COMBO_PPC 1202 + +#define HC_MIN_COMBO 1203 +#define HC_MIN_UPDOWN 1204 +#define HC_MIN_COMBO_PPC 1205 + +#define HC_MAX_COMBO 1206 +#define HC_MAX_UPDOWN 1207 +#define HC_MAX_COMBO_PPC 1208 + +#define IDC_SVGM_GAMELIST 1209 +#define IDC_SVGM_UPDOWN 1210 +#define IDC_SVGM_GAMELIST_PPC 1211 + +#define BLANKFACE_LIST 1212 +#define IDC_ASKBLANK_UPDOWN 1213 +#define BLANKFACE_LIST_PPC 1214 + +#define IDC_DICTLIST 1215 +#define IDC_DICTUPDOWN 1216 +#define IDC_DICTLIST_PPC 1217 + +#define IDC_NPLAYERSCOMBO 1218 +#define IDC_NPLAYERSUPDOWN 1219 +#define IDC_NPLAYERSCOMBO_PPC 1220 + +#define IDC_DICTLABEL 1221 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 125 +#define _APS_NEXT_COMMAND_VALUE 40033 +#define _APS_NEXT_CONTROL_VALUE 1128 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif + diff --git a/xwords4/wince/scripts/makezip.sh b/xwords4/wince/scripts/makezip.sh new file mode 100755 index 000000000..49b9bbb5c --- /dev/null +++ b/xwords4/wince/scripts/makezip.sh @@ -0,0 +1,86 @@ +#!/bin/sh + +DICT=../dawg/English/BasEnglish2to8.xwd +TMPDIR=/tmp/_$$ +README=$TMPDIR/README.txt + +function usage() { + echo "usage: $0 --exe exe_file " + echo " [--name exe_name_to_use]" + echo " [--dict dict_to_include]" + echo " [--out zipfile_to_produce]" + exit 0 +} + +while :; do + case "$1" in + --exe) + [ -z $2 ] && usage + EXE=$2 + shift 2 + ;; + --name) + [ -z $2 ] && usage + NAME=$2 + shift 2 + ;; + --dict) + [ -z $2 ] && usage + DICT=$2 + shift 2 + ;; + --out) + [ -z $2 ] && usage + OUTFILE=$2 + shift 2 + ;; + "") + break + ;; + *) + usage + esac +done + +[ -z "$EXE" ] && usage +[ -z "$OUTFILE" ] && OUTFILE=${NAME%.exe}.zip + +mkdir -p $TMPDIR + +# If name's specified, we need to create that file. Do it before +# catting text below so EXE will be correct + +if [ ! -z "$NAME" ]; then + NAME=$TMPDIR/$NAME + echo "copying $EXE to $NAME" + cp $EXE $NAME + EXE=$NAME +fi + +cat > $README </dev/null 2>&1; then + : # nothing to do +elif [ -s "$XWDICT" ]; then + cp $XWDICT ../obj_${PLAT}_${DBG} +else + cp ../../dawg/English/BasEnglish2to8.xwd ../obj_${PLAT}_${DBG} +fi + +for SIZE in ${SIZES[*]}; do + WIDTH=${SIZE%x*} + HEIGHT=${SIZE#*x} + + for EXE in $EXES; do # + CMD="wine $EXE width=$WIDTH height=$HEIGHT" + echo $CMD + eval "$CMD" + break # -c sorts by date, so quit after running newest + done + +done diff --git a/xwords4/wince/shared.mk b/xwords4/wince/shared.mk new file mode 100755 index 000000000..83d0c0fcd --- /dev/null +++ b/xwords4/wince/shared.mk @@ -0,0 +1,46 @@ +# -*- mode: Makefile; -*- + +ifeq (x$(shell echo $$PPC_SDK_PPC)x,xx) +WINCE_PATH = "PLEASE DEFINE shell env variable WINCE_DEV_PATH" +include exit_right_now +else +WINCE_PATH = $(shell echo $$PPC_SDK_PPC) +endif + +PLATOBJ = \ + $(PLATFORM)/StdAfx.o \ + $(PLATFORM)/ceaskpwd.o \ + $(PLATFORM)/ceblank.o \ + $(PLATFORM)/cedict.o \ + $(PLATFORM)/cedraw.o \ + $(PLATFORM)/ceginfo.o \ + $(PLATFORM)/cecondlg.o \ + $(PLATFORM)/cemain.o \ + $(PLATFORM)/ceprefs.o \ + $(PLATFORM)/cestrbx.o \ + $(PLATFORM)/ceutil.o \ + $(PLATFORM)/ceclrsel.o \ + $(PLATFORM)/cesockwr.o \ + $(PLATFORM)/cehntlim.o \ + +XW_BOTH_DEFINES = \ + /DBEYOND_IR /DNODE_CAN_4 /DMY_COLOR_SEL \ + /DCOLOR_SUPPORT /DFEATURE_TRAY_EDIT /DXWFEATURE_SEARCHLIMIT \ + /DXWFEATURE_HINT_CONFIG \ + /D POINTER_SUPPORT /D KEY_SUPPORT /D __LITTLE_ENDIAN \ + /DCEFEATURE_CANSCROLL \ + $(DEBUG) $(MEM_DEBUG) \ + +XW_C_DEFINES = \ + $(XW_BOTH_DEFINES) \ + +XW_RES_DEFINES = \ + $(XW_BOTH_DEFINES) + +all: $(TARGET) + +debug: + $(MAKE) DEBUG="/DDEBUG" + +memdebug: + $(MAKE) DEBUG="/DDEBUG" MEM_DEBUG="/DMEM_DEBUG" diff --git a/xwords4/wince/simrel.mk b/xwords4/wince/simrel.mk new file mode 100755 index 000000000..4c068cabf --- /dev/null +++ b/xwords4/wince/simrel.mk @@ -0,0 +1,59 @@ +# -*- mode: Makefile; compile-command: "make -f simrel.mk"; -*- + +PLATFORM = emulatorDbg +TARGET = $(PLATFORM)/xwords4.exe + +LIBS = commctrl.lib coredll.lib corelibc.lib winsock.lib aygshell.lib +RSRC = $(PLATFORM)/xwords4.res + +MAKE = make -f simrel.mk +CC = $(WCE420)/bin/cl.exe +LINK = $(WCE420)/bin/link.exe +RC = $(VSDIR)/Common/MSDev98/Bin/rc.exe + +include ../common/config.mk + +include ./shared.mk + +C_CMD = \ + $(CC) /nologo /W3 /Zi /Od \ + /I "$(WINCE_PATH)\Include\Emulator" \ + /I "..\common" /I "..\relay" /I "." /D "DEBUG" /D "_i386_" /D UNDER_CE=420 \ + /D _WIN32_WCE=420 /D "WIN32_PLATFORM_PSPC=400" /D "i_386_" \ + /D "UNICODE" /D "_UNICODE" /D "_X86_" /D "x86" $(XW_C_DEFINES) \ + /Fo$@ /Gs8192 /GF /c $< + +$(PLATFORM)/StdAfx.o: StdAfx.cpp + $(C_CMD) + +$(PLATFORM)/%.o: %.c + $(C_CMD) + +../common/$(PLATFORM)/%.o: ../common/%.c + $(C_CMD) + +$(RSRC): xwords4.rc + $(RC) /l 0x409 /fo$@ \ + /i "$(WINCE_PATH)\Include\Emulator" \ + /d "WIN32_PLATFORM_PSPC=400" /d UNDER_CE=420 /d _WIN32_WCE=420 \ + /d "UNICODE" /d "_UNICODE" /d "DEBUG" /d "_X86_" /d "x86" /d "_i386_" \ + $(XW_RES_DEFINES) \ + /r $< + +test: + echo $(basename $(TARGET)).pdb + +$(TARGET): $(COMMONOBJ) $(PLATOBJ) $(RSRC) + $(LINK) $(LIBS) /nologo /base:"0x00010000" /stack:0x10000,0x1000 \ + /entry:"WinMainCRTStartup" /incremental:yes \ + /pdb:$(basename $(TARGET)).pdb \ + /debug /nodefaultlib:"OLDNAMES.lib" /nodefaultlib:libc.lib \ + /nodefaultlib:libcd.lib /nodefaultlib:libcmt.lib \ + /nodefaultlib:libcmtd.lib /nodefaultlib:msvcrt.lib \ + /nodefaultlib:msvcrtd.lib /out:$@ \ + /libpath:"$(WINCE_PATH)\Lib\emulator" \ + /subsystem:windowsce,4.20 /MACHINE:IX86 \ + $(COMMONOBJ) $(PLATOBJ) $(RSRC) + +clean: + rm -f $(COMMONOBJ) $(PLATOBJ) $(TARGET) $(RSRC) $(PLATFORM)/*.pdb diff --git a/xwords4/wince/stdafx.h b/xwords4/wince/stdafx.h new file mode 100755 index 000000000..3bc1e1762 --- /dev/null +++ b/xwords4/wince/stdafx.h @@ -0,0 +1,25 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_) +#define AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +// Windows Header Files: +#include + +// Local Header Files + +// TODO: reference additional headers your program requires here + +//{{AFX_INSERT_LOCATION}} +// Microsoft eMbedded Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_) diff --git a/xwords4/wince/xptypes.h b/xwords4/wince/xptypes.h new file mode 100755 index 000000000..3bf17bdb2 --- /dev/null +++ b/xwords4/wince/xptypes.h @@ -0,0 +1,146 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 1999-2008 by Eric House (xwords@eehouse.org). All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _XPTYPES_H_ +#define _XPTYPES_H_ + +#include "stdafx.h" +#include +#include "xwords4.h" +#include +#include +#include + +typedef unsigned char XP_U8; +typedef signed char XP_S8; + +typedef unsigned short XP_U16; +typedef signed short XP_S16; + +typedef unsigned long XP_U32; +typedef signed long XP_S32; + +typedef char XP_UCHAR; + +typedef signed short XP_FontCode; /* not sure how I'm using this yet */ +typedef BOOL XP_Bool; +typedef XP_U32 XP_Time; + +#define XP_TRUE ((XP_Bool)(1==1)) +#define XP_FALSE ((XP_Bool)(1==0)) + +#define XP_CR "\015\012" /* 'Doze expects a carraige return followed by a linefeed */ + +#ifdef _WIN32_WCE +# define XP_RANDOM() Random() +#else +# define XP_RANDOM() rand() +#endif + +#ifdef MEM_DEBUG +# define XP_PLATMALLOC(nbytes) malloc(nbytes) +# define XP_PLATREALLOC(p,s) realloc((p), (s)) +# define XP_PLATFREE(p) free(p) +#else +# define XP_MALLOC(pool, nbytes) malloc(nbytes) +# define XP_REALLOC(pool, p, bytes) realloc((p), (bytes)) +# define XP_FREE(pool, p) free(p) +#endif + +#define XP_MEMSET(src, val, nbytes) memset( (src), (val), (nbytes) ) +#define XP_MEMCPY(d,s,l) memcpy((d),(s),(l)) +#define XP_MEMMOVE(d,s,l) memmove((d),(s),(l)) +#define XP_MEMCMP( a1, a2, l ) memcmp((a1),(a2),(l)) +#define XP_STRLEN(s) strlen((char*)(s)) +#define XP_STRCAT(d,s) strcat((d),(s)) +#define XP_STRCMP(s1,s2) strcmp((char*)(s1),(char*)(s2)) +#define XP_STRNCMP(s1,s2,l) strncmp((char*)(s1),(char*)(s2),(l)) +#define XP_SNPRINTF wince_snprintf + +#define XP_MIN(a,b) ((a)<(b)?(a):(b)) +#define XP_MAX(a,b) ((a)>(b)?(a):(b)) + +#ifdef DEBUG + +#define XP_ASSERT(b) if(!(b)) { wince_assert(#b, __LINE__, __FILE__); } +#else +# define XP_ASSERT(b) +#endif + +//#define XP_STATUSF if(0)p_ignore +#define XP_STATUSF XP_DEBUGF + +#ifdef ENABLE_LOGGING +#define XP_DEBUGF(...) wince_debugf(__VA_ARGS__) +#define XP_LOGF(...) wince_debugf(__VA_ARGS__) +#define XP_WARNF(...) wince_warnf(__VA_ARGS__) +#else +#define XP_DEBUGF(...) +#define XP_LOGF(...) +#define XP_WARNF(...) +#endif + +#ifdef CPLUS +extern "C" { +#endif + +void wince_assert(XP_UCHAR* s, int line, char* fileName ); +void wince_debugf(const XP_UCHAR*, ...) + __attribute__ ((format (printf, 1, 2))); +void wince_warnf(const XP_UCHAR*, ...) + __attribute__ ((format (printf, 1, 2))); +void p_ignore(XP_UCHAR*, ...); +XP_U16 wince_snprintf( XP_UCHAR* buf, XP_U16 len, + const XP_UCHAR* format, ... ); + +#define XP_NTOHL(l) ntohl(l) +#define XP_NTOHS(s) ntohs(s) +#define XP_HTONL(l) htonl(l) +#define XP_HTONS(s) htons(s) + +#define XP_LD "%ld" +#define XP_P "%p" + +/* The pocketpc sdk on linux renames certain functions to avoid conflicts + with same-named posix symbols. */ +#if defined __GNUC__ && defined _WIN32_WCE +# define MS(func) M$_##func +#else +# define MS(func) func +#endif + +#ifdef _WIN32_WCE +# undef CALLBACK +# define CALLBACK +#endif + +#ifdef _WIN32_WCE +# define XP_UNUSED_CE(x) XP_UNUSED(x) +# define XP_UNUSED_32(x) x +#else +# define XP_UNUSED_32(x) XP_UNUSED(x) +# define XP_UNUSED_CE(x) x +#endif + +#ifdef CPLUS +} +#endif + +#endif + diff --git a/xwords4/wince/xwd2cab.pl b/xwords4/wince/xwd2cab.pl new file mode 100755 index 000000000..9f15c7154 --- /dev/null +++ b/xwords4/wince/xwd2cab.pl @@ -0,0 +1,49 @@ +#!/usr/bin/perl + +# Script for turning Crosswords .xwd files into .cab files that, when +# run on a PPC device, will install the enclosed .xwd file into a +# Crosswords directory in "c:\Program Files\Crosswords". + +use strict; + +my $provider = "\"Crosswords project\""; + +usage() if 0 == @ARGV; + +while ( my $path = shift @ARGV ) { + + unless ( $path =~ m|.xwd$| ) { + print STDERR "skipping $path: doesn't end in .xwd\n"; + next; + } + unless (-f $path ) { + print STDERR "skipping $path: not found\n"; + next; + } + + my $fname = "/tmp/file$$.list"; + + # see this url for %CE5% and other definitions: + # http://msdn.microsoft.com/library/default.asp?url=/library/en-us/DevGuideSP/html/sp_wce51consmartphonewindowscestringsozup.asp + + open FILE, "> $fname"; + print FILE "$path "; + print FILE '%CE1%\\Crosswords', "\n"; + close FILE; + + my $cabname = `basename $path`; + chomp $cabname; + $cabname =~ s/.xwd$//; + my $appname = $cabname; + $cabname .= "_xwd.cab"; + + `pocketpc-cab -p $provider -a $appname $fname $cabname`; + + print STDERR "$cabname done\n"; + unlink $fname; +} + +sub usage() { + print STDERR "usage: $0 xwdfile[ xwdfile]*\n"; + exit 2; +} diff --git a/xwords4/wince/xwords.vcp b/xwords4/wince/xwords.vcp new file mode 100644 index 000000000..7cab46794 --- /dev/null +++ b/xwords4/wince/xwords.vcp @@ -0,0 +1,4630 @@ +# Microsoft eMbedded Visual Tools Project File - Name="xwords" - Package Owner=<4> +# Microsoft eMbedded Visual Tools Generated Build File, Format Version 6.02 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (WCE ARMV4) Application" 0xa301 +# TARGTYPE "Win32 (WCE x86) Application" 0x8301 +# TARGTYPE "Win32 (WCE ARM) Application" 0x8501 +# TARGTYPE "Win32 (WCE emulator) Application" 0xa601 + +CFG=xwords - Win32 (WCE ARM) Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "xwords.vcn". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "xwords.vcn" CFG="xwords - Win32 (WCE ARM) Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "xwords - Win32 (WCE ARM) Release" (based on "Win32 (WCE ARM) Application") +!MESSAGE "xwords - Win32 (WCE ARM) Debug" (based on "Win32 (WCE ARM) Application") +!MESSAGE "xwords - Win32 (WCE x86) Release" (based on "Win32 (WCE x86) Application") +!MESSAGE "xwords - Win32 (WCE x86) Debug" (based on "Win32 (WCE x86) Application") +!MESSAGE "xwords - Win32 (WCE ARMV4) Debug" (based on "Win32 (WCE ARMV4) Application") +!MESSAGE "xwords - Win32 (WCE emulator) Debug" (based on "Win32 (WCE emulator) Application") +!MESSAGE "xwords - Win32 (WCE ARMV4) Release" (based on "Win32 (WCE ARMV4) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +# PROP ATL_Project 2 + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "ARMRel" +# PROP BASE Intermediate_Dir "ARMRel" +# PROP BASE CPU_ID "{D6518FFC-710F-11D3-99F2-00105A0DF099}" +# PROP BASE Platform_ID "{8A9A2F80-6887-11D3-842E-005004848CBA}" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "ARMRel" +# PROP Intermediate_Dir "ARMRel" +# PROP CPU_ID "{D6518FFC-710F-11D3-99F2-00105A0DF099}" +# PROP Platform_ID "{8A9A2F80-6887-11D3-842E-005004848CBA}" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +LINK32=link.exe +# ADD BASE LINK32 commctrl.lib coredll.lib aygshell.lib /nologo /base:"0x00010000" /stack:0x10000,0x1000 /entry:"WinMainCRTStartup" /nodefaultlib:"$(CENoDefaultLib)" /subsystem:$(CESubsystem) /align:"4096" /MACHINE:ARM +# ADD LINK32 commctrl.lib coredll.lib aygshell.lib winsock.lib commdlg.lib /nologo /base:"0x00010000" /stack:0x10000,0x1000 /entry:"WinMainCRTStartup" /nodefaultlib:"$(CENoDefaultLib)" /out:"ARMRel/Crosswords.exe" /subsystem:$(CESubsystem) /align:"4096" /MACHINE:ARM +# SUBTRACT LINK32 /pdb:none +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +MTL=midl.exe +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o "NUL" /win32 +CPP=clarm.exe +# ADD BASE CPP /nologo /W3 /D _WIN32_WCE=$(CEVersion) /D "$(CePlatform)" /D "ARM" /D "_ARM_" /D UNDER_CE=$(CEVersion) /D "UNICODE" /D "_UNICODE" /D "NDEBUG" /YX /Oxs /M$(CECrtMT) /c +# ADD CPP /nologo /W3 /I "." /I "../common" /D "ARM" /D "_ARM_" /D "NDEBUG" /D "XWFEATURE_CE_EDITCOLORS" /D "XWFEATURE_SEARCHLIMIT" /D UNDER_CE=$(CEVersion) /D _WIN32_WCE=$(CEVersion) /D "$(CePlatform)" /D "UNICODE" /D "_UNICODE" /D "POINTER_SUPPORT" /D "KEY_SUPPORT" /D "__LITTLE_ENDIAN" /D "XWFEATURE_STANDALONE_ONLY" /D "NODE_CAN_4" /D "FEATURE_TRAY_EDIT" /D "MY_COLOR_SEL" /YX /Oxs /M$(CECrtMT) /c +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d UNDER_CE=$(CEVersion) /d _WIN32_WCE=$(CEVersion) /d "UNICODE" /d "_UNICODE" /d "NDEBUG" /d "$(CePlatform)" /d "ARM" /d "_ARM_" /r +# ADD RSC /l 0x409 /d "NDEBUG" /d "ARM" /d "_ARM_" /d "FEATURE_TRAY_EDIT" /d UNDER_CE=$(CEVersion) /d _WIN32_WCE=$(CEVersion) /d "UNICODE" /d "_UNICODE" /d "$(CePlatform)" /d "XWFEATURE_STANDALONE_ONLY" /d "MY_COLOR_SEL" /d "XWFEATURE_SEARCHLIMIT" /r + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "ARMDbg" +# PROP BASE Intermediate_Dir "ARMDbg" +# PROP BASE CPU_ID "{D6518FFC-710F-11D3-99F2-00105A0DF099}" +# PROP BASE Platform_ID "{8A9A2F80-6887-11D3-842E-005004848CBA}" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "ARMDbg" +# PROP Intermediate_Dir "ARMDbg" +# PROP CPU_ID "{D6518FFC-710F-11D3-99F2-00105A0DF099}" +# PROP Platform_ID "{8A9A2F80-6887-11D3-842E-005004848CBA}" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +LINK32=link.exe +# ADD BASE LINK32 commctrl.lib coredll.lib aygshell.lib /nologo /base:"0x00010000" /stack:0x10000,0x1000 /entry:"WinMainCRTStartup" /debug /nodefaultlib:"$(CENoDefaultLib)" /subsystem:$(CESubsystem) /align:"4096" /MACHINE:ARM +# ADD LINK32 winsock.lib commctrl.lib coredll.lib commdlg.lib aygshell.lib /nologo /base:"0x00010000" /stack:0x10000,0x1000 /entry:"WinMainCRTStartup" /debug /nodefaultlib:"$(CENoDefaultLib)" /out:"ARMDbg/Crosswords.exe" /subsystem:$(CESubsystem) /align:"4096" /MACHINE:ARM +# SUBTRACT LINK32 /pdb:none +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +MTL=midl.exe +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o "NUL" /win32 +CPP=clarm.exe +# ADD BASE CPP /nologo /W3 /Zi /Od /D "DEBUG" /D "ARM" /D "_ARM_" /D UNDER_CE=$(CEVersion) /D _WIN32_WCE=$(CEVersion) /D "$(CePlatform)" /D "UNICODE" /D "_UNICODE" /YX /M$(CECrtMTDebug) /c +# ADD CPP /nologo /W3 /Zi /Od /I "." /I "../common" /D "DEBUG" /D "ARM" /D "_ARM_" /D "XWFEATURE_CE_EDITCOLORS" /D UNDER_CE=$(CEVersion) /D _WIN32_WCE=$(CEVersion) /D "$(CePlatform)" /D "UNICODE" /D "_UNICODE" /D "POINTER_SUPPORT" /D "KEY_SUPPORT" /D "__LITTLE_ENDIAN" /D "XWFEATURE_STANDALONE_ONLY" /D "NODE_CAN_4" /D "FEATURE_TRAY_EDIT" /D "MY_COLOR_SEL" /D "XWFEATURE_SEARCHLIMIT" /YX /M$(CECrtMTDebug) /c +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d UNDER_CE=$(CEVersion) /d _WIN32_WCE=$(CEVersion) /d "UNICODE" /d "_UNICODE" /d "DEBUG" /d "$(CePlatform)" /d "ARM" /d "_ARM_" /r +# ADD RSC /l 0x409 /d "DEBUG" /d "ARM" /d "_ARM_" /d "FEATURE_TRAY_EDIT" /d UNDER_CE=$(CEVersion) /d _WIN32_WCE=$(CEVersion) /d "UNICODE" /d "_UNICODE" /d "$(CePlatform)" /d "XWFEATURE_STANDALONE_ONLY" /d "MY_COLOR_SEL" /d "XWFEATURE_SEARCHLIMIT" /r + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "X86Rel" +# PROP BASE Intermediate_Dir "X86Rel" +# PROP BASE CPU_ID "{D6518FF3-710F-11D3-99F2-00105A0DF099}" +# PROP BASE Platform_ID "{8A9A2F80-6887-11D3-842E-005004848CBA}" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "X86Rel" +# PROP Intermediate_Dir "X86Rel" +# PROP CPU_ID "{D6518FF3-710F-11D3-99F2-00105A0DF099}" +# PROP Platform_ID "{8A9A2F80-6887-11D3-842E-005004848CBA}" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d UNDER_CE=$(CEVersion) /d _WIN32_WCE=$(CEVersion) /d "UNICODE" /d "_UNICODE" /d "NDEBUG" /d "$(CePlatform)" /d "_X86_" /d "x86" /d "_i386_" /r +# ADD RSC /l 0x409 /d "NDEBUG" /d "_X86_" /d "x86" /d "_i386_" /d UNDER_CE=$(CEVersion) /d _WIN32_WCE=$(CEVersion) /d "UNICODE" /d "_UNICODE" /d "$(CePlatform)" /d "XWFEATURE_SEARCHLIMIT" /d "FEATURE_TRAY_EDIT" /d "XWFEATURE_STANDALONE_ONLY" /d "MY_COLOR_SEL" /d "CEFEATURE_CANSCROLL" /r +CPP=cl.exe +# ADD BASE CPP /nologo /W3 /Oxs /D _WIN32_WCE=$(CEVersion) /D "$(CePlatform)" /D "_i386_" /D UNDER_CE=$(CEVersion) /D "i_386_" /D "UNICODE" /D "_UNICODE" /D "_X86_" /D "x86" /D "NDEBUG" /YX /Gs8192 /GF /c +# ADD CPP /nologo /W3 /Oxs /I "." /I "../common" /I "./" /D "_i386_" /D "i_386_" /D "_X86_" /D "x86" /D "NDEBUG" /D "XWFEATURE_CE_EDITCOLORS" /D "MY_COLOR_SEL" /D "POINTER_SUPPORT" /D UNDER_CE=$(CEVersion) /D _WIN32_WCE=$(CEVersion) /D "$(CePlatform)" /D "UNICODE" /D "_UNICODE" /D "KEY_SUPPORT" /D "__LITTLE_ENDIAN" /D "NODE_CAN_4" /D "XWFEATURE_STANDALONE_ONLY" /D "XWFEATURE_SEARCHLIMIT" /D "FEATURE_TRAY_EDIT" /D "CEFEATURE_CANSCROLL" /YX /Gs8192 /GF /c +MTL=midl.exe +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o "NUL" /win32 +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 commctrl.lib coredll.lib $(CEx86Corelibc) aygshell.lib /nologo /base:"0x00010000" /stack:0x10000,0x1000 /entry:"WinMainCRTStartup" /nodefaultlib:"OLDNAMES.lib" /nodefaultlib:$(CENoDefaultLib) /subsystem:$(CESubsystem) /MACHINE:IX86 +# ADD LINK32 $(CEx86Corelibc) aygshell.lib commctrl.lib coredll.lib winsock.lib /nologo /base:"0x00010000" /stack:0x10000,0x1000 /entry:"WinMainCRTStartup" /nodefaultlib:"OLDNAMES.lib" /nodefaultlib:$(CENoDefaultLib) /out:"X86Rel/Crosswords.exe" /subsystem:$(CESubsystem) /MACHINE:IX86 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "X86Dbg" +# PROP BASE Intermediate_Dir "X86Dbg" +# PROP BASE CPU_ID "{D6518FF3-710F-11D3-99F2-00105A0DF099}" +# PROP BASE Platform_ID "{8A9A2F80-6887-11D3-842E-005004848CBA}" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "X86Dbg" +# PROP Intermediate_Dir "X86Dbg" +# PROP CPU_ID "{D6518FF3-710F-11D3-99F2-00105A0DF099}" +# PROP Platform_ID "{8A9A2F80-6887-11D3-842E-005004848CBA}" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d UNDER_CE=$(CEVersion) /d _WIN32_WCE=$(CEVersion) /d "UNICODE" /d "_UNICODE" /d "DEBUG" /d "$(CePlatform)" /d "_X86_" /d "x86" /d "_i386_" /r +# ADD RSC /l 0x409 /d "DEBUG" /d "_X86_" /d "x86" /d "_i386_" /d UNDER_CE=$(CEVersion) /d _WIN32_WCE=$(CEVersion) /d "UNICODE" /d "_UNICODE" /d "$(CePlatform)" /d "XWFEATURE_SEARCHLIMIT" /d "FEATURE_TRAY_EDIT" /d "XWFEATURE_STANDALONE_ONLY" /d "MY_COLOR_SEL" /d "CEFEATURE_CANSCROLL" /r +CPP=cl.exe +# ADD BASE CPP /nologo /W3 /Zi /Od /D "DEBUG" /D "_i386_" /D UNDER_CE=$(CEVersion) /D _WIN32_WCE=$(CEVersion) /D "$(CePlatform)" /D "i_386_" /D "UNICODE" /D "_UNICODE" /D "_X86_" /D "x86" /YX /Gs8192 /GF /c +# ADD CPP /nologo /W3 /Zi /Od /I "." /I "../common" /I "./" /D "DEBUG" /D "_i386_" /D "i_386_" /D "_X86_" /D "x86" /D "MY_COLOR_SEL" /D "POINTER_SUPPORT" /D UNDER_CE=$(CEVersion) /D _WIN32_WCE=$(CEVersion) /D "$(CePlatform)" /D "UNICODE" /D "_UNICODE" /D "KEY_SUPPORT" /D "__LITTLE_ENDIAN" /D "NODE_CAN_4" /D "XWFEATURE_STANDALONE_ONLY" /D "XWFEATURE_SEARCHLIMIT" /D "FEATURE_TRAY_EDIT" /D "CEFEATURE_CANSCROLL" /YX /Gs8192 /GF /c +MTL=midl.exe +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o "NUL" /win32 +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 commctrl.lib coredll.lib $(CEx86Corelibc) aygshell.lib /nologo /base:"0x00010000" /stack:0x10000,0x1000 /entry:"WinMainCRTStartup" /debug /nodefaultlib:"OLDNAMES.lib" /nodefaultlib:$(CENoDefaultLib) /subsystem:$(CESubsystem) /MACHINE:IX86 +# ADD LINK32 $(CEx86Corelibc) aygshell.lib commctrl.lib coredll.lib winsock.lib /nologo /base:"0x00010000" /stack:0x10000,0x1000 /entry:"WinMainCRTStartup" /debug /nodefaultlib:"OLDNAMES.lib" /nodefaultlib:$(CENoDefaultLib) /out:"X86Dbg/Crosswords.exe" /subsystem:$(CESubsystem) /MACHINE:IX86 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "ARMV4Dbg" +# PROP BASE Intermediate_Dir "ARMV4Dbg" +# PROP BASE CPU_ID "{ECBEA43D-CD7B-4852-AD55-D4227B5D624B}" +# PROP BASE Platform_ID "{8A9A2F80-6887-11D3-842E-005004848CBA}" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "ARMV4Dbg" +# PROP Intermediate_Dir "ARMV4Dbg" +# PROP CPU_ID "{ECBEA43D-CD7B-4852-AD55-D4227B5D624B}" +# PROP Platform_ID "{8A9A2F80-6887-11D3-842E-005004848CBA}" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d UNDER_CE=$(CEVersion) /d _WIN32_WCE=$(CEVersion) /d "DEBUG" /d "UNICODE" /d "_UNICODE" /d "$(CePlatform)" /d "ARM" /d "_ARM_" /d "ARMV4" /r +# ADD RSC /l 0x409 /d "DEBUG" /d "ARM" /d "_ARM_" /d "ARMV4" /d UNDER_CE=$(CEVersion) /d _WIN32_WCE=$(CEVersion) /d "UNICODE" /d "_UNICODE" /d "$(CePlatform)" /d "XWFEATURE_SEARCHLIMIT" /d "FEATURE_TRAY_EDIT" /d "XWFEATURE_STANDALONE_ONLY" /d "MY_COLOR_SEL" /d "CEFEATURE_CANSCROLL" /r +CPP=clarm.exe +# ADD BASE CPP /nologo /W3 /Zi /Od /D "DEBUG" /D "ARM" /D "_ARM_" /D "ARMV4" /D UNDER_CE=$(CEVersion) /D _WIN32_WCE=$(CEVersion) /D "$(CePlatform)" /D "UNICODE" /D "_UNICODE" /YX /M$(CECrtMTDebug) /c +# ADD CPP /nologo /W3 /Zi /Od /I "../common" /I "./" /D "DEBUG" /D "ARM" /D "_ARM_" /D "ARMV4" /D "MY_COLOR_SEL" /D "POINTER_SUPPORT" /D UNDER_CE=$(CEVersion) /D _WIN32_WCE=$(CEVersion) /D "$(CePlatform)" /D "UNICODE" /D "_UNICODE" /D "KEY_SUPPORT" /D "__LITTLE_ENDIAN" /D "NODE_CAN_4" /D "XWFEATURE_STANDALONE_ONLY" /D "XWFEATURE_SEARCHLIMIT" /D "FEATURE_TRAY_EDIT" /D "CEFEATURE_CANSCROLL" /YX /M$(CECrtMTDebug) /c +MTL=midl.exe +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o "NUL" /win32 +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 commctrl.lib coredll.lib /nologo /base:"0x00010000" /stack:0x10000,0x1000 /entry:"WinMainCRTStartup" /debug /nodefaultlib:"$(CENoDefaultLib)" /subsystem:$(CESubsystem) /align:"4096" /MACHINE:ARM +# ADD LINK32 commctrl.lib coredll.lib winsock.lib aygshell.lib /nologo /base:"0x00010000" /stack:0x10000,0x1000 /entry:"WinMainCRTStartup" /debug /nodefaultlib:"$(CENoDefaultLib)" /subsystem:$(CESubsystem) /align:"4096" /MACHINE:ARM + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "emulatorDbg" +# PROP BASE Intermediate_Dir "emulatorDbg" +# PROP BASE CPU_ID "{32E52003-403E-442D-BE48-DE10F8C6131D}" +# PROP BASE Platform_ID "{8A9A2F80-6887-11D3-842E-005004848CBA}" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "emulatorDbg" +# PROP Intermediate_Dir "emulatorDbg" +# PROP CPU_ID "{32E52003-403E-442D-BE48-DE10F8C6131D}" +# PROP Platform_ID "{8A9A2F80-6887-11D3-842E-005004848CBA}" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "$(CePlatform)" /d UNDER_CE=$(CEVersion) /d _WIN32_WCE=$(CEVersion) /d "UNICODE" /d "_UNICODE" /d "DEBUG" /d "_X86_" /d "x86" /d "_i386_" /r +# ADD RSC /l 0x409 /d "DEBUG" /d "_X86_" /d "x86" /d "_i386_" /d UNDER_CE=$(CEVersion) /d _WIN32_WCE=$(CEVersion) /d "UNICODE" /d "_UNICODE" /d "$(CePlatform)" /d "XWFEATURE_SEARCHLIMIT" /d "FEATURE_TRAY_EDIT" /d "XWFEATURE_STANDALONE_ONLY" /d "MY_COLOR_SEL" /d "CEFEATURE_CANSCROLL" /r +CPP=cl.exe +# ADD BASE CPP /nologo /W3 /Zi /Od /D "DEBUG" /D "_i386_" /D UNDER_CE=$(CEVersion) /D _WIN32_WCE=$(CEVersion) /D "$(CePlatform)" /D "i_386_" /D "UNICODE" /D "_UNICODE" /D "_X86_" /D "x86" /YX /Gs8192 /GF /c +# ADD CPP /nologo /W3 /Zi /Od /I "../common" /I "./" /D "DEBUG" /D "_i386_" /D "i_386_" /D "_X86_" /D "x86" /D "MY_COLOR_SEL" /D "POINTER_SUPPORT" /D UNDER_CE=$(CEVersion) /D _WIN32_WCE=$(CEVersion) /D "$(CePlatform)" /D "UNICODE" /D "_UNICODE" /D "KEY_SUPPORT" /D "__LITTLE_ENDIAN" /D "NODE_CAN_4" /D "XWFEATURE_STANDALONE_ONLY" /D "XWFEATURE_SEARCHLIMIT" /D "FEATURE_TRAY_EDIT" /D "CEFEATURE_CANSCROLL" /YX /Gs8192 /GF /c +MTL=midl.exe +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o "NUL" /win32 +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 commctrl.lib coredll.lib $(CEx86Corelibc) /nologo /base:"0x00010000" /stack:0x10000,0x1000 /entry:"WinMainCRTStartup" /debug /nodefaultlib:"OLDNAMES.lib" /nodefaultlib:$(CENoDefaultLib) /subsystem:$(CESubsystem) /MACHINE:IX86 +# ADD LINK32 $(CEx86Corelibc) commctrl.lib coredll.lib winsock.lib aygshell.lib /nologo /base:"0x00010000" /stack:0x10000,0x1000 /entry:"WinMainCRTStartup" /debug /nodefaultlib:"OLDNAMES.lib" /nodefaultlib:$(CENoDefaultLib) /subsystem:$(CESubsystem) /MACHINE:IX86 + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "ARMV4Dbg" +# PROP BASE Intermediate_Dir "ARMV4Dbg" +# PROP BASE CPU_ID "{ECBEA43D-CD7B-4852-AD55-D4227B5D624B}" +# PROP BASE Platform_ID "{8A9A2F80-6887-11D3-842E-005004848CBA}" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "ARMV4Rel" +# PROP Intermediate_Dir "ARMV4Rel" +# PROP CPU_ID "{ECBEA43D-CD7B-4852-AD55-D4227B5D624B}" +# PROP Platform_ID "{8A9A2F80-6887-11D3-842E-005004848CBA}" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d UNDER_CE=$(CEVersion) /d _WIN32_WCE=$(CEVersion) /d "DEBUG" /d "UNICODE" /d "_UNICODE" /d "$(CePlatform)" /d "ARM" /d "_ARM_" /d "ARMV4" /r +# ADD RSC /l 0x409 /d "DEBUG" /d "ARM" /d "_ARM_" /d "ARMV4" /d UNDER_CE=$(CEVersion) /d _WIN32_WCE=$(CEVersion) /d "UNICODE" /d "_UNICODE" /d "$(CePlatform)" /d "XWFEATURE_SEARCHLIMIT" /d "FEATURE_TRAY_EDIT" /d "XWFEATURE_STANDALONE_ONLY" /d "MY_COLOR_SEL" /d "CEFEATURE_CANSCROLL" /r +CPP=clarm.exe +# ADD BASE CPP /nologo /W3 /Zi /Od /D "DEBUG" /D "ARM" /D "_ARM_" /D "ARMV4" /D "MY_COLOR_SEL" /D "POINTER_SUPPORT" /D UNDER_CE=$(CEVersion) /D _WIN32_WCE=$(CEVersion) /D "$(CePlatform)" /D "UNICODE" /D "_UNICODE" /D "KEY_SUPPORT" /D "__LITTLE_ENDIAN" /D "NODE_CAN_4" /D "XWFEATURE_STANDALONE_ONLY" /YX /M$(CECrtMTDebug) /c +# ADD CPP /nologo /W3 /Zi /Od /I "." /I "../common" /D "ARM" /D "_ARM_" /D "ARMV4" /D "MY_COLOR_SEL" /D "POINTER_SUPPORT" /D UNDER_CE=$(CEVersion) /D _WIN32_WCE=$(CEVersion) /D "$(CePlatform)" /D "UNICODE" /D "_UNICODE" /D "KEY_SUPPORT" /D "__LITTLE_ENDIAN" /D "NODE_CAN_4" /D "XWFEATURE_STANDALONE_ONLY" /D "XWFEATURE_SEARCHLIMIT" /D "FEATURE_TRAY_EDIT" /D "CEFEATURE_CANSCROLL" /YX /M$(CECrtMTDebug) /c +MTL=midl.exe +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o "NUL" /win32 +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 commctrl.lib coredll.lib winsock.lib /nologo /base:"0x00010000" /stack:0x10000,0x1000 /entry:"WinMainCRTStartup" /debug /nodefaultlib:"$(CENoDefaultLib)" /subsystem:$(CESubsystem) /align:"4096" /MACHINE:ARM +# ADD LINK32 commctrl.lib coredll.lib winsock.lib aygshell.lib /nologo /base:"0x00010000" /stack:0x10000,0x1000 /entry:"WinMainCRTStartup" /debug /nodefaultlib:"$(CENoDefaultLib)" /subsystem:$(CESubsystem) /align:"4096" /MACHINE:ARM + +!ENDIF + +# Begin Target + +# Name "xwords - Win32 (WCE ARM) Release" +# Name "xwords - Win32 (WCE ARM) Debug" +# Name "xwords - Win32 (WCE x86) Release" +# Name "xwords - Win32 (WCE x86) Debug" +# Name "xwords - Win32 (WCE ARMV4) Debug" +# Name "xwords - Win32 (WCE emulator) Debug" +# Name "xwords - Win32 (WCE ARMV4) Release" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\common\board.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_BOARD=\ + "..\common\board.h"\ + "..\common\boardp.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_BOARD=\ + "..\common\board.h"\ + "..\common\boardp.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_BOARD=\ + "..\common\board.h"\ + "..\common\boardp.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_BOARD=\ + "..\common\board.h"\ + "..\common\boardp.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_BOARD=\ + "..\common\board.h"\ + "..\common\boardp.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_BOARD=\ + "..\common\board.h"\ + "..\common\boardp.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_BOARD=\ + "..\common\board.h"\ + "..\common\boardp.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ceaskpwd.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_CEASK=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceaskpwd.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_CEASK=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceaskpwd.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_CEASK=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceaskpwd.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_CEASK=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceaskpwd.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_CEASK=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceaskpwd.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + +NODEP_CPP_CEASK=\ + "..\..\..\..\..\..\..\..\evc4\wce420\POCKET PC 2003\Include\ARMV4\vibrate.h"\ + ".\inuserm.h"\ + ".\ipapi.h"\ + ".\ygshell.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_CEASK=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceaskpwd.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + +NODEP_CPP_CEASK=\ + "..\..\..\..\..\..\..\..\evc4\wce420\POCKET PC 2003\Include\emulator\vibrate.h"\ + ".\inuserm.h"\ + ".\ipapi.h"\ + ".\ygshell.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_CEASK=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceaskpwd.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + {$(INCLUDE)}"aygshell.h"\ + {$(INCLUDE)}"sipapi.h"\ + {$(INCLUDE)}"winuserm.h"\ + +NODEP_CPP_CEASK=\ + "..\..\..\..\..\..\..\..\Program_Files\Windows_CE_Tools\wce420\PPC_2003\Include\ARMV4\vibrate.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ceblank.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_CEBLA=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceblank.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_CEBLA=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceblank.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_CEBLA=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceblank.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_CEBLA=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceblank.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_CEBLA=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceblank.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + +NODEP_CPP_CEBLA=\ + "..\..\..\..\..\..\..\..\evc4\wce420\POCKET PC 2003\Include\ARMV4\vibrate.h"\ + ".\inuserm.h"\ + ".\ipapi.h"\ + ".\ygshell.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_CEBLA=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceblank.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + +NODEP_CPP_CEBLA=\ + "..\..\..\..\..\..\..\..\evc4\wce420\POCKET PC 2003\Include\emulator\vibrate.h"\ + ".\inuserm.h"\ + ".\ipapi.h"\ + ".\ygshell.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_CEBLA=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceblank.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + {$(INCLUDE)}"aygshell.h"\ + {$(INCLUDE)}"sipapi.h"\ + {$(INCLUDE)}"winuserm.h"\ + +NODEP_CPP_CEBLA=\ + "..\..\..\..\..\..\..\..\Program_Files\Windows_CE_Tools\wce420\PPC_2003\Include\ARMV4\vibrate.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ceclrsel.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_CECLR=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceclrsel.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_CECLR=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceclrsel.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_CECLR=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceclrsel.h"\ + ".\cemain.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_CECLR=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceclrsel.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_CECLR=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceclrsel.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + +NODEP_CPP_CECLR=\ + "..\..\..\..\..\..\..\..\evc4\wce420\POCKET PC 2003\Include\ARMV4\vibrate.h"\ + ".\inuserm.h"\ + ".\ipapi.h"\ + ".\ygshell.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_CECLR=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceclrsel.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + +NODEP_CPP_CECLR=\ + "..\..\..\..\..\..\..\..\evc4\wce420\POCKET PC 2003\Include\emulator\vibrate.h"\ + ".\inuserm.h"\ + ".\ipapi.h"\ + ".\ygshell.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_CECLR=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceclrsel.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + {$(INCLUDE)}"aygshell.h"\ + {$(INCLUDE)}"sipapi.h"\ + {$(INCLUDE)}"winuserm.h"\ + +NODEP_CPP_CECLR=\ + "..\..\..\..\..\..\..\..\Program_Files\Windows_CE_Tools\wce420\PPC_2003\Include\ARMV4\vibrate.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cedict.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_CEDIC=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\dictnryp.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\cedict.h"\ + ".\cemain.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_CEDIC=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\dictnryp.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\cedict.h"\ + ".\cemain.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_CEDIC=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\dictnryp.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\cedict.h"\ + ".\cemain.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_CEDIC=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\dictnryp.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\cedict.h"\ + ".\cemain.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_CEDIC=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\dictnryp.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\cedict.h"\ + ".\cemain.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + +NODEP_CPP_CEDIC=\ + "..\..\..\..\..\..\..\..\evc4\wce420\POCKET PC 2003\Include\ARMV4\vibrate.h"\ + ".\inuserm.h"\ + ".\ipapi.h"\ + ".\ygshell.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_CEDIC=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\dictnryp.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\cedict.h"\ + ".\cemain.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + +NODEP_CPP_CEDIC=\ + "..\..\..\..\..\..\..\..\evc4\wce420\POCKET PC 2003\Include\emulator\vibrate.h"\ + ".\inuserm.h"\ + ".\ipapi.h"\ + ".\ygshell.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_CEDIC=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\dictnryp.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\cedict.h"\ + ".\cemain.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + {$(INCLUDE)}"aygshell.h"\ + {$(INCLUDE)}"sipapi.h"\ + {$(INCLUDE)}"winuserm.h"\ + +NODEP_CPP_CEDIC=\ + "..\..\..\..\..\..\..\..\Program_Files\Windows_CE_Tools\wce420\PPC_2003\Include\ARMV4\vibrate.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cedraw.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_CEDRA=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cedefines.h"\ + ".\cedict.h"\ + ".\cemain.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_CEDRA=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cedefines.h"\ + ".\cedict.h"\ + ".\cemain.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_CEDRA=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cedefines.h"\ + ".\cedict.h"\ + ".\cemain.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_CEDRA=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cedefines.h"\ + ".\cedict.h"\ + ".\cemain.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_CEDRA=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cedefines.h"\ + ".\cedict.h"\ + ".\cemain.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + +NODEP_CPP_CEDRA=\ + "..\..\..\..\..\..\..\..\evc4\wce420\POCKET PC 2003\Include\ARMV4\vibrate.h"\ + ".\inuserm.h"\ + ".\ipapi.h"\ + ".\ygshell.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_CEDRA=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cedefines.h"\ + ".\cedict.h"\ + ".\cemain.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + +NODEP_CPP_CEDRA=\ + "..\..\..\..\..\..\..\..\evc4\wce420\POCKET PC 2003\Include\emulator\vibrate.h"\ + ".\inuserm.h"\ + ".\ipapi.h"\ + ".\ygshell.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_CEDRA=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cedefines.h"\ + ".\cedict.h"\ + ".\cemain.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + {$(INCLUDE)}"aygshell.h"\ + {$(INCLUDE)}"sipapi.h"\ + {$(INCLUDE)}"winuserm.h"\ + +NODEP_CPP_CEDRA=\ + "..\..\..\..\..\..\..\..\Program_Files\Windows_CE_Tools\wce420\PPC_2003\Include\ARMV4\vibrate.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ceginfo.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_CEGIN=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\cedict.h"\ + ".\ceginfo.h"\ + ".\cemain.h"\ + ".\ceprefs.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_CEGIN=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\cedict.h"\ + ".\ceginfo.h"\ + ".\cemain.h"\ + ".\ceprefs.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_CEGIN=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\cedict.h"\ + ".\ceginfo.h"\ + ".\cemain.h"\ + ".\ceprefs.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_CEGIN=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\cedict.h"\ + ".\ceginfo.h"\ + ".\cemain.h"\ + ".\ceprefs.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_CEGIN=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\cedict.h"\ + ".\ceginfo.h"\ + ".\cemain.h"\ + ".\ceprefs.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + +NODEP_CPP_CEGIN=\ + "..\..\..\..\..\..\..\..\evc4\wce420\POCKET PC 2003\Include\ARMV4\vibrate.h"\ + ".\inuserm.h"\ + ".\ipapi.h"\ + ".\ygshell.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_CEGIN=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\cedict.h"\ + ".\ceginfo.h"\ + ".\cemain.h"\ + ".\ceprefs.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + +NODEP_CPP_CEGIN=\ + "..\..\..\..\..\..\..\..\evc4\wce420\POCKET PC 2003\Include\emulator\vibrate.h"\ + ".\inuserm.h"\ + ".\ipapi.h"\ + ".\ygshell.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_CEGIN=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\cedict.h"\ + ".\ceginfo.h"\ + ".\cemain.h"\ + ".\ceprefs.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + {$(INCLUDE)}"aygshell.h"\ + {$(INCLUDE)}"sipapi.h"\ + {$(INCLUDE)}"winuserm.h"\ + +NODEP_CPP_CEGIN=\ + "..\..\..\..\..\..\..\..\Program_Files\Windows_CE_Tools\wce420\PPC_2003\Include\ARMV4\vibrate.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cehntlim.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_CEHNT=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cehntlim.h"\ + ".\cemain.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_CEHNT=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cehntlim.h"\ + ".\cemain.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_CEHNT=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cehntlim.h"\ + ".\cemain.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_CEHNT=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cehntlim.h"\ + ".\cemain.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_CEHNT=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cehntlim.h"\ + ".\cemain.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + +NODEP_CPP_CEHNT=\ + "..\..\..\..\..\..\..\..\evc4\wce420\POCKET PC 2003\Include\ARMV4\vibrate.h"\ + ".\inuserm.h"\ + ".\ipapi.h"\ + ".\ygshell.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_CEHNT=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cehntlim.h"\ + ".\cemain.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + +NODEP_CPP_CEHNT=\ + "..\..\..\..\..\..\..\..\evc4\wce420\POCKET PC 2003\Include\emulator\vibrate.h"\ + ".\inuserm.h"\ + ".\ipapi.h"\ + ".\ygshell.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_CEHNT=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cehntlim.h"\ + ".\cemain.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + {$(INCLUDE)}"aygshell.h"\ + {$(INCLUDE)}"sipapi.h"\ + {$(INCLUDE)}"winuserm.h"\ + +NODEP_CPP_CEHNT=\ + "..\..\..\..\..\..\..\..\Program_Files\Windows_CE_Tools\wce420\PPC_2003\Include\ARMV4\vibrate.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cemain.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_CEMAI=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\ceaskpwd.h"\ + ".\ceblank.h"\ + ".\ceclrsel.h"\ + ".\cedefines.h"\ + ".\cedict.h"\ + ".\ceginfo.h"\ + ".\cehntlim.h"\ + ".\ceir.h"\ + ".\cemain.h"\ + ".\ceprefs.h"\ + ".\cestrbx.h"\ + ".\ceutil.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_CEMAI=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\ceaskpwd.h"\ + ".\ceblank.h"\ + ".\ceclrsel.h"\ + ".\cedefines.h"\ + ".\cedict.h"\ + ".\ceginfo.h"\ + ".\ceir.h"\ + ".\cemain.h"\ + ".\ceprefs.h"\ + ".\cestrbx.h"\ + ".\ceutil.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_CEMAI=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\ceaskpwd.h"\ + ".\ceblank.h"\ + ".\cedefines.h"\ + ".\cedict.h"\ + ".\ceginfo.h"\ + ".\ceir.h"\ + ".\cemain.h"\ + ".\ceprefs.h"\ + ".\cestrbx.h"\ + ".\ceutil.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_CEMAI=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\ceaskpwd.h"\ + ".\ceblank.h"\ + ".\ceclrsel.h"\ + ".\cedefines.h"\ + ".\cedict.h"\ + ".\ceginfo.h"\ + ".\cehntlim.h"\ + ".\ceir.h"\ + ".\cemain.h"\ + ".\ceprefs.h"\ + ".\cestrbx.h"\ + ".\ceutil.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_CEMAI=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\ceaskpwd.h"\ + ".\ceblank.h"\ + ".\ceclrsel.h"\ + ".\cedefines.h"\ + ".\cedict.h"\ + ".\ceginfo.h"\ + ".\cehntlim.h"\ + ".\ceir.h"\ + ".\cemain.h"\ + ".\ceprefs.h"\ + ".\cestrbx.h"\ + ".\ceutil.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + +NODEP_CPP_CEMAI=\ + "..\..\..\..\..\..\..\..\evc4\wce420\POCKET PC 2003\Include\ARMV4\vibrate.h"\ + ".\inuserm.h"\ + ".\ipapi.h"\ + ".\ygshell.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_CEMAI=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\ceaskpwd.h"\ + ".\ceblank.h"\ + ".\ceclrsel.h"\ + ".\cedefines.h"\ + ".\cedict.h"\ + ".\ceginfo.h"\ + ".\cehntlim.h"\ + ".\ceir.h"\ + ".\cemain.h"\ + ".\ceprefs.h"\ + ".\cestrbx.h"\ + ".\ceutil.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + +NODEP_CPP_CEMAI=\ + "..\..\..\..\..\..\..\..\evc4\wce420\POCKET PC 2003\Include\emulator\vibrate.h"\ + ".\inuserm.h"\ + ".\ipapi.h"\ + ".\ygshell.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_CEMAI=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\ceaskpwd.h"\ + ".\ceblank.h"\ + ".\ceclrsel.h"\ + ".\cedefines.h"\ + ".\cedict.h"\ + ".\ceginfo.h"\ + ".\cehntlim.h"\ + ".\ceir.h"\ + ".\cemain.h"\ + ".\ceprefs.h"\ + ".\cestrbx.h"\ + ".\ceutil.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + {$(INCLUDE)}"aygshell.h"\ + {$(INCLUDE)}"sipapi.h"\ + {$(INCLUDE)}"winuserm.h"\ + +NODEP_CPP_CEMAI=\ + "..\..\..\..\..\..\..\..\Program_Files\Windows_CE_Tools\wce420\PPC_2003\Include\ARMV4\vibrate.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ceprefs.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_CEPRE=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceclrsel.h"\ + ".\cemain.h"\ + ".\ceprefs.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_CEPRE=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cemain.h"\ + ".\ceprefs.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_CEPRE=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cemain.h"\ + ".\ceprefs.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_CEPRE=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceclrsel.h"\ + ".\cemain.h"\ + ".\ceprefs.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_CEPRE=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceclrsel.h"\ + ".\cemain.h"\ + ".\ceprefs.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + +NODEP_CPP_CEPRE=\ + "..\..\..\..\..\..\..\..\evc4\wce420\POCKET PC 2003\Include\ARMV4\vibrate.h"\ + ".\inuserm.h"\ + ".\ipapi.h"\ + ".\ygshell.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_CEPRE=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceclrsel.h"\ + ".\cemain.h"\ + ".\ceprefs.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + +NODEP_CPP_CEPRE=\ + "..\..\..\..\..\..\..\..\evc4\wce420\POCKET PC 2003\Include\emulator\vibrate.h"\ + ".\inuserm.h"\ + ".\ipapi.h"\ + ".\ygshell.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_CEPRE=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\ceclrsel.h"\ + ".\cemain.h"\ + ".\ceprefs.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + {$(INCLUDE)}"aygshell.h"\ + {$(INCLUDE)}"sipapi.h"\ + {$(INCLUDE)}"winuserm.h"\ + +NODEP_CPP_CEPRE=\ + "..\..\..\..\..\..\..\..\Program_Files\Windows_CE_Tools\wce420\PPC_2003\Include\ARMV4\vibrate.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cestrbx.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_CESTR=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cemain.h"\ + ".\cestrbx.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_CESTR=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cemain.h"\ + ".\cestrbx.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_CESTR=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cemain.h"\ + ".\cestrbx.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_CESTR=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cemain.h"\ + ".\cestrbx.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_CESTR=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cemain.h"\ + ".\cestrbx.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + +NODEP_CPP_CESTR=\ + "..\..\..\..\..\..\..\..\evc4\wce420\POCKET PC 2003\Include\ARMV4\vibrate.h"\ + ".\inuserm.h"\ + ".\ipapi.h"\ + ".\ygshell.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_CESTR=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cemain.h"\ + ".\cestrbx.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + +NODEP_CPP_CESTR=\ + "..\..\..\..\..\..\..\..\evc4\wce420\POCKET PC 2003\Include\emulator\vibrate.h"\ + ".\inuserm.h"\ + ".\ipapi.h"\ + ".\ygshell.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_CESTR=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cemain.h"\ + ".\cestrbx.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + {$(INCLUDE)}"aygshell.h"\ + {$(INCLUDE)}"sipapi.h"\ + {$(INCLUDE)}"winuserm.h"\ + +NODEP_CPP_CESTR=\ + "..\..\..\..\..\..\..\..\Program_Files\Windows_CE_Tools\wce420\PPC_2003\Include\ARMV4\vibrate.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ceutil.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_CEUTI=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_CEUTI=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_CEUTI=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_CEUTI=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_CEUTI=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + +NODEP_CPP_CEUTI=\ + "..\..\..\..\..\..\..\..\evc4\wce420\POCKET PC 2003\Include\ARMV4\vibrate.h"\ + ".\inuserm.h"\ + ".\ipapi.h"\ + ".\ygshell.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_CEUTI=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + +NODEP_CPP_CEUTI=\ + "..\..\..\..\..\..\..\..\evc4\wce420\POCKET PC 2003\Include\emulator\vibrate.h"\ + ".\inuserm.h"\ + ".\ipapi.h"\ + ".\ygshell.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_CEUTI=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\cemain.h"\ + ".\ceutil.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + {$(INCLUDE)}"aygshell.h"\ + {$(INCLUDE)}"sipapi.h"\ + {$(INCLUDE)}"winuserm.h"\ + +NODEP_CPP_CEUTI=\ + "..\..\..\..\..\..\..\..\Program_Files\Windows_CE_Tools\wce420\PPC_2003\Include\ARMV4\vibrate.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\common\comms.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_COMMS=\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_COMMS=\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_COMMS=\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_COMMS=\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_COMMS=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_COMMS=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_COMMS=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\common\dictnry.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_DICTN=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\dictnryp.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_DICTN=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\dictnryp.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_DICTN=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\dictnryp.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_DICTN=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\dictnryp.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_DICTN=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\dictnryp.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_DICTN=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\dictnryp.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_DICTN=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\dictnryp.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\common\draw.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_DRAW_=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_DRAW_=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_DRAW_=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_DRAW_=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_DRAW_=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_DRAW_=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_DRAW_=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\common\engine.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_ENGIN=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\engine.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_ENGIN=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\engine.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_ENGIN=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\engine.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_ENGIN=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\engine.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_ENGIN=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\engine.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_ENGIN=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\engine.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_ENGIN=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\engine.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\common\game.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_GAME_=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_GAME_=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_GAME_=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_GAME_=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_GAME_=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_GAME_=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_GAME_=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\common\mempool.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_MEMPO=\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_MEMPO=\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_MEMPO=\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_MEMPO=\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_MEMPO=\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_MEMPO=\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_MEMPO=\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\common\memstream.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_MEMST=\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_MEMST=\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_MEMST=\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_MEMST=\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_MEMST=\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_MEMST=\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_MEMST=\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\common\model.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_MODEL=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\modelp.h"\ + "..\common\movestak.h"\ + "..\common\pool.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_MODEL=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\modelp.h"\ + "..\common\movestak.h"\ + "..\common\pool.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_MODEL=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\modelp.h"\ + "..\common\movestak.h"\ + "..\common\pool.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_MODEL=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\modelp.h"\ + "..\common\movestak.h"\ + "..\common\pool.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_MODEL=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\modelp.h"\ + "..\common\movestak.h"\ + "..\common\pool.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_MODEL=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\modelp.h"\ + "..\common\movestak.h"\ + "..\common\pool.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_MODEL=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\modelp.h"\ + "..\common\movestak.h"\ + "..\common\pool.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\common\movestak.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_MOVES=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\movestak.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_MOVES=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\movestak.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_MOVES=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\movestak.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_MOVES=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\movestak.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_MOVES=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\movestak.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_MOVES=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\movestak.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_MOVES=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\movestak.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\common\mscore.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_MSCOR=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\modelp.h"\ + "..\common\movestak.h"\ + "..\common\server.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_MSCOR=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\modelp.h"\ + "..\common\movestak.h"\ + "..\common\server.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_MSCOR=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\modelp.h"\ + "..\common\movestak.h"\ + "..\common\server.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_MSCOR=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\modelp.h"\ + "..\common\movestak.h"\ + "..\common\server.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_MSCOR=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\modelp.h"\ + "..\common\movestak.h"\ + "..\common\server.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_MSCOR=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\modelp.h"\ + "..\common\movestak.h"\ + "..\common\server.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_MSCOR=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\modelp.h"\ + "..\common\movestak.h"\ + "..\common\server.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\common\pool.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_POOL_=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\pool.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_POOL_=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\pool.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_POOL_=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\pool.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_POOL_=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\pool.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_POOL_=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\pool.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_POOL_=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\pool.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_POOL_=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\pool.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\common\server.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_SERVE=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\pool.h"\ + "..\common\server.h"\ + "..\common\states.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwproto.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_SERVE=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\pool.h"\ + "..\common\server.h"\ + "..\common\states.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwproto.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_SERVE=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\pool.h"\ + "..\common\server.h"\ + "..\common\states.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwproto.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_SERVE=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\pool.h"\ + "..\common\server.h"\ + "..\common\states.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwproto.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_SERVE=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\pool.h"\ + "..\common\server.h"\ + "..\common\states.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwproto.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_SERVE=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\pool.h"\ + "..\common\server.h"\ + "..\common\states.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwproto.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_SERVE=\ + "..\common\board.h"\ + "..\common\commmgr.h"\ + "..\common\comms.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\game.h"\ + "..\common\mempool.h"\ + "..\common\memstream.h"\ + "..\common\model.h"\ + "..\common\pool.h"\ + "..\common\server.h"\ + "..\common\states.h"\ + "..\common\strutils.h"\ + "..\common\vtabmgr.h"\ + "..\common\xwproto.h"\ + "..\common\xwstream.h"\ + ".\LocalizedStrIncludes.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.cpp + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_STDAF=\ + ".\stdafx.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_STDAF=\ + ".\stdafx.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_STDAF=\ + ".\stdafx.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_STDAF=\ + ".\stdafx.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_STDAF=\ + ".\stdafx.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_STDAF=\ + ".\stdafx.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_STDAF=\ + ".\stdafx.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\common\strutils.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_STRUT=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_STRUT=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_STRUT=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_STRUT=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_STRUT=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_STRUT=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_STRUT=\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\strutils.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\common\tray.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_TRAY_=\ + "..\common\board.h"\ + "..\common\boardp.h"\ + "..\common\commmgr.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_TRAY_=\ + "..\common\board.h"\ + "..\common\boardp.h"\ + "..\common\commmgr.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_TRAY_=\ + "..\common\board.h"\ + "..\common\boardp.h"\ + "..\common\commmgr.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_TRAY_=\ + "..\common\board.h"\ + "..\common\boardp.h"\ + "..\common\commmgr.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_TRAY_=\ + "..\common\board.h"\ + "..\common\boardp.h"\ + "..\common\commmgr.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_TRAY_=\ + "..\common\board.h"\ + "..\common\boardp.h"\ + "..\common\commmgr.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_TRAY_=\ + "..\common\board.h"\ + "..\common\boardp.h"\ + "..\common\commmgr.h"\ + "..\common\comtypes.h"\ + "..\common\dawg.h"\ + "..\common\dictnry.h"\ + "..\common\draw.h"\ + "..\common\engine.h"\ + "..\common\mempool.h"\ + "..\common\model.h"\ + "..\common\server.h"\ + "..\common\xwstream.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\common\vtabmgr.c + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +DEP_CPP_VTABM=\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\vtabmgr.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +DEP_CPP_VTABM=\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\vtabmgr.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +DEP_CPP_VTABM=\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\vtabmgr.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +DEP_CPP_VTABM=\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\vtabmgr.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +DEP_CPP_VTABM=\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\vtabmgr.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +DEP_CPP_VTABM=\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\vtabmgr.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +DEP_CPP_VTABM=\ + "..\common\comtypes.h"\ + "..\common\mempool.h"\ + "..\common\vtabmgr.h"\ + ".\stdafx.h"\ + ".\xptypes.h"\ + ".\xwords4.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\xwords4.rc + +!IF "$(CFG)" == "xwords - Win32 (WCE ARM) Release" + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARM) Debug" + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Release" + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE x86) Debug" + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Debug" + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE emulator) Debug" + +!ELSEIF "$(CFG)" == "xwords - Win32 (WCE ARMV4) Release" + +!ENDIF + +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE=.\bmps\xwords4.ico +# End Source File +# End Group +# End Target +# End Project diff --git a/xwords4/wince/xwords4.h b/xwords4/wince/xwords4.h new file mode 100755 index 000000000..562e0b69f --- /dev/null +++ b/xwords4/wince/xwords4.h @@ -0,0 +1,11 @@ +#if !defined(AFX_XWORDS4_H__3DCA3EAB_CFD4_4487_BE8C_E563D65B4008__INCLUDED_) +#define AFX_XWORDS4_H__3DCA3EAB_CFD4_4487_BE8C_E563D65B4008__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include "resource.h" + + +#endif // !defined(AFX_XWORDS4_H__3DCA3EAB_CFD4_4487_BE8C_E563D65B4008__INCLUDED_) diff --git a/xwords4/wince/xwords4.ico b/xwords4/wince/xwords4.ico new file mode 100644 index 0000000000000000000000000000000000000000..5dc8fc1d0258a5250ad2b331ce8a365b3743fd34 GIT binary patch literal 1550 zcmchXu}%Xq42GSw5(`6a=^SN19gw*~20FXP?ss_dWQh zIk8h6(sU9H&PU~bomV2i?!DPQR{SZL@)c*4ohEc(Gf>9m{w14BnQ~;Shg!$yUH8O~ z`2KU7nv;LY*2-~hEiitDer;1rJ~mRzR%jFb{5t}qERPL#j49$UPea5+_&uaNcm6{js)QyGDdD#xti zyx1g>wCJJgJLNj4#9NPEcgBz77wV}Wm234b@tXUc-+qhj0K>b5;g#oe1^TvVS@U0M K#Y4OnH^Ud+qjG`( literal 0 HcmV?d00001 diff --git a/xwords4/wince/xwords4.rc b/xwords4/wince/xwords4.rc new file mode 100755 index 000000000..34c4a0b11 --- /dev/null +++ b/xwords4/wince/xwords4.rc @@ -0,0 +1,969 @@ +// -*- mode: c; compile-command: "make TARGET_OS=wince DEBUG=TRUE"; -*- +// +// Microsoft Developer Studio generated resource script. But now I'm +// editing it. :-) +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "newres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +#include "winnt.h" +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_XWORDS4 ICON DISCARDABLE "xwords4.ico" + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""winnt.h""\r\n" + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""newres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Menubar +// + +IDM_MENU MENU DISCARDABLE +BEGIN +#ifndef _WIN32_WCE + POPUP "Button" + BEGIN + MENUITEM "--", W32_DUMMY_ID + END +#endif + POPUP "Menu" + BEGIN + MENUITEM "Turn done", ID_MOVE_TURNDONE + MENUITEM "Juggle", ID_MOVE_JUGGLE + MENUITEM "Flip", ID_MOVE_FLIP + MENUITEM "Trade", ID_MOVE_TRADE + MENUITEM "Hide tray", ID_MOVE_HIDETRAY + + POPUP "Undo" + BEGIN + MENUITEM "Undo current", ID_MOVE_UNDOCURRENT + MENUITEM "Undo last", ID_MOVE_UNDOLAST + END + + POPUP "Hint" + BEGIN + MENUITEM "Next hint", ID_MOVE_NEXTHINT + MENUITEM "Hint", ID_MOVE_HINT +#ifdef XWFEATURE_SEARCHLIMIT + MENUITEM "Limited hint...", ID_MOVE_LIMITEDHINT +#endif + MENUITEM SEPARATOR + MENUITEM "Show values", ID_MOVE_VALUES + END + + POPUP "Game" + BEGIN + MENUITEM "Tile counts and values", ID_GAME_TILECOUNTSANDVALUES + MENUITEM "Tiles left", ID_GAME_TILESLEFT + MENUITEM SEPARATOR + MENUITEM "Game info", ID_GAME_GAMEINFO + MENUITEM "History", ID_GAME_HISTORY + MENUITEM "Final scores", ID_GAME_FINALSCORES +#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH + MENUITEM "Resend messages", ID_GAME_RESENDMSGS +#endif + END + + POPUP "File" + BEGIN + MENUITEM "Full screen", ID_FILE_FULLSCREEN + MENUITEM SEPARATOR + MENUITEM "New game...", ID_FILE_NEWGAME + MENUITEM "Saved games...", ID_FILE_SAVEDGAMES + MENUITEM "Preferences...", ID_FILE_PREFERENCES + MENUITEM SEPARATOR + MENUITEM "About...", ID_FILE_ABOUT + MENUITEM SEPARATOR + MENUITEM "Exit", ID_FILE_EXIT + END + END +END + +#ifdef _WIN32_WCE +// soft key bar described at +// http://msdn2.microsoft.com/en-us/library/aa457781.aspx +IDM_MAIN_MENUBAR RCDATA +BEGIN + IDM_MENU, 2, + I_IMAGENONE, ID_INITIAL_SOFTID, TBSTATE_ENABLED, + TBSTYLE_BUTTON | TBSTYLE_AUTOSIZE, IDS_DUMMY, 0, + NOMENU, + I_IMAGENONE, IDM_MENU, TBSTATE_ENABLED, + TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, IDS_MENU, 0, + 0 // Use the 0th popup above -- the only one +END + +IDM_OKCANCEL_MENUBAR RCDATA +BEGIN + 0, 2, + I_IMAGENONE, IDOK, TBSTATE_ENABLED, + TBSTYLE_BUTTON | TBSTYLE_AUTOSIZE, IDS_OK, 0, + NOMENU, + I_IMAGENONE, IDCANCEL, TBSTATE_ENABLED, + TBSTYLE_BUTTON | TBSTYLE_AUTOSIZE, IDS_CANCEL, 0, + NOMENU +END + +IDM_OK_MENUBAR RCDATA +BEGIN + 0, 1, + I_IMAGENONE, IDOK, TBSTATE_ENABLED, + TBSTYLE_BUTTON | TBSTYLE_AUTOSIZE, IDS_OK, 0, + NOMENU +END + +IDM_DONE_MENUBAR RCDATA +BEGIN + 0, 1, + I_IMAGENONE, IDOK, TBSTATE_ENABLED, + TBSTYLE_BUTTON | TBSTYLE_AUTOSIZE, IDS_DONE, 0, + NOMENU +END +#endif + +#ifdef _WIN32_WCE +# define UDS_EXPANDABLE 0x0200 +# define UDS_NOSCROLL 0x0400 +# define LISTBOX_CONTROL_FLAGS \ + NOT LBS_NOTIFY | LBS_NOINTEGRALHEIGHT | NOT WS_BORDER | WS_TABSTOP +# define SPINNER_CONTROL_FLAGS \ + UDS_AUTOBUDDY | UDS_HORZ | UDS_ALIGNRIGHT | UDS_ARROWKEYS |\ + UDS_SETBUDDYINT | UDS_EXPANDABLE +#endif + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// +#define BUTTON_HT 12 +#define REPOS_BUTTON_HT BUTTON_HT +#define REPOS_BUTTON_WIDTH 28 +#define REPOS_BUTTON_VPAD 2 +#define REPOS_BUTTON_HPAD 2 + +// ******** IDD_GAMEINFO DIALOG ********* +#ifdef CEFEATURE_CANSCROLL +# define ROW_HEIGHT 10 +# define ROW_SPACE 12 +# define ROW_SPACE_PL 11 +#else +# define ROW_HEIGHT 12 +# define ROW_SPACE 15 +# define ROW_SPACE_PL ROW_SPACE +#endif +#define CHECK_WIDTH 10 +#define GAME_NAME_WIDTH 56 +#define LEFT_COL 2 +#ifdef XWFEATURE_STANDALONE_ONLY +# define GAME_NAME_LEFT LEFT_COL +# define GAME_ROBOT_LEFT (GAME_NAME_LEFT + GAME_NAME_WIDTH + 5) +# define GAME_PWD_LEFT (GAME_ROBOT_LEFT + 15) +# define NPLAYERS_ROW 3 +# define GAME_NAMELABEL_LEFT GAME_NAME_LEFT +# define GAME_ROBOTLABEL_LEFT GAME_ROBOT_LEFT +# define GAME_PWDLABEL_LEFT GAME_PWD_LEFT +#else +# define SERVERROLE_ROW 3 +# define NPLAYERS_ROW (SERVERROLE_ROW+ROW_SPACE+3) +# define GAME_REMOTE_LEFT 2 +# define GAME_NAME_LEFT 15 +# define GAME_ROBOT_LEFT 92 +# define GAME_PWD_LEFT (GAME_ROBOT_LEFT + CHECK_WIDTH + 6) +# define GAME_NAMELABEL_LEFT (GAME_NAME_LEFT + 20) +# define GAME_ROBOTLABEL_LEFT 87 +# define GAME_PWDLABEL_LEFT 105 +#endif + + +#define LABELS_ROW (NPLAYERS_ROW+ROW_SPACE) +#define PLAYER_ROW_1 (LABELS_ROW+ROW_SPACE_PL) +#define PLAYER_ROW_2 (PLAYER_ROW_1+ROW_SPACE_PL) +#define PLAYER_ROW_3 (PLAYER_ROW_2+ROW_SPACE_PL) +#define PLAYER_ROW_4 (PLAYER_ROW_3+ROW_SPACE_PL) +#define DICTPICK_LAB_ROW (PLAYER_ROW_4+ROW_SPACE+2) +#define DICTPICK_ROW (DICTPICK_LAB_ROW+ROW_SPACE-2) +#define PREFS_ROW (DICTPICK_ROW+ROW_SPACE+3) +#ifndef _WIN32_WCE +# define BUTTONS_ROW (PREFS_ROW+ROW_SPACE+3) +#endif +#ifdef _WIN32_WCE +# define GAMEINFO_HEIGHT (PREFS_ROW + ROW_SPACE + 4) +#else +# define GAMEINFO_HEIGHT (BUTTONS_ROW + BUTTON_HT + 4) +#endif + + +/* in commctrl.h, but including isn't enough */ +#undef UPDOWN_CLASS +#define UPDOWN_CLASS "msctls_updown32" + +#define CE_PLAYER_ROW(nameEdit,playerRow,robotCheck,passEdit) \ + EDITTEXT nameEdit,GAME_NAME_LEFT,playerRow,GAME_NAME_WIDTH,ROW_HEIGHT, \ + ES_AUTOHSCROLL \ + CONTROL "",robotCheck,"Button",BS_AUTOCHECKBOX | WS_TABSTOP, \ + GAME_ROBOT_LEFT,playerRow,CHECK_WIDTH,ROW_HEIGHT \ + EDITTEXT passEdit,GAME_PWD_LEFT,playerRow,15,ROW_HEIGHT, \ + ES_PASSWORD | ES_AUTOHSCROLL \ + + +IDD_GAMEINFO DIALOG DISCARDABLE 0, 0, 116, GAMEINFO_HEIGHT +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | DS_CENTER | WS_VSCROLL +CAPTION "Game info" +FONT 8, "System" +BEGIN +#ifndef XWFEATURE_STANDALONE_ONLY + LTEXT "Role:",IDC_STATIC,25,SERVERROLE_ROW,20,8 + COMBOBOX IDC_ROLECOMBO,45,SERVERROLE_ROW,50,58,CBS_DROPDOWNLIST | + WS_VSCROLL | WS_TABSTOP + +#endif + + LTEXT "",IDC_TOTAL_LABEL,LEFT_COL,NPLAYERS_ROW,43,8 +#ifdef _WIN32_WCE + LISTBOX IDC_NPLAYERSCOMBO, 46, NPLAYERS_ROW, 24, ROW_HEIGHT, LISTBOX_CONTROL_FLAGS + CONTROL "", IDC_NPLAYERSUPDOWN, UPDOWN_CLASS, SPINNER_CONTROL_FLAGS, + 0, 0, 0, 0 +#endif + COMBOBOX IDC_NPLAYERSCOMBO_PPC,46,NPLAYERS_ROW,24,58,CBS_DROPDOWNLIST | + WS_VSCROLL | WS_TABSTOP + + PUSHBUTTON "Jugl.",GIJUGGLE_BUTTON,75,NPLAYERS_ROW,20,ROW_HEIGHT + + LTEXT "Name",IDC_STATIC,GAME_NAMELABEL_LEFT, + LABELS_ROW,GAME_NAME_WIDTH-10,8,SS_NOPREFIX + LTEXT "Robot",IDC_STATIC,GAME_ROBOTLABEL_LEFT-7,LABELS_ROW,22,8 + LTEXT "Pwd",IDC_STATIC,GAME_PWDLABEL_LEFT,LABELS_ROW,16,8 + +#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH + LTEXT "Remote",IDC_REMOTE_LABEL,LEFT_COL,LABELS_ROW,25,8,SS_NOPREFIX + CONTROL "",REMOTE_CHECK1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP, + GAME_REMOTE_LEFT, PLAYER_ROW_1,CHECK_WIDTH,ROW_HEIGHT + CONTROL "",REMOTE_CHECK2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP, + GAME_REMOTE_LEFT, PLAYER_ROW_2,CHECK_WIDTH,ROW_HEIGHT + CONTROL "",REMOTE_CHECK3,"Button",BS_AUTOCHECKBOX | WS_TABSTOP, + GAME_REMOTE_LEFT, PLAYER_ROW_3,CHECK_WIDTH,ROW_HEIGHT + CONTROL "",REMOTE_CHECK4,"Button",BS_AUTOCHECKBOX | WS_TABSTOP, + GAME_REMOTE_LEFT, PLAYER_ROW_4,CHECK_WIDTH,ROW_HEIGHT +#endif + CE_PLAYER_ROW( NAME_EDIT1, PLAYER_ROW_1, ROBOT_CHECK1, PASS_EDIT1 ) + CE_PLAYER_ROW( NAME_EDIT2, PLAYER_ROW_2, ROBOT_CHECK2, PASS_EDIT2 ) + CE_PLAYER_ROW( NAME_EDIT3, PLAYER_ROW_3, ROBOT_CHECK3, PASS_EDIT3 ) + CE_PLAYER_ROW( NAME_EDIT4, PLAYER_ROW_4, ROBOT_CHECK4, PASS_EDIT4 ) + + LTEXT "Dictionary:",IDC_DICTLABEL,LEFT_COL,DICTPICK_LAB_ROW,36,8, + SS_NOPREFIX +#ifdef _WIN32_WCE + LISTBOX IDC_DICTLIST, LEFT_COL+10,DICTPICK_ROW,80,ROW_HEIGHT,LISTBOX_CONTROL_FLAGS|LBS_SORT + CONTROL "", IDC_DICTUPDOWN, UPDOWN_CLASS, SPINNER_CONTROL_FLAGS, + 0, 0, 0, 0 +#endif + COMBOBOX IDC_DICTLIST_PPC,LEFT_COL+10,DICTPICK_ROW,80,58, + CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + + PUSHBUTTON "Preferences...",OPTIONS_BUTTON,LEFT_COL,PREFS_ROW,55,12 + +#ifndef _WIN32_WCE + PUSHBUTTON "OK",IDOK,29,BUTTONS_ROW,REPOS_BUTTON_WIDTH,REPOS_BUTTON_HT + DEFPUSHBUTTON "Cancel",IDCANCEL,70,BUTTONS_ROW, + REPOS_BUTTON_WIDTH,REPOS_BUTTON_HT +#endif +END + +#define SB_WIDTH 121 +#define SB_BUTTON_WIDTH 19 +#define SB_OK_LEFT ((SB_WIDTH/2)-10-SB_BUTTON_WIDTH) +#define SB_CANCEL_LEFT ((SB_WIDTH/2)+10) + +#ifdef _WIN32_WCE +# define STRBOX_HT 81 +#else +# define STRBOX_HT 97 +#endif + +IDD_STRBOX DIALOG DISCARDABLE 0, 25, SB_WIDTH, STRBOX_HT + STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | DS_CENTER | WS_VSCROLL +CAPTION "Dialog" +FONT 8, "System" +BEGIN + EDITTEXT ID_EDITTEXT,3,3,115,76,ES_MULTILINE | ES_READONLY + | WS_VSCROLL +#ifndef _WIN32_WCE + PUSHBUTTON "OK",IDOK, SB_OK_LEFT,81,SB_BUTTON_WIDTH,REPOS_BUTTON_HT + DEFPUSHBUTTON "No",IDCANCEL,SB_CANCEL_LEFT,81,SB_BUTTON_WIDTH,REPOS_BUTTON_HT +#endif +END + +#define ASKB_DLG_WIDTH 80 +#define ASKB_COLLEFT 5 +#define ASKB_LABELTOP 0 +#define ASKB_LABELHT 36 +#define ASKB_LABELWIDTH 40 +#define ASKB_COMBOLEFT (ASKB_COLLEFT+ASKB_LABELWIDTH) +#define ASKB_EDITTOP (ASKB_LABELTOP + ASKB_LABELHT) +#define ASKB_EDITHT 24 +#define ASKB_PUTTOP (ASKB_EDITTOP+ASKB_EDITHT) +#define WCE_BOTTOM (ASKB_PUTTOP + BUTTON_HT + 4) +#ifdef _WIN32_WCE +# define ASKB_DLG_HEIGHT WCE_BOTTOM +#else +# define ASKB_DLG_HEIGHT (WCE_BOTTOM + REPOS_BUTTON_HT + 4) +#endif + + +IDD_ASKBLANK DIALOG DISCARDABLE 0, 0, ASKB_DLG_WIDTH, ASKB_DLG_HEIGHT +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | DS_CENTER +CAPTION "Tile picker" +FONT 8, "System" +BEGIN + LTEXT "Enter a letter for this blank tile.",IDC_BPICK, + ASKB_COLLEFT,ASKB_LABELTOP,ASKB_LABELWIDTH,ASKB_LABELHT +#ifdef FEATURE_TRAY_EDIT + LTEXT "Pick a tile for your tray.",IDC_CPICK, + ASKB_COLLEFT,ASKB_LABELTOP,ASKB_LABELWIDTH,ASKB_LABELHT + EDITTEXT IDC_PICKMSG,ASKB_COLLEFT,ASKB_EDITTOP,80,ASKB_EDITHT, + ES_MULTILINE | ES_READONLY + + PUSHBUTTON "Put back",IDC_BACKUP,ASKB_COLLEFT,ASKB_PUTTOP,35,14 +#endif + +#ifdef _WIN32_WCE + LISTBOX BLANKFACE_LIST,ASKB_COMBOLEFT,ASKB_COLLEFT,27,12, + LISTBOX_CONTROL_FLAGS | LBS_SORT + CONTROL "", IDC_ASKBLANK_UPDOWN, UPDOWN_CLASS, SPINNER_CONTROL_FLAGS, + 0, 0, 0, 0 +#endif + COMBOBOX BLANKFACE_LIST_PPC,ASKB_COMBOLEFT,ASKB_COLLEFT,27,70, + CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP +#ifndef _WIN32_WCE + DEFPUSHBUTTON "OK",IDOK,80,WCE_BOTTOM,REPOS_BUTTON_WIDTH, BUTTON_HT + PUSHBUTTON "Cancel",IDCANCEL,20,WCE_BOTTOM,REPOS_BUTTON_WIDTH, BUTTON_HT +#endif +END + +#define SVGN_LEFT_COL 2 +#define SVGN_ROW_1 2 +#define SVGN_LTEXT_HT 46 +#define SVGN_ROW_2 (SVGN_ROW_1+SVGN_LTEXT_HT) +#define SVGN_ROW_3 (SVGN_ROW_2+18) +#ifdef _WIN32_WCE +# define SVGN_DLG_HT SVGN_ROW_3 +#else +# define SVGN_DLG_HT (SVGN_ROW_3+BUTTON_HT+3) +#endif + +IDD_SAVENAMEDLG DIALOG DISCARDABLE 0, 0, 90, SVGN_DLG_HT +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | DS_CENTER +CAPTION "Game name" +FONT 8, "System" +BEGIN + LTEXT "", + IDC_SVGN_SELLAB,SVGN_LEFT_COL,SVGN_ROW_1,86,SVGN_LTEXT_HT + EDITTEXT IDC_SVGN_EDIT,SVGN_LEFT_COL,SVGN_ROW_2,86,12, + ES_AUTOHSCROLL +#ifndef _WIN32_WCE + DEFPUSHBUTTON "OK",IDOK,SVGN_LEFT_COL+10,SVGN_ROW_3,REPOS_BUTTON_WIDTH, + BUTTON_HT + PUSHBUTTON "Cancel",IDCANCEL,SVGN_LEFT_COL+10+REPOS_BUTTON_WIDTH+10, + SVGN_ROW_3,REPOS_BUTTON_WIDTH, BUTTON_HT +#endif +END + +#define SVGM_LEFT_COL 2 +#define SVGM_ROW_1 2 +#define SVGM_ROW_2 (SVGM_ROW_1+35) +#define SVGM_ROW_3 (SVGM_ROW_2+13) +#define SVGM_ROW_4 (SVGM_ROW_3+19) +#ifdef _WIN32_WCE +/* # define SVGM_DLG_HT (SVGM_ROW_3) */ +# define SVGM_DLG_HT (SVGM_ROW_4+16) + +#else +# define SVGM_ROW_OK (SVGM_ROW_4 + 21) +# define SVGM_DLG_HT (SVGM_ROW_OK + 16) +#endif + + +IDD_SAVEDGAMESDLG DIALOG DISCARDABLE 0, 0, 85, SVGM_DLG_HT +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | DS_CENTER | WS_VSCROLL +CAPTION "Saved Games" +FONT 8, "System" +BEGIN + LTEXT /* wince windres can't handle split lines....*/ + "Select a saved game. (Some buttons will be disabled when the current game is selected.)", + IDC_SVGM_SELLAB,SVGM_LEFT_COL, + SVGM_ROW_1,90,35 +#ifdef _WIN32_WCE + LISTBOX IDC_SVGM_GAMELIST, SVGM_LEFT_COL,SVGM_ROW_2,70,ROW_HEIGHT, + LISTBOX_CONTROL_FLAGS | LBS_SORT + CONTROL "", IDC_SVGM_UPDOWN, UPDOWN_CLASS, SPINNER_CONTROL_FLAGS, + 0, 0, 0, 0 +#endif + COMBOBOX IDC_SVGM_GAMELIST_PPC,SVGM_LEFT_COL,SVGM_ROW_2,70,58, + CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP | CBS_SORT + + PUSHBUTTON "Open",IDC_SVGM_OPEN, + SVGM_LEFT_COL,SVGM_ROW_3,40,14,WS_DISABLED + + PUSHBUTTON "Dup.",IDC_SVGM_DUP, + SVGM_LEFT_COL,SVGM_ROW_4,20,14,WS_DISABLED + PUSHBUTTON "Delete",IDC_SVGM_DEL, + SVGM_LEFT_COL+22,SVGM_ROW_4,28,14,WS_DISABLED + PUSHBUTTON "Rename",IDC_SVGM_CHANGE, + SVGM_LEFT_COL+52,SVGM_ROW_4,32,14,WS_DISABLED + +#ifndef _WIN32_WCE + DEFPUSHBUTTON "Done",IDOK,SVGM_LEFT_COL,SVGM_ROW_OK,REPOS_BUTTON_WIDTH, + BUTTON_HT +#endif +END + +#define PW_WIDTH 104 +#define PW_OK_LEFT ((PW_WIDTH/2)-10-REPOS_BUTTON_WIDTH) +#define PW_CANCEL_LEFT ((PW_WIDTH/2)+10) + +#ifdef _WIN32_WCE +# define ASKPASS_HT 32 +#else +# define ASKPASS_HT 50 +#endif + +IDD_ASKPASS DIALOG DISCARDABLE 0, 0, PW_WIDTH, ASKPASS_HT +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | DS_CENTER +CAPTION "Password" +FONT 8, "System" +BEGIN + LTEXT "",IDC_PWDLABEL,5,8,67,24 + EDITTEXT PASS_EDIT,76,8,23,12,ES_PASSWORD | ES_AUTOHSCROLL +#ifndef _WIN32_WCE + DEFPUSHBUTTON "OK",IDOK,PW_OK_LEFT,32,REPOS_BUTTON_WIDTH,REPOS_BUTTON_HT + PUSHBUTTON "Cancel",IDCANCEL,PW_CANCEL_LEFT,32,REPOS_BUTTON_WIDTH,REPOS_BUTTON_HT +#endif +END + +#ifdef XWFEATURE_SEARCHLIMIT +#define HC_LABELS_COL 5 +#define HC_DROPDOWNS_COL 70 +#define HC_MINROW 3 +#define HC_MAXROW 16 +#define HC_WIDTH 98 +#define HC_OK_LEFT ((HC_WIDTH/2)-10-REPOS_BUTTON_WIDTH) +#define HC_CANCEL_LEFT ((HC_WIDTH/2)+10) + +#ifdef _WIN32_WCE +# define ASKHINTLIMTS_HT 31 +#else +# define ASKHINTLIMTS_HT 47 +#endif + +IDD_ASKHINTLIMTS DIALOG DISCARDABLE 0, 0, HC_WIDTH, ASKHINTLIMTS_HT +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | DS_CENTER +CAPTION "Tile hint limits" +FONT 8, "System" +BEGIN + LTEXT "Use at least:",IDC_STATIC,HC_LABELS_COL,HC_MINROW,60,ROW_HEIGHT +#ifdef _WIN32_WCE + LISTBOX HC_MIN_COMBO, HC_DROPDOWNS_COL,HC_MINROW, 24, ROW_HEIGHT, LISTBOX_CONTROL_FLAGS + CONTROL "", HC_MIN_UPDOWN, UPDOWN_CLASS, SPINNER_CONTROL_FLAGS, + 0, 0, 0, 0 +#endif + COMBOBOX HC_MIN_COMBO_PPC,HC_DROPDOWNS_COL,HC_MINROW,17,58, + CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + + LTEXT "But no more than:",IDC_STATIC,HC_LABELS_COL,HC_MAXROW,60,ROW_HEIGHT +#ifdef _WIN32_WCE + LISTBOX HC_MAX_COMBO, HC_DROPDOWNS_COL,HC_MAXROW, 24, ROW_HEIGHT, LISTBOX_CONTROL_FLAGS + CONTROL "", HC_MAX_UPDOWN, UPDOWN_CLASS, SPINNER_CONTROL_FLAGS, + 0, 0, 0, 0 +#endif + COMBOBOX HC_MAX_COMBO_PPC,HC_DROPDOWNS_COL,HC_MAXROW,17,58, + CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP +#ifndef _WIN32_WCE + DEFPUSHBUTTON "OK",IDOK,HC_OK_LEFT,31,REPOS_BUTTON_WIDTH,REPOS_BUTTON_HT + PUSHBUTTON "Cancel",IDCANCEL,HC_CANCEL_LEFT,31,REPOS_BUTTON_WIDTH, + REPOS_BUTTON_HT +#endif +END +#endif + +#define PR_WIDTH 118 +#define PR_OK_LEFT ((PR_WIDTH/2)-10-REPOS_BUTTON_WIDTH) +#define PR_CANCEL_LEFT ((PR_WIDTH/2)+10) +#define PREFS_ROW_HT 9 + +#define PR_SPACING 13 +#define PR_ROW1 5 +#define PR_ROW2 (PR_ROW1+PR_SPACING) +#define PR_ROW3 (PR_ROW2+PR_SPACING) +#define PR_ROW4 (PR_ROW3+PR_SPACING) +#define PR_ROW5 (PR_ROW4+PR_SPACING) +#define PR_ROW6 (PR_ROW5+PR_SPACING) +#define PR_ROW7 (PR_ROW6+PR_SPACING) +#define PR_BUTTONROW (PR_ROW7+PR_SPACING) + +#ifdef _WIN32_WCE +# define PREFS_DLG_HT PR_BUTTONROW +#else +# define PREFS_DLG_HT (PR_BUTTONROW+PR_SPACING+3) +#endif + +IDD_OPTIONSDLG DIALOG DISCARDABLE 0, 20, PR_WIDTH, PREFS_DLG_HT +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | DS_CENTER | WS_VSCROLL +CAPTION "Preferences" +FONT 8, "System" +BEGIN + CONTROL "This game",IDC_RADIOLOCAL,"Button", + BS_AUTORADIOBUTTON | WS_GROUP,4,PR_ROW1,47,10 + CONTROL "All games",IDC_RADIOGLOBAL,"Button", + BS_AUTORADIOBUTTON,52,PR_ROW1,53,10 + + /* Global */ + CONTROL "Color played tiles",IDC_CHECKCOLORPLAYED,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP | WS_GROUP, + 8,PR_ROW2,80,PREFS_ROW_HT + CONTROL "Enable cursor",IDC_CHECKSHOWCURSOR,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,8,PR_ROW3,80,PREFS_ROW_HT + CONTROL "Explain robot scores",IDC_CHECKROBOTSCORES,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,8,PR_ROW4,80,PREFS_ROW_HT + CONTROL "Hide tile values",IDC_HIDETILEVALUES,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,8,PR_ROW5,80,PREFS_ROW_HT + PUSHBUTTON "Edit colors...",IDC_PREFCOLORS,8,PR_ROW6,60,12 +#ifdef ALLOW_CHOOSE_FONTS + PUSHBUTTON "Choose font...",IDC_PREFFONTS,8,PR_ROW7,60,12 +#endif + + /* Per game */ + CONTROL "Smart robot",IDC_CHECKSMARTROBOT,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP | WS_GROUP,8,PR_ROW2,60, + PREFS_ROW_HT + CONTROL "Allow hints",IDC_CHECKHINTSOK,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,8,PR_ROW3,60,PREFS_ROW_HT +#ifdef XWFEATURE_SEARCHLIMIT + CONTROL "Hint limits",IDC_CHECKHINTSLIMITS,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,8+10,PR_ROW4-2,50,10 +#endif + CONTROL "Timer on (minutes)",TIMER_CHECK,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,8,PR_ROW5,75,PREFS_ROW_HT + EDITTEXT TIMER_EDIT,85,PR_ROW5,16,12,ES_AUTOHSCROLL | ES_NUMBER + LTEXT "Phonies:",PHONIES_LABEL,8,PR_ROW6,28,PREFS_ROW_HT +#ifdef _WIN32_WCE + LISTBOX PHONIES_COMBO, 38,PR_ROW6,50,PREFS_ROW_HT, LISTBOX_CONTROL_FLAGS + CONTROL "", IDC_PHONIESUPDOWN, UPDOWN_CLASS, SPINNER_CONTROL_FLAGS, + 0, 0, 0, 0 +#endif + COMBOBOX PHONIES_COMBO_PPC,38,PR_ROW6,40,58,CBS_DROPDOWNLIST | WS_VSCROLL | + WS_TABSTOP + +#ifdef FEATURE_TRAY_EDIT + CONTROL "Pick tiles face-up", IDC_PICKTILES, "Button", + BS_AUTOCHECKBOX | WS_TABSTOP,8,PR_ROW7,90,PREFS_ROW_HT +#else +#endif +#ifndef _WIN32_WCE + DEFPUSHBUTTON "OK",IDOK,PR_OK_LEFT,PR_BUTTONROW,REPOS_BUTTON_WIDTH,REPOS_BUTTON_HT + PUSHBUTTON "Cancel",IDCANCEL,PR_CANCEL_LEFT,PR_BUTTONROW, + REPOS_BUTTON_WIDTH,REPOS_BUTTON_HT +#endif +END + +#if defined XWFEATURE_RELAY || defined XWFEATURE_BLUETOOTH + +# define LAB_COL 8 +# define LAB_COL_WIDTH 40 +# define CTRL_COL 50 +# define CTRL_COL_WIDTH 60 +# define CONN_ROW_1 10 +# define CONN_ROW_2 25 +# define CONN_ROW_HINT 45 +# define CONN_ROW_3 57 +# define CONN_ROW_4 70 +# define BUTTON_ROW 98 + /* #This is a comment???? */ +IDD_CONNSSDLG DIALOG DISCARDABLE 0, 20, 120, 115 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | DS_CENTER +CAPTION "Connection" +FONT 8, "System" +BEGIN + + LTEXT "Connect via",IDC_CCONVIA_LAB,LAB_COL,CONN_ROW_1,40,12 + COMBOBOX IDC_CONNECTCOMBO,CTRL_COL,CONN_ROW_1,CTRL_COL_WIDTH,58,CBS_DROPDOWNLIST | + WS_VSCROLL | WS_TABSTOP + +#ifdef XWFEATURE_RELAY + LTEXT "Cookie",IDC_COOKIE_LAB,LAB_COL,CONN_ROW_2,40,12 + EDITTEXT COOKIE_EDIT,CTRL_COL,CONN_ROW_2,CTRL_COL_WIDTH,12, + ES_AUTOHSCROLL + + + CTEXT "(These will rarely change)",IDC_CRELAYHINT_LAB,0,CONN_ROW_HINT,120,12 + LTEXT "Relay name",IDC_CRELAYNAME_LAB,LAB_COL,CONN_ROW_3,40,12 + EDITTEXT RELAYNAME_EDIT,CTRL_COL,CONN_ROW_3,CTRL_COL_WIDTH,12, + ES_AUTOHSCROLL + LTEXT "Relay port",IDC_CRELAYPORT_LAB,LAB_COL,CONN_ROW_4,40,12 + EDITTEXT RELAYPORT_EDIT,CTRL_COL,CONN_ROW_4,CTRL_COL_WIDTH,12, + ES_AUTOHSCROLL | ES_NUMBER +#else + LTEXT "Relay connection not supported.",IDC_COOKIE_LAB,LAB_COL,CONN_ROW_2,40,12 +#endif + +#ifdef XWFEATURE_BLUETOOTH + LTEXT "Host name:", + IDC_BLUET_ADDR_LAB,LAB_COL,CONN_ROW_2,40,36 + EDITTEXT IDC_BLUET_ADDR_EDIT,CTRL_COL,CONN_ROW_2,CTRL_COL_WIDTH,12, + ES_AUTOHSCROLL + PUSHBUTTON "Browse",IDC_BLUET_ADDR_BROWSE,CTRL_COL,CONN_ROW_HINT, + REPOS_BUTTON_WIDTH,REPOS_BUTTON_HT + +#else + LTEXT "Bluetooth not supported.", + IDC_BLUET_ADDR_LAB,LAB_COL,CONN_ROW_2,40,12 +#endif +#ifndef _WIN32_WCE + PUSHBUTTON "OK",IDOK,9,BUTTON_ROW,REPOS_BUTTON_WIDTH,REPOS_BUTTON_HT + DEFPUSHBUTTON "Cancel",IDCANCEL,70,BUTTON_ROW, + REPOS_BUTTON_WIDTH,REPOS_BUTTON_HT +#endif +END +#endif + +#define CLR_WIDTH 114 +#define CLR_OK_LEFT ((CLR_WIDTH/2)-10-REPOS_BUTTON_WIDTH) +#define CLR_CANCEL_LEFT ((CLR_WIDTH/2)+10) +#define CLR_LAB_WIDTH 45 +#define CLR_SAMPLE_WIDTH 12 +#define CLR_LAB_HT 12 +#define CLR_BUT_WIDTH 12 +#define CLR_BUT_HT 10 + +#define CLR_SMALL_GAP 12 +#define CLR_LRG_GAP 16 + +#define CLR_ROW_1 4 +#define CLR_ROW_2 (CLR_ROW_1+CLR_SMALL_GAP) +#define CLR_ROW_3 (CLR_ROW_2+CLR_SMALL_GAP) +#define CLR_ROW_4 (CLR_ROW_3+CLR_SMALL_GAP) +#define CLR_ROW_5 (CLR_ROW_4+CLR_LRG_GAP) +#define CLR_ROW_6 (CLR_ROW_5+CLR_SMALL_GAP) +#define CLR_ROW_7 (CLR_ROW_6+CLR_SMALL_GAP) +#define CLR_ROW_8 (CLR_ROW_7+CLR_LRG_GAP) +#define CLR_ROW_9 (CLR_ROW_8+CLR_SMALL_GAP) +#define CLR_ROW_10 (CLR_ROW_9+CLR_SMALL_GAP) +#define CLR_ROW_11 (CLR_ROW_10+CLR_SMALL_GAP) + +#define CLR_BUTTON_ROW CLR_ROW_11 + CLR_LRG_GAP +#define CLR_COL_1 7 +#define CLR_COL_2 (CLR_COL_1 + CLR_LAB_WIDTH+2) +/* #define CLR_COL_3 58 */ +/* #define CLR_COL_4 99 */ +#define CLR_COL_3 CLR_COL_1 +#define CLR_COL_4 CLR_COL_2 +#ifdef _WIN32_WCE +# define COLORSDLG_HT CLR_BUTTON_ROW +#else +# define COLORSDLG_HT CLR_BUTTON_ROW + BUTTON_HT + 3 +#endif + +#define COLOR_BUTTON(txt,id,xx,yy) \ + LTEXT txt,id,xx,yy,CLR_LAB_WIDTH,CLR_LAB_HT + /* Hack alert. Smartphone isn't delivering WM_COMMAND events for + clicks on BS_OWNERDRAW buttons, and WinMo (PPC and Smartphone) + won't deliver WM_DRAWITEM events for SS_OWNERDRAW static + controls. The solution: use an OWNERDRAW button as the color + sample (must disable it in code; WS_DISABLED doesn't work here) + and have a separate button to trigger the edit dialog. */ +#define COLOR_SAMPLE(id1,id2,xx,yy) \ + PUSHBUTTON "",id2,xx,yy,CLR_BUT_WIDTH,CLR_BUT_HT,BS_OWNERDRAW \ + PUSHBUTTON "Edit",id1,xx+CLR_SAMPLE_WIDTH+4,yy,20,CLR_BUT_HT,BS_NOTIFY + +IDD_COLORSDLG DIALOG DISCARDABLE 0, 20, CLR_WIDTH, COLORSDLG_HT +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | DS_CENTER | WS_VSCROLL +CAPTION "Color preferences" +FONT 8, "System" +BEGIN + COLOR_BUTTON("Double letter:",DLBLTR_LABEL,CLR_COL_1,CLR_ROW_1) + COLOR_SAMPLE(DLBLTR_BUTTON,DLBLTR_SAMPLE,CLR_COL_2,CLR_ROW_1) + + COLOR_BUTTON("Double word:",DBLWRD_LABEL,CLR_COL_1,CLR_ROW_2 ) + COLOR_SAMPLE(DBLWRD_BUTTON,DBLWRD_SAMPLE,CLR_COL_2,CLR_ROW_2) + COLOR_BUTTON("Triple letter:",TPLLTR_LABEL,CLR_COL_1,CLR_ROW_3 ) + COLOR_SAMPLE(TPLLTR_BUTTON,TPLLTR_SAMPLE,CLR_COL_2,CLR_ROW_3) + + COLOR_BUTTON("Triple word:",TPLWRD_LABEL,CLR_COL_3,CLR_ROW_4) + COLOR_SAMPLE(TPLWRD_BUTTON,TPLWRD_SAMPLE,CLR_COL_4,CLR_ROW_4) + COLOR_BUTTON("Empty cell:",EMPCELL_LABEL,CLR_COL_1,CLR_ROW_5) + COLOR_SAMPLE(EMPCELL_BUTTON,EMPCELL_SAMPLE,CLR_COL_2,CLR_ROW_5) + COLOR_BUTTON("Tile back:",TBACK_LABEL,CLR_COL_3,CLR_ROW_6) + COLOR_SAMPLE(TBACK_BUTTON,TBACK_SAMPLE,CLR_COL_4,CLR_ROW_6) + COLOR_BUTTON("Focus color:",FOCUSCLR_LABEL,CLR_COL_1,CLR_ROW_7) + COLOR_SAMPLE(FOCUSCLR_BUTTON,FOCUSCLR_SAMPLE,CLR_COL_2,CLR_ROW_7) + COLOR_BUTTON("Player 1:",PLAYER1_LABEL,CLR_COL_1,CLR_ROW_8) + COLOR_SAMPLE(PLAYER1_BUTTON,PLAYER1_SAMPLE,CLR_COL_2,CLR_ROW_8) + COLOR_BUTTON("Player 2:",PLAYER2_LABEL,CLR_COL_3,CLR_ROW_9) + COLOR_SAMPLE(PLAYER2_BUTTON,PLAYER2_SAMPLE,CLR_COL_4,CLR_ROW_9) + COLOR_BUTTON("Player 3:",PLAYER3_LABEL,CLR_COL_1,CLR_ROW_10) + COLOR_SAMPLE(PLAYER3_BUTTON,PLAYER3_SAMPLE,CLR_COL_2,CLR_ROW_10) + COLOR_BUTTON("Player 4:",PLAYER4_LABEL,CLR_COL_3,CLR_ROW_11) + COLOR_SAMPLE(PLAYER4_BUTTON,PLAYER4_SAMPLE,CLR_COL_4,CLR_ROW_11) + +#ifndef _WIN32_WCE + DEFPUSHBUTTON "OK",IDOK,CLR_OK_LEFT,CLR_BUTTON_ROW, + REPOS_BUTTON_WIDTH,BUTTON_HT,BS_NOTIFY + PUSHBUTTON "Cancel",IDCANCEL,CLR_CANCEL_LEFT,CLR_BUTTON_ROW, + REPOS_BUTTON_WIDTH,BUTTON_HT,BS_NOTIFY +#endif +END + +// +// Editor for individual colors +// +#ifdef MY_COLOR_SEL + +# define CLRE_LAB_WIDTH 22 +# define CLEDIT_WIDTH 18 +# define CLSLIDER_WIDTH 30 +# define CLSAMPLE_WIDTH 15 + +# define CLRELABEL_COL 3 +# define CLREEDIT_COL (CLRELABEL_COL+CLRE_LAB_WIDTH+2) +# define CLRESLIDER_COL (CLREEDIT_COL+CLEDIT_WIDTH+3) +# define CLSAMPLE_COL (CLRESLIDER_COL+CLSLIDER_WIDTH+5) +# define CLRE_LAB_HT 12 +# define CLREDIT_ROW_1 5 +# define CLREDIT_ROW_2 21 +# define CLREDIT_ROW_3 37 +# define CLRE_WIDTH (CLSAMPLE_COL+CLSAMPLE_WIDTH+5) +# define CLRE_OK_LEFT ((CLRE_WIDTH/2)-10-REPOS_BUTTON_WIDTH) +# define CLRE_CANCEL_LEFT ((CLRE_WIDTH/2)+10) +# ifdef _WIN32_WCE +# define COLOREDITDLG_HT 56 +# else +# define COLOREDITDLG_HT 72 +# endif + +# define COLOR_EDIT_LINE(txt,row,labelId,editId,sliderId) \ + LTEXT txt,labelId,CLRELABEL_COL,row, \ + CLRE_LAB_WIDTH,CLRE_LAB_HT \ + EDITTEXT editId,CLREEDIT_COL,row,CLEDIT_WIDTH,12, \ + ES_AUTOHSCROLL | ES_NUMBER \ + CONTROL "",sliderId,"msctls_trackbar32", \ + TBS_BOTH|TBS_NOTICKS|WS_TABSTOP, \ + CLRESLIDER_COL,row,CLSLIDER_WIDTH,12 + +IDD_COLOREDITDLG DIALOG DISCARDABLE 0, 0, CLRE_WIDTH, COLOREDITDLG_HT +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | DS_CENTER +CAPTION "" /* set by code */ +FONT 8, "System" +BEGIN + COLOR_EDIT_LINE("Red:", CLREDIT_ROW_1, RED_LABEL, RED_EDIT, CLREDT_SLIDER1 ) + COLOR_EDIT_LINE("Green:", CLREDIT_ROW_2, GREEN_LABEL, GREEN_EDIT, CLREDT_SLIDER2 ) + COLOR_EDIT_LINE("Blue:", CLREDIT_ROW_3, BLUE_LABEL, BLUE_EDIT, CLREDT_SLIDER3 ) + + /* See hack alert above. Bogus owner-draw button seems the only way to get a sample + color rect into the dialog. */ + PUSHBUTTON "",CLSAMPLE_BUTTON_ID,CLSAMPLE_COL,CLREDIT_ROW_1, + CLSAMPLE_WIDTH,CLREDIT_ROW_3-CLREDIT_ROW_1+CLRE_LAB_HT,BS_OWNERDRAW + +# ifndef _WIN32_WCE + DEFPUSHBUTTON "OK",IDOK,CLRE_OK_LEFT,56,REPOS_BUTTON_WIDTH, + REPOS_BUTTON_HT + PUSHBUTTON "Cancel",IDCANCEL,CLRE_CANCEL_LEFT,56,REPOS_BUTTON_WIDTH, + REPOS_BUTTON_HT +# endif +END + +#endif + +#ifdef ALLOW_CHOOSE_FONTS +IDD_FONTSSDLG DIALOG DISCARDABLE 0, 0, 120, 115 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | DS_CENTER +CAPTION "" /* set by code */ +FONT 8, "System" +BEGIN + + LTEXT "Fonts:",FONTS_LABEL,5,2,25,12 +#ifdef _WIN32_WCE + LISTBOX FONTS_COMBO, 30,2,70,12, LISTBOX_CONTROL_FLAGS | LBS_SORT + CONTROL "", IDC_FONTSUPDOWN, UPDOWN_CLASS, SPINNER_CONTROL_FLAGS, + 0, 0, 0, 0 +#endif + COMBOBOX FONTS_COMBO_PPC,30,2,70,58, + CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + + LTEXT "Size:",FONTSIZE_LABEL,5,16,25,12 +#ifdef _WIN32_WCE + LISTBOX FONTSIZE_COMBO, 30,16,25,12, LISTBOX_CONTROL_FLAGS | LBS_SORT + CONTROL "", IDC_FONTSIZEUPDOWN, UPDOWN_CLASS, SPINNER_CONTROL_FLAGS, + 0, 0, 0, 0 +#endif + COMBOBOX FONTSIZE_COMBO_PPC,30,16,25,58, + CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + +# ifndef _WIN32_WCE + DEFPUSHBUTTON "OK",IDOK,20,100,REPOS_BUTTON_WIDTH, + REPOS_BUTTON_HT + PUSHBUTTON "Cancel",IDCANCEL,100,100,REPOS_BUTTON_WIDTH, + REPOS_BUTTON_HT +# endif +END +#endif + +///////////////////////////////////////////////////////////////////////////// +// +// Bitmap +// + +IDB_RIGHTARROW BITMAP DISCARDABLE "bmps/rightarrow.bmp" +IDB_DOWNARROW BITMAP DISCARDABLE "bmps/downarro.bmp" +IDB_ORIGIN BITMAP DISCARDABLE "bmps/origin.bmp" + +///////////////////////////////////////////////////////////////////////////// +// +// CLRS +// + +ID_COLORS_RES CLRS MOVEABLE PURE +BEGIN + 0xAF, 0xAF, 0x00, /* bonus 1 */ + 0x00, 0xAF, 0xAF, + 0xAF, 0x00, 0xAF, + 0xAF, 0xAF, 0xAF, + 0xFF, 0xFF, 0xFF, /* empty cells/CE_BKG_COLOR */ + 0xFF, 0xFF, 0x99, /* tile background */ + 0x70, 0x70, 0xFF, /* focus */ + 0x00, 0x00, 0x00, /* player 1 */ + 0xFF, 0x00, 0x00, + 0x00, 0x00, 0xFF, + 0x00, 0x8F, 0x00, /* 8F: full-green contrasts badly with background */ + 0x00, 0x00, 0x00, /* black */ + 0xFF, 0xFF, 0xFF /* white */ +END + +///////////////////////////////////////////////////////////////////////////// +// +// BONS: bonus square values. +// + +// Butts' board +ID_BONUS_RES BONS MOVEABLE PURE +BEGIN + 0x4001, 0x0004, + 0x0200, 0x0300, + 0x0020, 0x0010, + 0x1002, 0x0001, + 0x0000, 0x2000, + 0x0300, 0x0300, + 0x0010, 0x0010, + 0x4001, 0x0002 +END + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE DISCARDABLE +BEGIN +#ifdef DEBUG + IDS_APP_TITLE "Crossw_dbg" + IDC_XWORDS4 "XWORDS4_DBG" +#else + IDS_APP_TITLE "Crosswords" + IDC_XWORDS4 "XWORDS4" +#endif + IDS_MENU "Menu" + IDS_DUMMY "--" + IDS_CANCEL "Cancel" + IDS_OK "Ok" + IDS_DONE "Done" + IDS_ABOUT "Crosswords 4.2rc1 (rev " SVN_REV ") "\ + "for Windows Mobile. Copyright 1998-2008 by "\ + "Eric House. This software is released under the GNU "\ + "Public License.\r\r"\ + "For dictionaries, a manual, or source code go to "\ + "http://xwords.sf.net or http://eehouse.org/xwords/." + IDS_DICTLOC "Please install a Crosswords dictionary (.xwd file) "\ + "in the same directory as the Crosswords .exe file, or "\ + "in a directory called Crosswords on an external card, "\ + "e.g. in \\Storage Card\\Crosswords. "\ + "Download dictionaries from http://xwords.sf.net or "\ + "http://eehouse.org/xwords." + IDS_SAVENAME "Enter a name for the current game. (You can change the "\ + "name later using the Saved Games dialog.)" + IDS_DUPENAME "Enter a name for a copy of this game." + IDS_RENAME "Enter a new name for this game." + +END + +#ifdef _WIN32_WCE +// from http://msdn.microsoft.com/en-us/library/ms838191.aspx: don't +// let WinMo draw 320x320 as 240x240 +HI_RES_AWARE CEUX {1} +#endif + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + +/* This is supposed to keep the OS from running us in "emulation mode", but it + isn't working. Changing the versions as reported by objdump does work */ +/* #ifdef _WIN32_WCE */ +/* HI_RES_AWARE CEUX {1} */ +/* #endif */ + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED +