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 <string.h>
28 #include <stdlib.h>
29 #include <gdk/gdkkeysyms.h>
30 #include "text.h"
31 #include "widgetproxy.h"
32 #include "global.h"
33 #include "tstring.h"
34 #include "stl.h"
35 #include "eboard.h"
36
37 #include "xpm/icon-console.xpm"
38 #include "xpm/addcons.xpm"
39
40 gboolean dc_entry_focus_out(GtkWidget *widget,GdkEventFocus *event,
41 gpointer user_data);
42 gboolean dc_entry_force_focus(gpointer data);
43
~OutputPane()44 OutputPane::~OutputPane() {
45
46 }
47
Text()48 Text::Text() {
49 GdkFont *fxd;
50
51 linecount=0;
52 LastMatch=-1;
53
54 setBG(global.Colors.Background);
55 setFont(global.ConsoleFont);
56 updateScrollBack();
57 }
58
~Text()59 Text::~Text() {
60
61 }
62
execSearch()63 void Text::execSearch() {
64 LastMatch = -1;
65 findTextNext();
66 }
67
findTextNext()68 void Text::findTextNext() {
69 char z[256];
70 bool wrapped = false;
71
72 if (SearchString.empty()) {
73 global.status->setText(_("No previous search."),5);
74 return;
75 }
76
77 if (LastMatch == 0) wrapped = true;
78 if (LastMatch < 0) LastMatch = 0;
79
80 if (findTextUpward(0,LastMatch - 1,SearchString.c_str())) {
81
82 LastMatch = getLastFoundLine();
83 global.ebook->goToPageId(-2);
84 gotoLine(LastMatch);
85
86 if (!wrapped)
87 snprintf(z,256,_("Match Found at Line %d."), LastMatch+1);
88 else
89 snprintf(z,256,_("(Wrapped) Match Found at Line %d."), LastMatch+1);
90 global.status->setText(z,10);
91
92 } else {
93
94 LastMatch = -1;
95 global.status->setText(_("Search text not found."),5);
96
97 }
98 }
99
saveBuffer()100 void Text::saveBuffer() {
101 FileDialog *fd;
102
103 fd=new FileDialog(_("Save Buffer As..."));
104
105 if (fd->run()) {
106 if (saveTextBuffer(fd->FileName)) {
107 global.status->setText(_("Console buffer saved."),5);
108 } else {
109 global.status->setText(_("Buffer Save failed."),10);
110 }
111 }
112
113 delete fd;
114 }
115
updateFont()116 void Text::updateFont() {
117 setFont(global.ConsoleFont);
118 repaint();
119 }
120
updateScrollBack()121 void Text::updateScrollBack() {
122 setScrollBack(global.ScrollBack);
123 }
124
pageUp()125 void Text::pageUp() { NText::pageUp(0.75f); }
pageDown()126 void Text::pageDown() { NText::pageDown(0.75f); }
127
append(const char * msg,int color,Importance imp)128 void Text::append(const char *msg,int color,Importance imp) {
129 if (Filter.accept(msg)) {
130 pushLevel(imp);
131 NText::append(msg,strlen(msg),color);
132 contentUpdated();
133 repaint();
134 }
135 }
136
append(const char * msg,const char * msg2,int color,Importance imp)137 void Text::append(const char *msg,const char *msg2,int color, Importance imp) {
138 char *d;
139 d=(char *)g_malloc0(strlen(msg)+strlen(msg2)+1);
140 strcpy(d,msg);
141 strcat(d,msg2);
142 append(d,color,imp);
143 g_free(d);
144 }
145
setBackground(int color)146 void Text::setBackground(int color) {
147 setBG(color);
148 }
149
150 // -----------------
151 // textset
152 // -----------------
153
TextSet()154 TextSet::TextSet() {
155
156 }
157
~TextSet()158 TextSet::~TextSet() {
159 while(!targets.empty())
160 removeTarget(targets.front());
161 }
162
addTarget(Text * target)163 void TextSet::addTarget(Text *target) {
164 targets.push_back(target);
165 }
166
removeTarget(Text * target)167 void TextSet::removeTarget(Text *target) {
168 list<Text *>::iterator ti;
169 for(ti=targets.begin();ti!=targets.end();ti++)
170 if ( (*ti) == target ) {
171 delete(*ti);
172 targets.erase(ti);
173 return;
174 }
175 }
176
append(const char * msg,int color,Importance imp)177 void TextSet::append(const char *msg,int color,Importance imp) {
178 list<Text *>::iterator ti;
179 for(ti=targets.begin();ti!=targets.end();ti++)
180 (*ti)->append(msg,color,imp);
181 }
182
append(const char * msg,const char * msg2,int color,Importance imp)183 void TextSet::append(const char *msg,const char *msg2,int color,Importance imp) {
184 list<Text *>::iterator ti;
185 for(ti=targets.begin();ti!=targets.end();ti++)
186 (*ti)->append(msg,msg2,color,imp);
187 }
188
pageUp()189 void TextSet::pageUp() {
190 list<Text *>::iterator ti;
191 for(ti=targets.begin();ti!=targets.end();ti++)
192 (*ti)->pageUp();
193 }
194
pageDown()195 void TextSet::pageDown() {
196 list<Text *>::iterator ti;
197 for(ti=targets.begin();ti!=targets.end();ti++)
198 (*ti)->pageDown();
199 }
200
updateScrollBack()201 void TextSet::updateScrollBack() {
202 list<Text *>::iterator ti;
203 for(ti=targets.begin();ti!=targets.end();ti++)
204 (*ti)->updateScrollBack();
205 }
206
updateFont()207 void TextSet::updateFont() {
208 list<Text *>::iterator ti;
209 for(ti=targets.begin();ti!=targets.end();ti++)
210 (*ti)->updateFont();
211 }
212
setBackground(int color)213 void TextSet::setBackground(int color) {
214 list<Text *>::iterator ti;
215 for(ti=targets.begin();ti!=targets.end();ti++)
216 (*ti)->setBackground(color);
217 }
218
219 // Detached text console
220
221 int DetachedConsole::ConsoleCount=0;
222
DetachedConsole(TextSet * yourset,ConsoleListener * cl)223 DetachedConsole::DetachedConsole(TextSet *yourset, ConsoleListener *cl) {
224 GtkWidget *vb,*hb,*sf,*ac,*ae;
225 GdkPixmap *ad;
226 GdkBitmap *ab;
227 GtkStyle *style;
228 char tmp[64];
229
230 ConsoleCount++;
231 global.Consoles.push_back(this);
232
233 myset=yourset;
234 listener=cl;
235 // my copy of Stroustrup says one thing about the stringstream class
236 // and my libstdc++ says another, better not risk compiling problems,
237 // so I'm using C constructs here. Ack.
238 snprintf(tmp,64,_("eboard: Console #%d"),ConsoleCount);
239 basetitle=tmp;
240
241 widget=gtk_window_new(GTK_WINDOW_TOPLEVEL);
242 gtk_widget_set_events(widget,GDK_KEY_PRESS_MASK);
243 gtk_window_set_default_size(GTK_WINDOW(widget),650,400);
244 gtk_window_set_title(GTK_WINDOW(widget),basetitle.c_str());
245 gtk_window_set_position(GTK_WINDOW(widget),GTK_WIN_POS_CENTER);
246 gtk_container_set_border_width(GTK_CONTAINER(widget),4);
247 gtk_widget_realize(widget);
248
249 vb=gtk_vbox_new(FALSE,0);
250 gtk_container_add(GTK_CONTAINER(widget),vb);
251
252 inner=new Text();
253 gtk_box_pack_start(GTK_BOX(vb),inner->widget,TRUE,TRUE,0);
254
255 hb=gtk_hbox_new(FALSE,2);
256
257 inputbox=gtk_entry_new();
258 gtk_widget_set_events(inputbox,(GdkEventMask)(gtk_widget_get_events(inputbox)|GDK_FOCUS_CHANGE_MASK));
259
260 focus_sig_id=(int)gtk_signal_connect(GTK_OBJECT(inputbox),"focus_out_event",
261 GTK_SIGNAL_FUNC(dc_entry_focus_out),(gpointer)inputbox);
262
263 gtk_box_pack_start(GTK_BOX(vb),hb,FALSE,TRUE,4);
264 gtk_box_pack_start(GTK_BOX(hb),inputbox,TRUE,TRUE,4);
265
266 flabel=gtk_label_new(_("Filter: (none)"));
267
268 sf=gtk_button_new_with_label(_("Set Filter..."));
269 gtk_box_pack_start(GTK_BOX(hb),flabel,FALSE,FALSE,4);
270 gtk_box_pack_start(GTK_BOX(hb),sf,FALSE,FALSE,4);
271
272 // add new console
273 ac=gtk_button_new();
274 style=gtk_widget_get_style(widget);
275 ad = gdk_pixmap_create_from_xpm_d (widget->window, &ab,
276 &style->bg[GTK_STATE_NORMAL],
277 (gchar **) addcons_xpm);
278 ae=gtk_pixmap_new(ad,ab);
279 gtk_container_add(GTK_CONTAINER(ac),ae);
280 gshow(ae);
281
282 gtk_box_pack_start(GTK_BOX(hb),ac,FALSE,FALSE,4);
283
284 Gtk::show(inputbox,sf,flabel,ac,hb,vb,NULL);
285 inner->show();
286
287 yourset->addTarget(inner);
288
289 setIcon(icon_console_xpm,_("Console"));
290
291 gtk_signal_connect(GTK_OBJECT(widget),"delete_event",
292 GTK_SIGNAL_FUNC(detached_delete),(gpointer)this);
293 gtk_signal_connect(GTK_OBJECT(widget),"destroy",
294 GTK_SIGNAL_FUNC(detached_destroy),(gpointer)this);
295
296 gtk_signal_connect (GTK_OBJECT (inputbox), "key_press_event",
297 GTK_SIGNAL_FUNC (dc_input_key_press), (gpointer)this);
298
299 gtk_signal_connect (GTK_OBJECT (sf), "clicked",
300 GTK_SIGNAL_FUNC (dc_set_filter), (gpointer)this);
301
302 gtk_signal_connect (GTK_OBJECT (ac), "clicked",
303 GTK_SIGNAL_FUNC (dc_new_console), (gpointer)this);
304
305 hcursor=global.inputhistory->getCursor();
306
307 setPasswordMode(global.PasswordMode);
308 }
309
clone()310 void DetachedConsole::clone() {
311 DetachedConsole *dc;
312 dc=new DetachedConsole(myset,0);
313 //global.input->peekKeys(dc->widget);
314 dc->show();
315 }
316
~DetachedConsole()317 DetachedConsole::~DetachedConsole() {
318 myset->removeTarget(inner);
319 global.Consoles.erase( find(global.Consoles.begin(), global.Consoles.end(), this) );
320 }
321
setPasswordMode(int pm)322 void DetachedConsole::setPasswordMode(int pm) {
323 gtk_entry_set_visibility(GTK_ENTRY(inputbox),(pm?FALSE:TRUE));
324 }
325
show()326 void DetachedConsole::show() {
327 WidgetProxy::show();
328 gtk_widget_grab_focus(inputbox);
329 }
330
injectInput()331 void DetachedConsole::injectInput() {
332 global.input->userInput(gtk_entry_get_text(GTK_ENTRY(inputbox)));
333 gtk_entry_set_text(GTK_ENTRY(inputbox),"\0");
334 hcursor=global.inputhistory->getCursor();
335 }
336
historyUp()337 void DetachedConsole::historyUp() {
338 gtk_entry_set_text(GTK_ENTRY(inputbox),
339 global.inputhistory->moveUp(hcursor));
340 }
341
historyDown()342 void DetachedConsole::historyDown() {
343 gtk_entry_set_text(GTK_ENTRY(inputbox),
344 global.inputhistory->moveDown(hcursor));
345 }
346
getFilter()347 const char * DetachedConsole::getFilter() {
348 return(inner->Filter.getString());
349 }
350
setFilter(const char * s)351 void DetachedConsole::setFilter(const char *s) {
352 inner->Filter.set(s);
353 updateFilterLabel();
354 }
355
updateFilterLabel()356 void DetachedConsole::updateFilterLabel() {
357 char z[256];
358 g_strlcpy(z,_("Filter: "),256);
359 g_strlcat(z,inner->Filter.getString(),256);
360 if (strlen(z)==strlen(_("Filter: ")))
361 g_strlcat(z,_("(none)"),256);
362 gtk_label_set_text(GTK_LABEL(flabel),z);
363 gtk_widget_queue_resize(flabel);
364 }
365
366
detached_delete(GtkWidget * widget,GdkEvent * event,gpointer data)367 gint detached_delete (GtkWidget * widget, GdkEvent * event, gpointer data) {
368 DetachedConsole *me;
369 me=(DetachedConsole *)data;
370 gtk_signal_disconnect(GTK_OBJECT(me->inputbox),me->focus_sig_id);
371 return FALSE;
372 }
373
detached_destroy(GtkWidget * widget,gpointer data)374 void detached_destroy (GtkWidget * widget, gpointer data) {
375 DetachedConsole *me;
376 me=(DetachedConsole *)data;
377 if (me->listener)
378 me->listener->consoleClosed();
379 else
380 delete me;
381 }
382
dc_input_key_press(GtkWidget * wid,GdkEventKey * evt,gpointer data)383 int dc_input_key_press (GtkWidget * wid, GdkEventKey * evt,
384 gpointer data)
385 {
386 DetachedConsole *me;
387 me=(DetachedConsole *)data;
388 switch(evt->keyval) {
389 case GDK_Up:
390 gtk_signal_emit_stop_by_name(GTK_OBJECT(wid), "key_press_event");
391 me->historyUp();
392 return 1;
393 case GDK_Down:
394 gtk_signal_emit_stop_by_name(GTK_OBJECT(wid), "key_press_event");
395 me->historyDown();
396 return 1;
397 case GDK_KP_Enter:
398 gtk_signal_emit_stop_by_name(GTK_OBJECT(wid), "key_press_event");
399 case GDK_Return:
400 me->injectInput();
401 return 1;
402 case GDK_Page_Up:
403 me->inner->pageUp();
404 return 1;
405 case GDK_Page_Down:
406 me->inner->pageDown();
407 return 1;
408 default:
409 return(global.input->keyPressed(evt->keyval, evt->state));
410 }
411 return 0;
412 }
413
dc_entry_focus_out(GtkWidget * widget,GdkEventFocus * event,gpointer user_data)414 gboolean dc_entry_focus_out(GtkWidget *widget,GdkEventFocus *event,gpointer user_data)
415 {
416 gtk_timeout_add(50,dc_entry_force_focus,user_data);
417 return FALSE;
418 }
419
dc_entry_force_focus(gpointer data)420 gboolean dc_entry_force_focus(gpointer data)
421 {
422 gtk_widget_grab_focus(GTK_WIDGET(data));
423 return FALSE;
424 }
425
dc_set_filter(GtkWidget * w,gpointer data)426 void dc_set_filter(GtkWidget *w,gpointer data) {
427 DetachedConsole *me;
428 me=(DetachedConsole *)data;
429 (new TextFilterDialog(me->inner,me->flabel))->show();
430 }
431
dc_new_console(GtkWidget * w,gpointer data)432 void dc_new_console(GtkWidget *w,gpointer data) {
433 DetachedConsole *me;
434 me=(DetachedConsole *)data;
435 me->clone();
436 }
437
438 // --- set filter... dialog
439
TextFilterDialog(Text * target,GtkWidget * label2update)440 TextFilterDialog::TextFilterDialog(Text *target,GtkWidget *label2update)
441 : ModalDialog(N_("Set Filter"))
442 {
443 GtkWidget *v,*h,*lb,*lb2,*hs,*bb,*ok,*can;
444
445 obj=target;
446 ulabel=label2update;
447
448 v=gtk_vbox_new(FALSE,4);
449 gtk_container_add(GTK_CONTAINER(widget),v);
450
451 h=gtk_hbox_new(FALSE,4);
452 lb=gtk_label_new(_("Match Pattern: "));
453 lb2=gtk_label_new(_("Only lines that match the above pattern will be added\n"\
454 "to this text pane. Patterns can be OR'ed with the | (pipe)\n"\
455 "character. A * (star) can be used to match anything.\n"\
456 "Examples:\n"\
457 "'(20)|(22)' shows only lines from channels 20 and 22\n"\
458 "'blik * bored' shows lines containing 'blik '(...)' bored'."));
459 gtk_label_set_justify(GTK_LABEL(lb2),GTK_JUSTIFY_LEFT);
460
461 gtk_box_pack_start(GTK_BOX(h),lb,FALSE,FALSE,2);
462 pattern=gtk_entry_new();
463 gtk_box_pack_start(GTK_BOX(h),pattern,TRUE,TRUE,2);
464
465 gtk_box_pack_start(GTK_BOX(v),h,FALSE,FALSE,4);
466 gtk_box_pack_start(GTK_BOX(v),lb2,FALSE,FALSE,4);
467
468 hs=gtk_hseparator_new();
469 gtk_box_pack_start(GTK_BOX(v),hs,FALSE,FALSE,4);
470
471 bb=gtk_hbutton_box_new();
472 gtk_button_box_set_layout(GTK_BUTTON_BOX(bb), GTK_BUTTONBOX_END);
473 gtk_button_box_set_spacing(GTK_BUTTON_BOX(bb), 5);
474 gtk_box_pack_start(GTK_BOX(v),bb,FALSE,FALSE,2);
475
476 ok=gtk_button_new_with_label(_("Ok"));
477 GTK_WIDGET_SET_FLAGS(ok,GTK_CAN_DEFAULT);
478 can=gtk_button_new_with_label(_("Cancel"));
479 GTK_WIDGET_SET_FLAGS(can,GTK_CAN_DEFAULT);
480 gtk_box_pack_start(GTK_BOX(bb),ok,TRUE,TRUE,0);
481 gtk_box_pack_start(GTK_BOX(bb),can,TRUE,TRUE,0);
482 gtk_widget_grab_default(ok);
483
484 Gtk::show(ok,can,bb,hs,lb2,lb,pattern,h,v,NULL);
485 gtk_entry_set_text(GTK_ENTRY(pattern),obj->Filter.getString());
486
487 setDismiss(GTK_OBJECT(can),"clicked");
488 gtk_signal_connect(GTK_OBJECT(ok),"clicked",
489 GTK_SIGNAL_FUNC(tfd_ok),(gpointer)this);
490
491 focused_widget=pattern;
492 }
493
tfd_ok(GtkWidget * w,gpointer data)494 void tfd_ok(GtkWidget *w, gpointer data)
495 {
496 TextFilterDialog *me;
497 char z[1024];
498 me=(TextFilterDialog *)data;
499 me->obj->Filter.set(gtk_entry_get_text(GTK_ENTRY(me->pattern)));
500
501 if (me->ulabel) {
502 g_strlcpy(z,_("Filter: "),1024);
503 g_strlcat(z,me->obj->Filter.getString(),1024);
504 if (strlen(z)==strlen(_("Filter: ")))
505 g_strlcat(z,_("(none)"),1024);
506 gtk_label_set_text(GTK_LABEL(me->ulabel),z);
507 gtk_widget_queue_resize(me->ulabel);
508 }
509
510 me->release();
511 }
512
513 // --- text filter class
514
TextFilter()515 TextFilter::TextFilter() {
516 FilterString="";
517 AcceptedLast=false;
518 }
519
~TextFilter()520 TextFilter::~TextFilter() {
521 cleanUp();
522 }
523
set(const char * t)524 void TextFilter::set(const char *t) {
525 tstring T;
526 ExtPatternMatcher *epm;
527 char local[1024],single[512];
528 const char *p;
529 char *q;
530 string *P;
531
532 cleanUp();
533 if (!strlen(t))
534 return;
535
536 memset(local,0,1024);
537
538 for(p=t,q=local;*p;p++) {
539 if ((*p)=='%') *(q++)='%';
540 *q++=*p;
541 }
542
543 FilterString=local;
544
545 T.set(local);
546
547 while( ( P=T.token("|\n\r") ) != 0 ) {
548 if (! P->empty() ) {
549 epm=new ExtPatternMatcher();
550 single[0]=0;
551 if (P->at(0) != '*')
552 g_strlcat(single,"*",512);
553 g_strlcat(single,P->c_str(),512);
554 if (P->at(P->length()-1) != '*')
555 g_strlcat(single,"*",512);
556 epm->set(single);
557 thefilter.push_back(epm);
558 }
559 }
560 }
561
getString()562 const char * TextFilter::getString() {
563 return(FilterString.c_str());
564 }
565
accept(const char * textline)566 bool TextFilter::accept(const char *textline) {
567 int i,j;
568
569 if (thefilter.empty()) return true;
570
571 if ((textline[0]=='\\')&&(AcceptedLast))
572 return true;
573
574 j=thefilter.size();
575 for(i=0;i<j;i++) {
576 if ( thefilter[i]->match(textline) ) {
577 AcceptedLast=true;
578 return true;
579 }
580 }
581 AcceptedLast=false;
582 return false;
583 }
584
cleanUp()585 void TextFilter::cleanUp() {
586 int i,j;
587 AcceptedLast=false;
588 j=thefilter.size();
589 for(i=0;i<j;i++)
590 delete(thefilter[i]);
591 thefilter.clear();
592 FilterString="";
593 }
594
595