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