1 /*
2 * src/editBoard.c, part of Complete Goban (game program)
3 * Copyright (C) 1995-1997 William Shubert
4 * See "configure.h.in" for more copyright information.
5 */
6
7 #include <wms.h>
8 #include <wms/str.h>
9 #include <wms/clp.h>
10 #include <but/but.h>
11 #include <abut/msg.h>
12 #include <but/text.h>
13 #include <abut/term.h>
14 #include <abut/fsel.h>
15 #include <but/plain.h>
16 #include <but/ctext.h>
17 #include <but/textin.h>
18 #include "cgoban.h"
19 #include "sgf.h"
20 #include "sgfIn.h"
21 #include "sgfPlay.h"
22 #include "sgfOut.h"
23 #include "goban.h"
24 #include "msg.h"
25 #include "sgfMap.h"
26 #ifdef _EDITBOARD_H_
27 #error Levelization Error.
28 #endif
29 #include "editBoard.h"
30
31
32 /**********************************************************************
33 * Data types
34 **********************************************************************/
35 typedef enum {
36 setMark_toggle, setMark_forceOn, setMark_forceOff
37 } SetMarkAction;
38
39
40 /**********************************************************************
41 * Forward declarations
42 **********************************************************************/
43 static void toolsQuit(void *packet);
44 static GobanOut gridPressed(void *packet, int loc);
45 static GobanOut quitPressed(void *packet);
46 static GobanOut passPressed(void *packet);
47 static GobanOut rewPressed(void *packet);
48 static GobanOut backPressed(void *packet);
49 static GobanOut fwdPressed(void *packet);
50 static GobanOut ffPressed(void *packet);
51 static GobanOut donePressed(void *packet);
52 static GobanOut disputePressed(void *packet);
53 static GobanOut savePressed(void *packet);
54 static GobanOut gameInfoPressed(void *packet);
55 static void writeGobanComments(EditBoard *e), readGobanComments(EditBoard *e);
56 static void clearComments(EditBoard *eb);
57 static bool backOk(void *packet);
58 static bool fwdOk(void *packet);
59 static void saveFile(AbutFsel *fsel, void *packet, const char *fname);
60 static void gobanDestroyed(void *packet);
61 static GobanOut jumpToLoc(EditBoard *eb, int loc);
62 static void addPattern(EditBoard *eb, GoStone color, int loc);
63 static void setMark(EditBoard *eb, SgfType sType, int loc,
64 SetMarkAction action);
65 static void addLetter(EditBoard *eb, int loc);
66 static bool addNumber(EditBoard *eb, int loc, bool moveNum);
67 static bool markGroup(EditBoard *eb, SgfType type, int loc);
68 static ButOut newTool(void *packet);
69 static ButOut shiftPressed(But *but, bool press);
70 static ButOut newActiveSgfNode(void *packet, int nodeNum);
71 static void changeWhoseMove(EditBoard *eb);
72 static ButOut reallyQuit(But *but);
73 static ButOut dontQuit(But *but);
74 static ButOut quitWinDead(void *packet);
75 static EditBoard *createSgf(Cgoban *cg, Sgf *sgf, const char *fName);
76 static void infoDead(EditInfo *info, void *packet);
77
78
79 /**********************************************************************
80 * Global variables
81 **********************************************************************/
82 static const GobanActions editBoard_actions = {
83 gridPressed, quitPressed, passPressed, rewPressed, backPressed,
84 fwdPressed, ffPressed, donePressed, disputePressed, savePressed,
85 NULL, /* editPressed / printPressed */ gameInfoPressed,
86 &help_editBoard,
87 gobanDestroyed,
88 backOk, backOk, fwdOk, fwdOk};
89
90
91 /**********************************************************************
92 * Functions
93 **********************************************************************/
editBoard_create(Cgoban * cg,const char * fName)94 EditBoard *editBoard_create(Cgoban *cg, const char *fName) {
95 Sgf *sgf;
96 const char *err;
97 Str *tmpTitle;
98 bool noFile = FALSE;
99
100 sgf = sgf_createFile(cg, fName, &err, &noFile);
101 if (sgf == NULL) {
102 if (noFile) {
103 tmpTitle = str_create();
104 str_print(tmpTitle, msg_noSuchGame, fName);
105 abutMsg_winCreate(cg->abut, "Cgoban Error", str_chars(tmpTitle));
106 str_destroy(tmpTitle);
107 } else {
108 abutMsg_winCreate(cg->abut, "Cgoban Error", err);
109 }
110 return(FALSE);
111 }
112 return(createSgf(cg, sgf, fName));
113 }
114
115
editBoard_createSgf(Cgoban * cg,const Sgf * sgf)116 EditBoard *editBoard_createSgf(Cgoban *cg, const Sgf *sgf) {
117 EditBoard *eb;
118
119 eb = createSgf(cg, sgf_copy(sgf), NULL);
120 if (eb != NULL) {
121 ffPressed(eb);
122 goban_update(eb->goban);
123 }
124 return(eb);
125 }
126
127
createSgf(Cgoban * cg,Sgf * sgf,const char * fName)128 static EditBoard *createSgf(Cgoban *cg, Sgf *sgf, const char *fName) {
129 EditBoard *eb;
130 SgfElem *me;
131 const char *white = NULL, *black = NULL, *title;
132 GoRules rules;
133 int size;
134 int hcap;
135 float komi;
136 GoTime time;
137 static const ButKey shiftKeys[] = {{XK_Shift_L, 0,0},
138 {XK_Shift_R,0,0}, {0,0,0}};
139 static const ButKey shiftUpKeys[] = {{XK_Up, ShiftMask, ShiftMask},
140 {0,0,0}};
141 static const ButKey shiftDownKeys[] = {{XK_Down, ShiftMask, ShiftMask},
142 {0,0,0}};
143 Str *tmpTitle = NULL;
144
145 sgf->mode = sgfInsert_variant;
146 eb = wms_malloc(sizeof(EditBoard));
147 MAGIC_SET(eb);
148 eb->cg = cg;
149 eb->fsel = NULL;
150 eb->reallyQuit = NULL;
151 eb->sgf = sgf;
152
153 me = sgf_findType(sgf, sgfType_rules);
154 if (me) {
155 rules = (GoRules)me->iVal;
156 } else
157 rules = goRules_japanese;
158 me = sgf_findType(sgf, sgfType_size);
159 if (me) {
160 size = me->iVal;
161 } else {
162 size = 19;
163 }
164 me = sgf_findType(sgf, sgfType_handicap);
165 if (me) {
166 hcap = me->iVal;
167 } else {
168 hcap = 0;
169 }
170 me = sgf_findType(sgf, sgfType_komi);
171 if (me) {
172 komi = (float)me->iVal / 2.0;
173 } else {
174 komi = 0.0;
175 }
176 me = sgf_findFirstType(sgf, sgfType_playerName);
177 while (me) {
178 if (me->gVal == goStone_white)
179 white = str_chars(me->sVal);
180 else {
181 assert(me->gVal == goStone_black);
182 black = str_chars(me->sVal);
183 }
184 me = sgfElem_findFirstType(me, sgfType_playerName);
185 }
186 str_init(&eb->fName);
187 if (fName != NULL) {
188 str_copyChars(&eb->fName, fName);
189 } else if (white && black) {
190 str_print(&eb->fName, "%s-%s.sgf", white, black);
191 } else {
192 str_copyChars(&eb->fName, "game.sgf");
193 }
194 me = sgf_findFirstType(sgf, sgfType_time);
195 if (me) {
196 goTime_parseDescribeChars(&time, str_chars(me->sVal));
197 } else {
198 time.type = goTime_none;
199 }
200 eb->game = goGame_create(size, rules, hcap, komi, &time, TRUE);
201 sgf_play(sgf, eb->game, NULL, eb->currentNodeNum = 0, NULL);
202 ++eb->game->maxMoves;
203
204 me = sgf_findFirstType(sgf, sgfType_title);
205 if (me) {
206 title = str_chars(me->sVal);
207 } else {
208 if (white && black) {
209 tmpTitle = str_create();
210 str_print(tmpTitle, msg_localTitle, white, black);
211 title = str_chars(tmpTitle);
212 } else
213 title = msg_noTitle;
214 }
215
216 eb->goban = goban_create(cg, &editBoard_actions, eb, eb->game, "edit",
217 title);
218 if (tmpTitle)
219 str_destroy(tmpTitle);
220 eb->goban->iDec1 = stoneGroup_create(&cg->cgbuts, eb->goban->iWin, 2,
221 BUT_DRAWABLE);
222 assert((hcap == 0) || ((hcap >= 2) && (hcap <= 27)));
223 butCt_setText(eb->goban->edit, msg_printGame);
224
225 eb->lastComment = NULL;
226
227 editToolWin_init(&eb->tools, cg, eb->sgf, toolsQuit,
228 newTool, newActiveSgfNode, eb);
229 eb->oldTool = editTool_changeBoard;
230 eb->shiftKeytrap = butKeytrap_create(shiftPressed, eb, eb->goban->win,
231 BUT_DRAWABLE|BUT_PRESSABLE);
232 but_setKeys(eb->shiftKeytrap, shiftKeys);
233 eb->prevVar = butKeytrap_create(editToolWin_shiftUpPressed, &eb->tools,
234 eb->goban->win, BUT_DRAWABLE|BUT_PRESSABLE);
235 but_setKeys(eb->prevVar, shiftUpKeys);
236 eb->nextVar = butKeytrap_create(editToolWin_shiftDownPressed, &eb->tools,
237 eb->goban->win, BUT_DRAWABLE|BUT_PRESSABLE);
238 but_setKeys(eb->nextVar, shiftDownKeys);
239 eb->invertShift = FALSE;
240 butKeytrap_setHold(eb->shiftKeytrap, FALSE);
241 writeGobanComments(eb);
242 newTool(eb);
243 eb->info = NULL;
244
245 return(eb);
246 }
247
248
editBoard_destroy(EditBoard * eb)249 void editBoard_destroy(EditBoard *eb) {
250 assert(MAGIC(eb));
251 str_deinit(&eb->fName);
252 if (eb->reallyQuit) {
253 abutMsg_destroy(eb->reallyQuit, FALSE);
254 eb->reallyQuit = NULL;
255 }
256 if (eb->sgf)
257 sgf_destroy(eb->sgf);
258 if (eb->goban)
259 goban_destroy(eb->goban, FALSE);
260 if (eb->game)
261 goGame_destroy(eb->game);
262 editToolWin_deinit(&eb->tools);
263 if (eb->info) {
264 editInfo_destroy(eb->info, FALSE);
265 eb->info = NULL;
266 }
267 MAGIC_UNSET(eb);
268 wms_free(eb);
269 }
270
271
toolsQuit(void * packet)272 static void toolsQuit(void *packet) {
273 quitPressed(packet);
274 }
275
276
gridPressed(void * packet,int loc)277 static GobanOut gridPressed(void *packet, int loc) {
278 EditBoard *eb = packet;
279 ButEnv *env;
280 GobanOut result = gobanOut_draw;
281
282 assert(MAGIC(eb));
283 assert(eb->sgf->active != NULL);
284 env = eb->cg->env;
285 switch(eb->tools.tool) {
286 case editTool_play:
287 if (butEnv_keyModifiers(env) & ShiftMask) {
288 result = jumpToLoc(eb, loc);
289 } else {
290 if (goBoard_stone(eb->game->board, loc) != goStone_empty) {
291 result = gobanOut_err;
292 } else {
293 eb->tools.modified = TRUE;
294 readGobanComments(eb);
295 sgf_addNode(eb->sgf);
296 assert(eb->sgf->active->mapX < 1000);
297 editToolWin_nodeAdded(&eb->tools, eb->sgf->active);
298 sgf_move(eb->sgf, goGame_whoseMove(eb->game),
299 goBoard_loc2Sgf(eb->game->board, loc));
300 sgf_play(eb->sgf, eb->game, eb->goban->pic, ++eb->currentNodeNum,
301 NULL);
302 editToolWin_newActiveNode(&eb->tools, eb->sgf->active);
303 writeGobanComments(eb);
304 }
305 }
306 break;
307 case editTool_changeBoard:
308 eb->tools.modified = TRUE;
309 if (goStone_isStone(goBoard_stone(eb->game->board, loc)))
310 addPattern(eb, goStone_empty, loc);
311 else if (butEnv_keyModifiers(env) & ShiftMask)
312 addPattern(eb, goStone_black, loc);
313 else
314 addPattern(eb, goStone_white, loc);
315 break;
316 case editTool_score:
317 if (goStone_isStone(goBoard_stone(eb->game->board, loc)))
318 goGame_markDead(eb->game, loc);
319 else
320 result = gobanOut_err;
321 break;
322 case editTool_triangle:
323 if (butEnv_keyModifiers(env) & ShiftMask) {
324 if (!markGroup(eb, sgfType_triangle, loc))
325 result = gobanOut_err;
326 else {
327 eb->tools.modified = TRUE;
328 }
329 } else {
330 eb->tools.modified = TRUE;
331 setMark(eb, sgfType_triangle, loc, setMark_toggle);
332 }
333 break;
334 case editTool_square:
335 if (butEnv_keyModifiers(env) & ShiftMask) {
336 if (!markGroup(eb, sgfType_square, loc))
337 result = gobanOut_err;
338 else {
339 eb->tools.modified = TRUE;
340 }
341 } else {
342 eb->tools.modified = TRUE;
343 setMark(eb, sgfType_square, loc, setMark_toggle);
344 }
345 break;
346 case editTool_circle:
347 if (butEnv_keyModifiers(env) & ShiftMask) {
348 if (!markGroup(eb, sgfType_circle, loc))
349 result = gobanOut_err;
350 else {
351 eb->tools.modified = TRUE;
352 }
353 } else {
354 eb->tools.modified = TRUE;
355 setMark(eb, sgfType_circle, loc, setMark_toggle);
356 }
357 break;
358 case editTool_letter:
359 eb->tools.modified = TRUE;
360 addLetter(eb, loc);
361 break;
362 case editTool_number:
363 if (addNumber(eb, loc, butEnv_keyModifiers(env) & ShiftMask)) {
364 eb->tools.modified = TRUE;
365 } else
366 result = gobanOut_err;
367 break;
368 }
369 editToolWin_newColor(&eb->tools, goGame_whoseMove(eb->game));
370 assert(eb->sgf->active != NULL);
371 sgfMap_changeNode(eb->tools.sgfMap, eb->sgf->active);
372 return(result);
373 }
374
375
jumpToLoc(EditBoard * eb,int loc)376 static GobanOut jumpToLoc(EditBoard *eb, int loc) {
377 char moveTo[5];
378 int newNode, i;
379
380 strcpy(moveTo, goBoard_loc2Sgf(eb->game->board, loc));
381 newNode = -1;
382 if (goBoard_stone(eb->game->board, loc) == goStone_empty) {
383 i = sgfElem_findMove(eb->sgf->active, moveTo, 1);
384 if (i >= 0) {
385 newNode = eb->currentNodeNum + i;
386 } else {
387 i = sgfElem_findMove(eb->sgf->active, moveTo, -1);
388 if (i >= 0)
389 newNode = eb->currentNodeNum - i;
390 }
391 } else {
392 i = sgfElem_findMove(eb->sgf->active, moveTo, -1);
393 assert(i >= 0);
394 newNode = eb->currentNodeNum - i;
395 }
396 if (newNode > 0) {
397 readGobanComments(eb);
398 sgf_play(eb->sgf, eb->game, eb->goban->pic, eb->currentNodeNum = newNode,
399 NULL);
400 editToolWin_newActiveNode(&eb->tools, eb->sgf->active);
401 writeGobanComments(eb);
402 return(gobanOut_draw);
403 } else
404 return(gobanOut_err);
405 }
406
407
quitPressed(void * packet)408 static GobanOut quitPressed(void *packet) {
409 EditBoard *eb = packet;
410 AbutMsgOpt buttons[2];
411 Str saveMessage;
412
413 assert(MAGIC(eb));
414 readGobanComments(eb);
415 if (eb->tools.modified) {
416 buttons[0].name = msg_noCancel;
417 buttons[0].callback = dontQuit;
418 buttons[0].packet = eb;
419 buttons[0].keyEq = NULL;
420 buttons[1].name = msg_yesQuit;
421 buttons[1].callback = reallyQuit;
422 buttons[1].packet = eb;
423 buttons[1].keyEq = NULL;
424 str_init(&saveMessage);
425 str_print(&saveMessage, msg_reallyQuit,
426 str_chars(&eb->fName));
427 if (eb->reallyQuit)
428 abutMsg_destroy(eb->reallyQuit, FALSE);
429 eb->reallyQuit = abutMsg_winOptCreate(eb->cg->abut, "Cgoban Warning",
430 str_chars(&saveMessage),
431 quitWinDead, eb, 2, buttons);
432 str_deinit(&saveMessage);
433 } else
434 editBoard_destroy(packet);
435 return(gobanOut_noDraw);
436 }
437
438
passPressed(void * packet)439 static GobanOut passPressed(void *packet) {
440 EditBoard *eb = packet;
441
442 assert(MAGIC(eb));
443 switch(eb->tools.tool) {
444 case editTool_play:
445 readGobanComments(eb);
446 sgf_addNode(eb->sgf);
447 assert(eb->sgf->active->parent->activeChild == eb->sgf->active);
448 editToolWin_nodeAdded(&eb->tools, eb->sgf->active);
449 assert(eb->sgf->active->parent->activeChild == eb->sgf->active);
450 readGobanComments(eb);
451 assert(eb->sgf->active->type == sgfType_node);
452 assert(eb->sgf->active->parent->activeChild == eb->sgf->active);
453 assert((eb->sgf->active->parent->childH !=
454 eb->sgf->active->parent->childT) ||
455 (eb->sgf->active->parent->activeChild ==
456 eb->sgf->active->parent->childH));
457 sgf_pass(eb->sgf, goGame_whoseMove(eb->game));
458 assert(eb->sgf->active->type == sgfType_pass);
459 assert(eb->sgf->active->parent->activeChild == eb->sgf->active);
460 assert(eb->sgf->active->parent->activeChild ==
461 eb->sgf->active->parent->childH);
462 assert(eb->sgf->active->parent->activeChild ==
463 eb->sgf->active->parent->childT);
464 assert((eb->sgf->active->parent->parent->childH !=
465 eb->sgf->active->parent->parent->childT) ||
466 (eb->sgf->active->parent->parent->activeChild ==
467 eb->sgf->active->parent->parent->childH));
468 sgf_play(eb->sgf, eb->game, eb->goban->pic, ++eb->currentNodeNum, NULL);
469 editToolWin_newActiveNode(&eb->tools, eb->sgf->active);
470 writeGobanComments(eb);
471 break;
472 case editTool_changeBoard:
473 readGobanComments(eb);
474 sgf_addNode(eb->sgf);
475 clearComments(eb);
476 editToolWin_newActiveNode(&eb->tools, eb->sgf->active);
477 writeGobanComments(eb);
478 break;
479 default:
480 break;
481 }
482 editToolWin_newColor(&eb->tools, goGame_whoseMove(eb->game));
483 sgfMap_changeNode(eb->tools.sgfMap, eb->sgf->active);
484 return(gobanOut_draw);
485 }
486
487
rewPressed(void * packet)488 static GobanOut rewPressed(void *packet) {
489 EditBoard *e = packet;
490
491 assert(MAGIC(e));
492 readGobanComments(e);
493 sgf_play(e->sgf, e->game, e->goban->pic, e->currentNodeNum = 0, NULL);
494 editToolWin_newActiveNode(&e->tools, e->sgf->active);
495 writeGobanComments(e);
496 editToolWin_newColor(&e->tools, goGame_whoseMove(e->game));
497 return(gobanOut_draw);
498 }
499
500
backPressed(void * packet)501 static GobanOut backPressed(void *packet) {
502 EditBoard *eb = packet;
503 SgfElem *active;
504 int backCount = 1;
505
506 assert(MAGIC(eb));
507 readGobanComments(eb);
508 if (butEnv_keyModifiers(eb->cg->env) & ShiftMask) {
509 /*
510 * Jump to next node with comments or a variation.
511 */
512 backCount = 0;
513 active = eb->sgf->active;
514 while (active) {
515 if (active->type == sgfType_node) {
516 ++backCount;
517 } else if ((active->type >= sgfType_setBoard) &&
518 (active->type <= sgfType_comment) && backCount)
519 break;
520 if ((backCount > 0) && (active->childH != active->childT))
521 break;
522 active = active->parent;
523 }
524 }
525 sgf_play(eb->sgf, eb->game, eb->goban->pic, eb->currentNodeNum -= backCount,
526 NULL);
527 editToolWin_newActiveNode(&eb->tools, eb->sgf->active);
528 writeGobanComments(eb);
529 editToolWin_newColor(&eb->tools, goGame_whoseMove(eb->game));
530 return(gobanOut_draw);
531 }
532
533
fwdPressed(void * packet)534 static GobanOut fwdPressed(void *packet) {
535 EditBoard *eb = packet;
536 SgfElem *active;
537 int fwdCount = 1;
538
539 assert(MAGIC(eb));
540 readGobanComments(eb);
541 if (butEnv_keyModifiers(eb->cg->env) & ShiftMask) {
542 /*
543 * Jump to next node with comments or a variation.
544 */
545 active = eb->sgf->active->activeChild->activeChild;
546 while (active) {
547 if (active->type == sgfType_node) {
548 ++fwdCount;
549 } else if ((active->type >= sgfType_setBoard) &&
550 (active->type <= sgfType_comment))
551 break;
552 if (active->childH != active->childT)
553 break;
554 active = active->activeChild;
555 }
556 }
557 sgf_play(eb->sgf, eb->game, eb->goban->pic, eb->currentNodeNum += fwdCount,
558 NULL);
559 editToolWin_newActiveNode(&eb->tools, eb->sgf->active);
560 writeGobanComments(eb);
561 editToolWin_newColor(&eb->tools, goGame_whoseMove(eb->game));
562 return(gobanOut_draw);
563 }
564
565
ffPressed(void * packet)566 static GobanOut ffPressed(void *packet) {
567 EditBoard *e = packet;
568
569 assert(MAGIC(e));
570 readGobanComments(e);
571 e->currentNodeNum = sgf_play(e->sgf, e->game, e->goban->pic, -1, NULL);
572 editToolWin_newActiveNode(&e->tools, e->sgf->active);
573 writeGobanComments(e);
574 editToolWin_newColor(&e->tools, goGame_whoseMove(e->game));
575 return(gobanOut_draw);
576 }
577
578
donePressed(void * packet)579 static GobanOut donePressed(void *packet) {
580 SgfElem *otherTerrMarks;
581 int i;
582 EditBoard *eb = packet;
583 Str *scoreComment;
584
585 assert(MAGIC(eb));
586 readGobanComments(eb);
587 while ((otherTerrMarks = sgfElem_findTypeInNode(eb->sgf->active,
588 sgfType_territory))) {
589 sgfElem_snip(otherTerrMarks, eb->sgf);
590 }
591 for (i = 0; i < goBoard_area(eb->game->board); ++i) {
592 if (((eb->game->flags[i] & GOGAMEFLAGS_SEEN) == GOGAMEFLAGS_SEEWHITE) &&
593 ((goBoard_stone(eb->game->board, i) == goStone_empty) ||
594 (eb->game->flags[i] & GOGAMEFLAGS_MARKDEAD)))
595 sgf_addTerritory(eb->sgf, goStone_white,
596 goBoard_loc2Sgf(eb->game->board, i));
597 }
598 for (i = 0; i < goBoard_area(eb->game->board); ++i) {
599 if (((eb->game->flags[i] & GOGAMEFLAGS_SEEN) == GOGAMEFLAGS_SEEBLACK) &&
600 ((goBoard_stone(eb->game->board, i) == goStone_empty) ||
601 (eb->game->flags[i] & GOGAMEFLAGS_MARKDEAD)))
602 sgf_addTerritory(eb->sgf, goStone_black,
603 goBoard_loc2Sgf(eb->game->board, i));
604 }
605 scoreComment = goScore_str(&eb->goban->score, eb->game,
606 &eb->game->time, eb->goban->timers);
607 sgf_catComment(eb->sgf, str_chars(scoreComment));
608 sgfMap_changeNode(eb->tools.sgfMap, eb->sgf->active);
609 str_destroy(scoreComment);
610 editToolWin_newTool(&eb->tools, editTool_play, TRUE);
611 writeGobanComments(eb);
612 sgfMap_changeNode(eb->tools.sgfMap, eb->sgf->active);
613 return(gobanOut_noDraw);
614 }
615
616
disputePressed(void * packet)617 static GobanOut disputePressed(void *packet) {
618 return(gobanOut_noDraw);
619 }
620
621
savePressed(void * packet)622 static GobanOut savePressed(void *packet) {
623 EditBoard *e = packet;
624
625 assert(MAGIC(e));
626 if (e->fsel)
627 abutFsel_destroy(e->fsel, FALSE);
628 e->fsel = abutFsel_create(e->cg->abut, saveFile, e, "CGoban",
629 msg_saveGameName,
630 str_chars(&e->fName));
631 return(gobanOut_noDraw);
632 }
633
634
saveFile(AbutFsel * fsel,void * packet,const char * fname)635 static void saveFile(AbutFsel *fsel, void *packet, const char *fname) {
636 EditBoard *e = packet;
637 int error;
638 Str str;
639
640 assert(MAGIC(e));
641 str_copy(&e->cg->lastDirAccessed, &fsel->pathVal);
642 if (fname != NULL) {
643 str_copyChars(&e->fName, fname);
644 clp_setStr(e->cg->clp, "edit.sgfName", butTextin_get(fsel->in));
645 readGobanComments(e);
646 e->tools.modified = FALSE;
647 if (e->info)
648 editInfo_updateSgf(e->info);
649 if (!sgf_writeFile(e->sgf, fname, &error)) {
650 str_init(&str);
651 str_print(&str, "Error saving file \"%s\": %s",
652 fname, strerror(errno));
653 cgoban_createMsgWindow(e->cg, "Cgoban Error", str_chars(&str));
654 str_deinit(&str);
655 }
656 }
657 e->fsel = NULL;
658 }
659
660
clearComments(EditBoard * eb)661 static void clearComments(EditBoard *eb) {
662 assert(MAGIC(eb));
663 goban_setComments(eb->goban, "");
664 }
665
666
667 /*
668 * This transfers comments from the SGF move chain to the goban.
669 */
writeGobanComments(EditBoard * e)670 static void writeGobanComments(EditBoard *e) {
671 SgfElem *comElem;
672
673 assert(MAGIC(e));
674 comElem = sgfElem_findTypeInNode(e->sgf->active, sgfType_comment);
675 if (comElem) {
676 if (strcmp(str_chars(comElem->sVal), goban_getComments(e->goban)))
677 goban_setComments(e->goban, str_chars(comElem->sVal));
678 } else {
679 if (e->lastComment)
680 goban_setComments(e->goban, "");
681 }
682 e->lastComment = comElem;
683 }
684
685
686 /*
687 * This transfers comments from the goban to the SGF move chain.
688 */
readGobanComments(EditBoard * eb)689 static void readGobanComments(EditBoard *eb) {
690 const char *comm;
691 int cmpLen;
692
693 assert(MAGIC(eb));
694 assert(eb->sgf->active->parent->activeChild == eb->sgf->active);
695 comm = goban_getComments(eb->goban);
696 if (comm[0]) {
697 if (eb->lastComment) {
698 cmpLen = str_len(eb->lastComment->sVal);
699 if (str_chars(eb->lastComment->sVal)[cmpLen - 1] != '\n') {
700 /* A "\n" was added. */
701 if ((strlen(comm) == cmpLen + 1) &&
702 !strncmp(comm, str_chars(eb->lastComment->sVal), cmpLen))
703 return;
704 } else {
705 if (!strcmp(comm, str_chars(eb->lastComment->sVal)))
706 return;
707 }
708 }
709 eb->tools.modified = TRUE;
710 eb->sgf->mode = sgfInsert_inline;
711 sgf_comment(eb->sgf, comm);
712 eb->sgf->mode = sgfInsert_variant;
713 eb->lastComment = sgfElem_findTypeInNode(eb->sgf->active, sgfType_comment);
714 assert(eb->lastComment != NULL);
715 assert(eb->sgf->active->parent->activeChild == eb->sgf->active);
716 } else if (eb->lastComment) {
717 eb->tools.modified = TRUE;
718 sgfElem_snip(eb->lastComment, eb->sgf);
719 eb->lastComment = NULL;
720 assert(eb->sgf->active->parent->activeChild == eb->sgf->active);
721 }
722 sgfMap_setMapPointer(eb->tools.sgfMap, eb->sgf->active);
723 }
724
725
backOk(void * packet)726 static bool backOk(void *packet) {
727 EditBoard *e = packet;
728
729 assert(MAGIC(e));
730 return((e->game->state == goGameState_play) && (e->currentNodeNum > 0));
731 }
732
733
fwdOk(void * packet)734 static bool fwdOk(void *packet) {
735 EditBoard *e = packet;
736
737 assert(MAGIC(e));
738 return((e->game->state == goGameState_play) &&
739 (e->sgf->active->activeChild != NULL));
740 }
741
742
gobanDestroyed(void * packet)743 static void gobanDestroyed(void *packet) {
744 EditBoard *eb = packet;
745
746 assert(MAGIC(eb));
747 eb->goban = NULL;
748 editBoard_destroy(eb);
749 }
750
751
addPattern(EditBoard * eb,GoStone color,int loc)752 static void addPattern(EditBoard *eb, GoStone color, int loc) {
753 SgfElem *otherEdits, *oldActive;
754 GoBoard *newBoard, *tmp;
755 int i;
756
757 otherEdits = sgfElem_findTypeInNode(eb->sgf->active, sgfType_setBoard);
758 if (otherEdits == NULL) {
759 /*
760 * We always add a node if there are no other edits, 'cause they
761 * shouldn't be in the same node as moves.
762 */
763 readGobanComments(eb);
764 sgf_addNode(eb->sgf);
765 editToolWin_nodeAdded(&eb->tools, eb->sgf->active);
766 ++eb->currentNodeNum;
767 editToolWin_newActiveNode(&eb->tools, eb->sgf->active);
768 writeGobanComments(eb);
769 }
770 /*
771 * What we do is add the stone, copy the board, then recreate the game
772 * just before this group of stones. Then we scan through the two boards,
773 * looking for differences, and recording them in the sgf file. This
774 * is very time consuming, but it's the easient way to do things so for
775 * now I'll leave it as is.
776 */
777 newBoard = goBoard_create(eb->game->size);
778 goGame_setBoard(eb->game, color, loc);
779 goBoard_copy(eb->game->board, newBoard);
780 if (eb->currentNodeNum == 0) {
781 /* We must start from a fresh board! */
782 goGame_moveTo(eb->game, 0);
783 } else {
784 /* We want to preserve the active node across this call to "play()". */
785 oldActive = eb->sgf->active;
786 sgf_play(eb->sgf, eb->game, NULL, eb->currentNodeNum - 1, NULL);
787 eb->sgf->active = oldActive;
788 }
789
790 eb->sgf->mode = sgfInsert_inline;
791
792 /*
793 * Now we have to snip out all previous edits.
794 */
795 while ((otherEdits = sgfElem_findTypeInNode(eb->sgf->active,
796 sgfType_setBoard))) {
797 sgfElem_snip(otherEdits, eb->sgf);
798 }
799
800 /* First add all the empties... */
801 for (i = 0; i < goBoard_area(newBoard); ++i) {
802 if ((goBoard_stone(newBoard, i) == goStone_empty) &&
803 (goBoard_stone(eb->game->board, i) != goStone_empty))
804 sgf_addStone(eb->sgf, goStone_empty, goBoard_loc2Sgf(newBoard, i));
805 }
806 /* Now the white stones... */
807 for (i = 0; i < goBoard_area(newBoard); ++i) {
808 if ((goBoard_stone(newBoard, i) == goStone_white) &&
809 (goBoard_stone(eb->game->board, i) != goStone_white))
810 sgf_addStone(eb->sgf, goStone_white, goBoard_loc2Sgf(newBoard, i));
811 }
812 /* And finally the black stones. */
813 for (i = 0; i < goBoard_area(newBoard); ++i) {
814 if ((goBoard_stone(newBoard, i) == goStone_black) &&
815 (goBoard_stone(eb->game->board, i) != goStone_black))
816 sgf_addStone(eb->sgf, goStone_black, goBoard_loc2Sgf(newBoard, i));
817 }
818 /* Now put the board back the way that it should be. */
819 tmp = eb->game->board;
820 eb->game->board = newBoard;
821 eb->sgf->mode = sgfInsert_variant;
822 goBoard_destroy(tmp);
823 }
824
825
setMark(EditBoard * eb,SgfType sType,int loc,SetMarkAction action)826 static void setMark(EditBoard *eb, SgfType sType, int loc,
827 SetMarkAction action) {
828 SgfElem *search;
829 bool remove;
830 GoMarkType markAdded;
831
832 for (search = eb->sgf->active; search && (search->type != sgfType_node);
833 search = search->parent) {
834 if (((search->type == sgfType_triangle) ||
835 (search->type == sgfType_circle) ||
836 (search->type == sgfType_square)) &&
837 (goBoard_sgf2Loc(eb->game->board, search->lVal) == loc)) {
838 remove = (((search->type == sType) && (action == setMark_toggle)) ||
839 (action == setMark_forceOff));
840 sgfElem_snip(search, eb->sgf);
841 if (remove) {
842 grid_setMark(eb->goban->pic->boardButs[loc], goMark_none, 0);
843 return;
844 }
845 break;
846 }
847 }
848 search = sgfElem_findTypeInNode(eb->sgf->active, sType);
849 if (search)
850 eb->sgf->active = search;
851 eb->sgf->mode = sgfInsert_inline;
852 sgf_addLElem(eb->sgf, sType, goBoard_loc2Sgf(eb->game->board, loc));
853 eb->sgf->mode = sgfInsert_variant;
854 if (sType == sgfType_triangle) {
855 markAdded = goMark_triangle;
856 } else if (sType == sgfType_square) {
857 markAdded = goMark_square;
858 } else {
859 assert(sType == sgfType_circle);
860 markAdded = goMark_circle;
861 }
862 grid_setMark(eb->goban->pic->boardButs[loc], markAdded, 0);
863 }
864
865
addLetter(EditBoard * eb,int loc)866 static void addLetter(EditBoard *eb, int loc) {
867 SgfElem *search;
868 int i;
869 char letter[2];
870
871 for (search = eb->sgf->active; search && (search->type != sgfType_node);
872 search = search->parent) {
873 if ((search->type == sgfType_label) &&
874 (goBoard_sgf2Loc(eb->game->board, search->lVal) == loc)) {
875 if (eb->sgf->active == search)
876 eb->sgf->active = search->parent;
877 sgfElem_snip(search, eb->sgf);
878 grid_setMark(eb->goban->pic->boardButs[loc], goMark_none, 0);
879 return;
880 }
881 }
882 letter[0] = 'A';
883 letter[1] = '\0';
884 for (i = 0; i < goBoard_area(eb->game->board); ++i) {
885 if (eb->goban->pic->boardButs[i]) {
886 if ((grid_markType(eb->goban->pic->boardButs[i]) == goMark_letter) &&
887 (grid_markAux(eb->goban->pic->boardButs[i]) >= letter[0]))
888 letter[0] = grid_markAux(eb->goban->pic->boardButs[i]) + 1;
889 }
890 }
891 if (letter[0] == 'Z' + 1)
892 letter[0] = 'a';
893 else if (letter[0] > 'z')
894 letter[0] = 'z';
895 eb->sgf->mode = sgfInsert_inline;
896 sgf_label(eb->sgf, goBoard_loc2Sgf(eb->game->board, loc), letter);
897 eb->sgf->mode = sgfInsert_variant;
898 grid_setMark(eb->goban->pic->boardButs[loc], goMark_letter, letter[0]);
899 }
900
901
addNumber(EditBoard * eb,int loc,bool moveNum)902 static bool addNumber(EditBoard *eb, int loc, bool moveNum) {
903 SgfElem *search;
904 int i, val;
905 char labelStr[4];
906 const char *sgfLoc;
907
908 for (search = eb->sgf->active; search && (search->type != sgfType_node);
909 search = search->parent) {
910 if ((search->type == sgfType_label) &&
911 (goBoard_sgf2Loc(eb->game->board, search->lVal) == loc)) {
912 if (eb->sgf->active == search)
913 eb->sgf->active = search->parent;
914 sgfElem_snip(search, eb->sgf);
915 grid_setMark(eb->goban->pic->boardButs[loc], goMark_none, 0);
916 return(TRUE);
917 }
918 }
919 if (moveNum) {
920 for (val = eb->game->moveNum - 1; val >= 0; --val) {
921 if (eb->game->moves[val].move == loc)
922 break;
923 }
924 if (val < 0)
925 return(FALSE);
926 else
927 ++val;
928 } else {
929 val = 1;
930 for (i = 0; i < goBoard_area(eb->game->board); ++i) {
931 if (eb->goban->pic->boardButs[i]) {
932 if ((grid_markType(eb->goban->pic->boardButs[i]) == goMark_number) &&
933 (grid_markAux(eb->goban->pic->boardButs[i]) >= val))
934 val = grid_markAux(eb->goban->pic->boardButs[i]) + 1;
935 }
936 }
937 }
938 if (val > 999)
939 val = 999;
940 sprintf(labelStr, "%d", val);
941 eb->sgf->mode = sgfInsert_inline;
942 sgfLoc = goBoard_loc2Sgf(eb->game->board, loc);
943 sgf_label(eb->sgf, sgfLoc, labelStr);
944 eb->sgf->mode = sgfInsert_variant;
945 grid_setMark(eb->goban->pic->boardButs[loc], goMark_number, val);
946 return(TRUE);
947 }
948
949
markGroup(EditBoard * eb,SgfType type,int loc)950 static bool markGroup(EditBoard *eb, SgfType type, int loc) {
951 GoMarkType oldMark;
952 SetMarkAction action;
953 GoBoardGroupIter i;
954
955 if (!goStone_isStone(goBoard_stone(eb->game->board, loc)))
956 return(FALSE);
957 oldMark = grid_markType(eb->goban->pic->boardButs[loc]);
958 if ((oldMark == goMark_triangle) || (oldMark == goMark_square) ||
959 (oldMark == goMark_circle))
960 action = setMark_forceOff;
961 else
962 action = setMark_forceOn;
963 goBoardGroupIter(i, eb->game->board, loc) {
964 setMark(eb, type, goBoardGroupIter_loc(i, eb->game->board), action);
965 }
966 return(TRUE);
967 }
968
969
shiftPressed(But * but,bool press)970 static ButOut shiftPressed(But *but, bool press) {
971 EditBoard *eb = but_packet(but);
972 ButOut result;
973
974 assert(MAGIC(eb));
975 /*
976 * Alas, the modifier flags don't change until AFTER the press, so we
977 * have to invert the shift here.
978 */
979 eb->invertShift = TRUE;
980 /*
981 * Change the oldTool thingy so we don't change color if we're on the
982 * play tool.
983 */
984 eb->oldTool = editTool_changeBoard;
985 result = newTool(but_packet(but));
986 eb->invertShift = FALSE;
987 return(result);
988 }
989
990
newTool(void * packet)991 static ButOut newTool(void *packet) {
992 EditBoard *eb = packet;
993 bool shiftPressed, updateGoban = FALSE;
994 uint pressAllowed = goPicMove_empty | goPicMove_stone;
995 GoGameState gameState = goGameState_play;
996
997 assert(MAGIC(eb));
998 shiftPressed = butEnv_keyModifiers(eb->cg->env) & ShiftMask;
999 if (eb->invertShift)
1000 shiftPressed = !shiftPressed;
1001 switch(eb->tools.tool) {
1002 case editTool_play:
1003 if (!shiftPressed)
1004 pressAllowed = goPicMove_legal;
1005 if (eb->oldTool == editTool_play) {
1006 /*
1007 * Change whose turn it is.
1008 */
1009 changeWhoseMove(eb);
1010 updateGoban = TRUE;
1011 }
1012 break;
1013 case editTool_changeBoard:
1014 if (!shiftPressed)
1015 pressAllowed |= goPicMove_forceWhite;
1016 else
1017 pressAllowed |= goPicMove_forceBlack;
1018 break;
1019 case editTool_score:
1020 pressAllowed = goPicMove_stone | goPicMove_noPass;
1021 gameState = goGameState_selectDead;
1022 break;
1023 case editTool_triangle:
1024 case editTool_square:
1025 case editTool_circle:
1026 case editTool_number:
1027 pressAllowed |= goPicMove_noPass;
1028 if (shiftPressed)
1029 pressAllowed = goPicMove_stone;
1030 break;
1031 case editTool_letter:
1032 pressAllowed |= goPicMove_noPass;
1033 break;
1034 }
1035 eb->oldTool = eb->tools.tool;
1036 if (pressAllowed != eb->goban->pic->allowedMoves) {
1037 eb->goban->pic->allowedMoves = pressAllowed;
1038 updateGoban = TRUE;
1039 }
1040 if (gameState != eb->game->state) {
1041 eb->game->state = gameState;
1042 if (eb->game->state == goGameState_play) {
1043 /*
1044 * Replay the game to get rid of all those nasty territory markers.
1045 */
1046 sgf_play(eb->sgf, eb->game, eb->goban->pic, eb->currentNodeNum,
1047 NULL);
1048 }
1049 updateGoban = TRUE;
1050 }
1051 if (updateGoban)
1052 goban_update(eb->goban);
1053 return(0);
1054 }
1055
1056
newActiveSgfNode(void * packet,int nodeNum)1057 static ButOut newActiveSgfNode(void *packet, int nodeNum) {
1058 EditBoard *eb = packet;
1059
1060 assert(MAGIC(eb));
1061 if (eb->game->state == goGameState_selectDead) {
1062 abutMsg_winCreate(eb->cg->abut, "Cgoban Error",
1063 msg_changeNodeWhileScoring);
1064 return(BUTOUT_ERR);
1065 } else {
1066 readGobanComments(eb);
1067 sgf_play(eb->sgf, eb->game, eb->goban->pic, nodeNum, NULL);
1068 eb->currentNodeNum = nodeNum;
1069 editToolWin_newActiveNode(&eb->tools, eb->sgf->active);
1070 writeGobanComments(eb);
1071 editToolWin_newColor(&eb->tools, goGame_whoseMove(eb->game));
1072 goban_update(eb->goban);
1073 return(0);
1074 }
1075 }
1076
1077
changeWhoseMove(EditBoard * eb)1078 static void changeWhoseMove(EditBoard *eb) {
1079 SgfElem *oldSetTurn;
1080 GoStone newColor;
1081
1082 newColor = goStone_opponent(eb->game->whoseMove);
1083 oldSetTurn = sgfElem_findTypeInNode(eb->sgf->active, sgfType_whoseMove);
1084 if (oldSetTurn) {
1085 assert(oldSetTurn->gVal != newColor);
1086 oldSetTurn->gVal = newColor;
1087 } else {
1088 eb->sgf->mode = sgfInsert_inline;
1089 sgf_setWhoseMove(eb->sgf, newColor);
1090 eb->sgf->mode = sgfInsert_variant;
1091 }
1092 eb->game->setWhoseMoveNum = eb->game->moveNum;
1093 eb->game->whoseMove = eb->game->setWhoseMoveColor = newColor;
1094 editToolWin_newColor(&eb->tools, newColor);
1095 but_draw(eb->tools.selDesc[editTool_play]);
1096 }
1097
1098
reallyQuit(But * but)1099 static ButOut reallyQuit(But *but) {
1100 editBoard_destroy(but_packet(but));
1101 return(0);
1102 }
1103
1104
dontQuit(But * but)1105 static ButOut dontQuit(But *but) {
1106 EditBoard *eb = but_packet(but);
1107
1108 assert(MAGIC(eb));
1109 abutMsg_destroy(eb->reallyQuit, FALSE);
1110 eb->reallyQuit = NULL;
1111 return(0);
1112 }
1113
1114
quitWinDead(void * packet)1115 static ButOut quitWinDead(void *packet) {
1116 EditBoard *eb = packet;
1117
1118 assert(MAGIC(eb));
1119 eb->reallyQuit = NULL;
1120 return(0);
1121 }
1122
1123
gameInfoPressed(void * packet)1124 static GobanOut gameInfoPressed(void *packet) {
1125 EditBoard *eb = packet;
1126
1127 assert(MAGIC(eb));
1128 if (eb->info) {
1129 XRaiseWindow(butEnv_dpy(eb->cg->env),
1130 butWin_xwin(eb->info->win));
1131 } else {
1132 eb->info = editInfo_create(eb->cg, eb->goban, eb->sgf, infoDead, eb);
1133 }
1134 return(gobanOut_noDraw);
1135 }
1136
1137
infoDead(EditInfo * info,void * packet)1138 static void infoDead(EditInfo *info, void *packet) {
1139 EditBoard *eb = packet;
1140
1141 assert(MAGIC(eb));
1142 assert(eb->info == info);
1143 eb->info = NULL;
1144 }
1145