1 /*
2 
3     eboard - chess client
4     http://www.bergo.eng.br/eboard
5     https://github.com/fbergo/eboard
6     Copyright (C) 2000-2016 Felipe Bergo
7     fbergo/at/gmail/dot/com
8 
9     This program is free software; you can redistribute it and/or modify
10     it under the terms of the GNU General Public License as published by
11     the Free Software Foundation; either version 2 of the License, or
12     (at your option) any later version.
13 
14     This program is distributed in the hope that it will be useful,
15     but WITHOUT ANY WARRANTY; without even the implied warranty of
16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17     GNU General Public License for more details.
18 
19     You should have received a copy of the GNU General Public License
20     along with this program; if not, write to the Free Software
21     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22 
23 */
24 
25 #include <iostream>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <math.h>
29 #include <string.h>
30 #include "seekgraph.h"
31 #include "global.h"
32 #include "eboard.h"
33 
34 gboolean skg2_expose(GtkWidget *w, GdkEventExpose *ee,gpointer data);
35 gboolean skg2_click(GtkWidget *w, GdkEventButton *be,gpointer data);
36 gboolean skg2_cfg(GtkWidget *w, GdkEventConfigure *ee,gpointer data);
37 gboolean skg2_hover(GtkWidget *w, GdkEventMotion *ee,gpointer data);
38 bool seekcmp_id(const SeekAd *a, const SeekAd *b);
39 bool seekcmp_pos(const SeekAd *a, const SeekAd *b);
40 
seekcmp_id(const SeekAd * a,const SeekAd * b)41 bool seekcmp_id(const SeekAd *a, const SeekAd *b) {
42   return (a->id < b->id);
43 }
44 
seekcmp_pos(const SeekAd * a,const SeekAd * b)45 bool seekcmp_pos(const SeekAd *a, const SeekAd *b) {
46   if (a->y > b->y) return true;
47   if (a->y < b->y) return false;
48   return (a->x < b->x);
49 }
50 
skg_refresh(GtkWidget * w,gpointer data)51 static void skg_refresh(GtkWidget *w, gpointer data) {
52   if (global.protocol!=NULL)
53     global.protocol->refreshSeeks(false);
54 }
55 
skg_sort_num(GtkCList * clist,gconstpointer ptr1,gconstpointer ptr2)56 static gint skg_sort_num(GtkCList *clist,
57 			 gconstpointer ptr1, gconstpointer ptr2)
58 {
59   char *t1, *t2;
60   int i1, i2;
61 
62   GtkCListRow *row1 = (GtkCListRow *) ptr1;
63   GtkCListRow *row2 = (GtkCListRow *) ptr2;
64 
65   t1 = GTK_CELL_TEXT (row1->cell[clist->sort_column])->text;
66   t2 = GTK_CELL_TEXT (row2->cell[clist->sort_column])->text;
67 
68   i1 = atoi(t1);
69   i2 = atoi(t2);
70 
71   if (i1<i2) return -1;
72   if (i1>i2) return 1;
73   return 0;
74 }
75 
skg_sort_time(GtkCList * clist,gconstpointer ptr1,gconstpointer ptr2)76 static gint skg_sort_time(GtkCList *clist,
77 			 gconstpointer ptr1, gconstpointer ptr2)
78 {
79   char t1[20], t2[20];
80   char *p;
81   int i1, i2, j1, j2;
82 
83   GtkCListRow *row1 = (GtkCListRow *) ptr1;
84   GtkCListRow *row2 = (GtkCListRow *) ptr2;
85 
86   g_strlcpy(t1, GTK_CELL_TEXT (row1->cell[clist->sort_column])->text, 20);
87   g_strlcpy(t2, GTK_CELL_TEXT (row2->cell[clist->sort_column])->text, 20);
88 
89   p=strtok(t1, " \t");
90   if (p) {
91     i1=atoi(p);
92     p=strtok(0," \t");
93     if (p) j1=atoi(p); else j1=0;
94   } else i1=j1=0;
95 
96   p=strtok(t2, " \t");
97   if (p) {
98     i2=atoi(p);
99     p=strtok(0," \t");
100     if (p) j2=atoi(p); else j2=0;
101   } else i2=j2=0;
102 
103   if (i1<i2) return -1;
104   if (i1>i2) return 1;
105   if (j1<j2) return -1;
106   if (j1>j2) return 1;
107   return 0;
108 }
109 
110 
SeekAd()111 SeekAd::SeekAd() {
112   id=32767;
113   rated=false;
114   automatic=false;
115   formula=false;
116   clock=incr=0;
117   x=y=sw=lx=ly=lw=lh=-1;
118 }
119 
operator =(const SeekAd & b)120 SeekAd & SeekAd::operator=(const SeekAd &b) {
121   id = b.id;
122   clock = b.clock;
123   incr = b.incr;
124   rated = b.rated;
125   automatic = b.automatic;
126   formula = b.formula;
127 
128   color = b.color;
129   rating = b.rating;
130   player = b.player;
131   range = b.range;
132   kind = b.kind;
133   flags = b.flags;
134   x = b.x;
135   y = b.y;
136   return(*this);
137 }
138 
operator ==(int v)139 int SeekAd::operator==(int v) {
140   return(id==v);
141 }
142 
getRating()143 int SeekAd::getRating() {
144   return(atoi(rating.c_str()));
145 }
146 
getEtime()147 float SeekAd::getEtime() {
148   return(clock + (2.0f*incr)/3.0f);
149 }
150 
isComputer()151 bool SeekAd::isComputer() {
152   return(flags.find("(C)",0)!=string::npos);
153 }
154 
distance(int px,int py)155 int SeekAd::distance(int px,int py) {
156   return((int)(sqrt( (px-x)*(px-x) + (py-y)*(py-y) )));
157 }
158 
getKind()159 variant SeekAd::getKind() {
160   if (!kind.compare("blitz") ||
161       !kind.compare("lightning") ||
162       !kind.compare("standard")) return REGULAR;
163   if (!kind.compare("crazyhouse")) return CRAZYHOUSE;
164   if (!kind.compare("suicide")) return SUICIDE;
165   if (!kind.compare("giveaway")) return GIVEAWAY;
166   if (!kind.compare("losers")) return LOSERS;
167   if (!kind.compare("atomic")) return ATOMIC;
168   return WILD;
169 }
170 
SeekGraph2()171 SeekGraph2::SeekGraph2() {
172 
173   boxid = -1;
174   widget = gtk_drawing_area_new();
175   gc = NULL;
176   pix = NULL;
177   lw=lh=mx=my=-1;
178   gtk_widget_set_events(widget,GDK_EXPOSURE_MASK|GDK_BUTTON_PRESS_MASK|
179 			GDK_POINTER_MOTION_MASK);
180 
181   gtk_signal_connect (GTK_OBJECT (widget), "expose_event",
182                       (GtkSignalFunc) skg2_expose, (gpointer) this);
183   gtk_signal_connect (GTK_OBJECT (widget), "configure_event",
184                       (GtkSignalFunc) skg2_cfg, (gpointer) this);
185   gtk_signal_connect (GTK_OBJECT (widget), "button_press_event",
186                       (GtkSignalFunc) skg2_click, (gpointer) this);
187   gtk_signal_connect (GTK_OBJECT (widget), "motion_notify_event",
188                       (GtkSignalFunc) skg2_hover, (gpointer) this);
189 
190 }
191 
~SeekGraph2()192 SeekGraph2::~SeekGraph2() {
193   clear();
194   if (pix!=NULL)
195     gdk_pixmap_unref(pix);
196   if (gc!=NULL)
197     gdk_gc_destroy(gc);
198 }
199 
rehover()200 void SeekGraph2::rehover() {
201   GdkEventMotion em;
202   em.x = mx;
203   em.y = my;
204   boxid = -2;
205   placeAds();
206   skg2_hover(widget,&em,(gpointer)this);
207 }
208 
remove(int id)209 void SeekGraph2::remove(int id) {
210   unsigned int i,n;
211   n = ads.size();
212   for(i=0;i<n;i++)
213     if (ads[i]->id == id) {
214       delete(ads[i]);
215       ads.erase(ads.begin() + i);
216       break;
217     }
218   //  contentUpdated();
219   rehover();
220   repaint();
221 }
222 
add(SeekAd * ad)223 void SeekGraph2::add(SeekAd *ad) {
224   ads.push_back(ad);
225   //  contentUpdated();
226   rehover();
227   repaint();
228 
229 }
230 
clear()231 void SeekGraph2::clear() {
232   unsigned int i,n;
233   n = ads.size();
234   for(i=0;i<n;i++)
235     delete(ads[i]);
236   ads.clear();
237   boxid = -1;
238   //  contentUpdated();
239   rehover();
240   repaint();
241 }
242 
updateFont()243 void SeekGraph2::updateFont() {
244   unsigned int i,n;
245   lw=-1;
246   n = ads.size();
247   for(i=0;i<n;i++) ads[i]->sw = -1;
248   repaint();
249 }
250 
skg2_click(GtkWidget * w,GdkEventButton * be,gpointer data)251 gboolean skg2_click(GtkWidget *w, GdkEventButton *be,gpointer data) {
252 
253   SeekGraph2 *me = (SeekGraph2 *) data;
254   char z[64];
255 
256   if (me->boxid >= 0 && global.protocol && be->button==1) {
257     snprintf(z,64,"play %d",me->boxid);
258     global.protocol->sendUserInput(z);
259     snprintf(z,64,_("Replied to seek #%d"),me->boxid);
260     global.status->setText(z,10);
261   } else if (global.protocol && be->button==3) {
262     global.protocol->refreshSeeks(false);
263   }
264 
265   return TRUE;
266 }
267 
skg2_cfg(GtkWidget * w,GdkEventConfigure * ee,gpointer data)268 gboolean skg2_cfg(GtkWidget *w, GdkEventConfigure *ee,gpointer data) {
269   int ww,wh;
270   SeekGraph2 *me = (SeekGraph2 *) data;
271   LayoutBox *L;
272 
273   gdk_window_get_size(w->window, &ww, &wh);
274 
275   L = &(me->L);
276 
277   if (me->lw != ww || me->lh != wh) {
278     if (me->pix != NULL) {
279       gdk_pixmap_unref(me->pix);
280       me->pix = NULL;
281     }
282     if (me->gc != NULL) {
283       gdk_gc_destroy(me->gc);
284       me->gc = NULL;
285     }
286   }
287 
288   if (me->pix == NULL) {
289     me->pix = gdk_pixmap_new(w->window,ww,wh,-1);
290     me->lw = ww;
291     me->lh = wh;
292     me->gc = gdk_gc_new(me->pix);
293     L->prepare(w,me->pix,me->gc,0,0,ww,wh);
294     L->setFont(0,global.SeekFont);
295   }
296 
297   me->draw();
298   return TRUE;
299 }
300 
placeAds()301 void SeekGraph2::placeAds() {
302   unsigned int i,n;
303   int j,lx,ly,qw,qh;
304   int fh=0;
305 
306   n = ads.size();
307   sort(ads.begin(),ads.end(),&seekcmp_id);
308   for(i=0;i<n;i++)
309     ads[i]->lx = ads[i]->lw = ads[i]->ly = ads[i]->lh = -1;
310 
311   if (gc!=NULL)
312     fh = L.fontHeight(0);
313 
314   for(i=0;i<n;i++) {
315     int rat;
316     float etime;
317     etime = ads[i]->getEtime();
318     rat = ads[i]->getRating();
319     if (etime > 60.0f) etime = 60.0f;
320     if (rat > 3000) rat = 3000;
321     if (rat < 200) rat = 200;
322     ads[i]->y = lh - ((lh * rat) / 3000);
323     ads[i]->x = (int)((sqrt(etime)*lw) / 8.0);
324 
325     // avoid dot overlaps
326     for(j=0;j<(int)i;j++)
327       if (ads[i]->distance(ads[j]->x,ads[j]->y) < 12) {
328 	ads[i]->y -= 12;
329 	j = -1;
330       }
331   }
332 
333   sort(ads.begin(),ads.end(),&seekcmp_pos);
334 
335   for(i=0;i<n;i++) {
336     if (gc!=NULL) {
337       if (ads[i]->sw < 0)
338 	qw = ads[i]->sw = L.stringWidth(0,ads[i]->player.c_str());
339       else
340 	qw = ads[i]->sw;
341       qh = fh;
342       lx = ads[i]->x + 8;
343       ly = ads[i]->y - fh/2;
344 
345       for(j=0;j<=200;j+=10)
346 	if (rectFree(lx,ly-j,qw,qh)) {
347 	  ads[i]->lx = lx;
348 	  ads[i]->ly = ly-j;
349 	  ads[i]->lw = qw;
350 	  ads[i]->lh = qh;
351 	  break;
352 	} else if (rectFree(lx,ly+j,qw,qh)) {
353 	  ads[i]->lx = lx;
354 	  ads[i]->ly = ly+j;
355 	  ads[i]->lw = qw;
356 	  ads[i]->lh = qh;
357 	  break;
358 	}
359       if (ads[i]->lx < 0) {
360 	    ads[i]->lx = lx;
361 	    ads[i]->ly = ly;
362 	    ads[i]->lw = qw;
363 	    ads[i]->lh = qh;
364       }
365 
366 
367     }
368   }
369 
370   sort(ads.begin(),ads.end(),&seekcmp_id);
371 }
372 
rectFree(int x,int y,int w,int h)373 bool SeekGraph2::rectFree(int x,int y,int w,int h) {
374   unsigned int i,n;
375   n = ads.size();
376   for(i=0;i<n;i++) {
377     if (rectOverlap(x,y,w,h,ads[i]->x-4,ads[i]->y-4,9,9))
378       return false;
379     if (ads[i]->lx >= 0)
380       if (rectOverlap(x,y,w,h,ads[i]->lx,ads[i]->ly,ads[i]->lw,ads[i]->lh))
381 	return false;
382   }
383   return true;
384 }
385 
rectOverlap(int rx,int ry,int rw,int rh,int sx,int sy,int sw,int sh)386 bool SeekGraph2::rectOverlap(int rx,int ry,int rw, int rh,
387 		 int sx,int sy,int sw, int sh) {
388 
389   return (intervalOverlap(rx,rx+rw,sx,sx+sw)&&
390 	  intervalOverlap(ry,ry+rh,sy,sy+sh));
391 }
392 
intervalOverlap(int a1,int a2,int b1,int b2)393 bool SeekGraph2::intervalOverlap(int a1, int a2, int b1, int b2) {
394   return ((a1 >= b1 && a1 <= b2) ||
395 	  (a2 >= b1 && a2 <= b2) ||
396 	  (b1 >= a1 && b1 <= a2) ||
397 	  (b2 >= a1 && b2 <= a2));
398 }
399 
draw()400 void SeekGraph2::draw() {
401   int W,H,RW,x,y,lb,bs,lx;
402   int a,fh,r;
403   char z[32];
404   unsigned int i,n;
405   SeekAd *j;
406 
407   int bg[6] = { 0xffd0d0, 0xeec0c0,
408 		0xffe0d0, 0xeed0c0,
409 		0xffffe0, 0xeeeed0 };
410 
411   int bg2[6] = { 0xddb0b0, 0xeec0c0,
412 		 0xddc0b0, 0xeed0c0,
413 		 0xddddc0, 0xeeeed0 };
414 
415 
416   W = lw; H = lh;
417 
418   RW = W;
419 
420   fh = L.fontHeight(0);
421 
422   // main panes
423   L.setColor(0xffffff);
424   L.drawRect(0,0,W,H,true);
425 
426   lb = (int)((sqrt(2.90) * RW)/8.0);
427   bs = (int)((sqrt(14.90) * RW)/8.0);
428 
429   // lightning
430   L.setColor(bg[0]);
431   L.drawRect(0,0,lb,H,true);
432 
433   // blitz
434   L.setColor(bg[2]);
435   L.drawRect(lb,0,bs-lb,H,true);
436 
437   // standard
438   L.setColor(bg[4]);
439   L.drawRect(bs,0,RW-bs,H,true);
440 
441 
442   for(a=500;a<3000;a+=1000) {
443     y = H - ((a*H) / 3000);
444     x = (H*500)/3000;
445     L.setColor(bg[1]);
446     L.drawRect(0,y,lb,x,true);
447     L.setColor(bg[3]);
448     L.drawRect(lb,y,bs-lb,x,true);
449     L.setColor(bg[5]);
450     L.drawRect(bs,y,RW-bs,x,true);
451   }
452 
453   for(a=100;a<3000;a+=100) {
454     y = H - ((a*H) / 3000);
455     L.setColor(bg2[ (a/500)%2 != 0 ? 1 : 0 ]);
456     L.drawLine(0,y,lb,y);
457     L.setColor(bg2[ (a/500)%2 != 0 ? 3 : 2 ]);
458     L.drawLine(lb,y,bs,y);
459     L.setColor(bg2[ (a/500)%2 != 0 ? 5 : 4 ]);
460     L.drawLine(bs,y,RW,y);
461   }
462 
463   // box it
464 
465   // vertical scale
466   L.setColor(0x444444);
467   lx = 0;
468 
469   int hval[12] = { 1, 2, 3, 5, 10, 15, 20, 25, 30, 40, 50, 60 };
470 
471   for(a=0;a<12;a++) {
472     x = (int)((sqrt(hval[a]) * RW)/8.0);
473     if (x > lx) {
474       snprintf(z,32,"%dm",hval[a]);
475       L.drawString(x+2,H-fh,0,z);
476       L.drawLine(x,H,x,H-fh);
477       lx = x + L.stringWidth(0,z) + 10;
478     }
479   }
480 
481   // horizontal scale
482   L.setColor(0x444444);
483   for(a=500;a<3000;a+=500) {
484     y = H - ((a*H) / 3000);
485     snprintf(z,32,"%d",a);
486     x = L.stringWidth(0,z);
487     L.drawString(W-x-10,y-fh-2,0,z);
488     L.drawLine(W-20,y,W,y);
489   }
490 
491   L.setColor(0x884444);
492   L.drawString(10,1,0,_("Left click to play, right click to refresh."));
493 
494   L.setColor(0);
495   L.drawRect(0,0,RW,H,false);
496 
497   placeAds();
498   n = ads.size();
499   for(i=0;i<n;i++) {
500     L.setColor(0xaa9999);
501     L.drawLine(ads[i]->x,ads[i]->y,ads[i]->lx,ads[i]->ly + fh/2);
502     L.drawString(ads[i]->lx+2,ads[i]->ly,0,ads[i]->player.c_str());
503   }
504 
505   for(i=0;i<n;i++) {
506     variant v;
507 
508     v = ads[i]->getKind();
509     switch(v) {
510     case REGULAR: L.setColor(0x2222ee); break;
511     case CRAZYHOUSE: L.setColor(0x00eedd); break;
512     case SUICIDE: L.setColor(0xff2222); break;
513     case LOSERS:
514     case GIVEAWAY: L.setColor(0x882222); break;
515     case ATOMIC: L.setColor(0x22ff22); break;
516     case WILD: L.setColor(0xffff00); break;
517     default: L.setColor(0x888888);
518     }
519 
520     r = (ads[i]->id == boxid) ? 6 : 4;
521 
522     bool isac = ads[i]->isComputer();
523 
524     if (isac)
525       L.drawRect(ads[i]->x-r,ads[i]->y-r,2*r+1,2*r+1,true);
526     else
527       L.drawEllipse(ads[i]->x-r,ads[i]->y-r,2*r+1,2*r+1,true);
528     L.setColor(ads[i]->rated ? 0 : 0xffffff);
529     if (isac)
530       L.drawRect(ads[i]->x-r,ads[i]->y-r,2*r+1,2*r+1,false);
531     else
532       L.drawEllipse(ads[i]->x-r,ads[i]->y-r,2*r+1,2*r+1,false);
533   }
534 
535   if (boxid < 0) return;
536   j = getAd(boxid);
537   if (j==NULL) return;
538 
539   int bx,by,bw,bh;
540 
541   char lines[3][100];
542   snprintf(lines[0],100,"%s%s %s",
543 	   j->player.c_str(),
544 	   j->flags.c_str(),
545 	   j->rating.c_str());
546   snprintf(lines[1],100,"%d %d %s %s (%s)",
547 	   j->clock,j->incr,
548 	   j->rated ? _("rated") :
549 	   _("unrated"),
550 	   j->kind.c_str(),
551 	   j->range.c_str());
552   snprintf(lines[2],100,"%c/%c %s",
553 	   j->automatic ? '-' : 'm',
554 	   j->formula ? 'f' : '-',
555 	   j->color.c_str());
556   x = L.stringWidth(0,lines[0]);
557   y = L.stringWidth(0,lines[1]);
558   if (x < y) x = y;
559   y = L.stringWidth(0,lines[2]);
560   if (x < y) x = y;
561 
562   bw = x+20;
563   bh = 3*(fh+2)+2;
564   if (j->x < RW/2) bx = j->x + 20; else bx = j->x - 20 - bw;
565   by = j->y - bh/2;
566   if (by < 0) by = 0;
567   if (by + bh > H) by = H - bh;
568 
569   L.setColor(0);
570   L.drawLine(j->x,j->y,bx+bw/2,by+bh/2);
571 
572   L.setColor(0x888877);
573   L.drawRect(bx+5,by+5,bw,bh,true);
574   L.setColor(0xffffff);
575   L.drawRect(bx,by,bw,bh,true);
576   L.setColor(0);
577   L.drawRect(bx,by,bw,bh,false);
578 
579   L.drawString(bx+10,by+2,0,lines[0]);
580   L.drawString(bx+10,by+2+(fh+2),0,lines[1]);
581   L.drawString(bx+10,by+2+2*(fh+2),0,lines[2]);
582 
583 }
584 
getAd(int id)585 SeekAd *SeekGraph2::getAd(int id) {
586   unsigned int i,n;
587   n = ads.size();
588   for(i=0;i<n;i++)
589     if (ads[i]->id == id)
590       return(ads[i]);
591   return NULL;
592 }
593 
skg2_expose(GtkWidget * w,GdkEventExpose * ee,gpointer data)594 gboolean skg2_expose(GtkWidget *w, GdkEventExpose *ee,gpointer data) {
595   SeekGraph2 *me = (SeekGraph2 *) data;
596 
597   if (me->pix == NULL) return FALSE;
598   gdk_draw_pixmap(w->window,w->style->black_gc,
599                   me->pix,
600                   ee->area.x, ee->area.y,
601                   ee->area.x, ee->area.y,
602                   ee->area.width, ee->area.height);
603   return TRUE;
604 }
605 
skg2_hover(GtkWidget * w,GdkEventMotion * ee,gpointer data)606 gboolean skg2_hover(GtkWidget *w, GdkEventMotion *ee,gpointer data) {
607   SeekGraph2 *me = (SeekGraph2 *) data;
608   int id,md,d,x,y;
609   unsigned int i,n;
610 
611 
612   if (me->ads.empty()) {
613     id = -1;
614     if (id != me->boxid) {
615       me->boxid = id;
616       me->repaint();
617     }
618     return TRUE;
619   }
620 
621   x = me->mx = (int) (ee->x);
622   y = me->my = (int) (ee->y);
623 
624   md = me->ads[0]->distance(x,y);
625   id = me->ads[0]->id;
626 
627   n = me->ads.size();
628   for(i=0;i<n;i++) {
629     d = me->ads[i]->distance(x,y);
630     if (d < md) { md = d; id = me->ads[i]->id; }
631   }
632 
633   if (md > 100)
634     id = -1;
635 
636   if (id != me->boxid) {
637     me->boxid = id;
638     me->repaint();
639   }
640 
641   return TRUE;
642 }
643