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