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