1 /*
2 FXiTe - The Free eXtensIble Text Editor
3 Copyright (c) 2009-2011 Jeffrey Pohlmeyer <yetanothergeek@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify it
6 under the terms of the GNU General Public License version 3 as
7 published by the Free Software Foundation.
8
9 This software is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
19
20 #include <fx.h>
21 #include <Scintilla.h>
22 #include <FXScintilla.h>
23
24 #include "sl.h"
25
26 #include "compat.h"
27 #include "appname.h"
28 #include "macro.h"
29
30 #include "intl.h"
31 #include "help.h"
32
33 #include "helptext.h"
34 #include "help_lua.h"
35
36 #define sendString(iMessage, wParam, lParam) sendMessage(iMessage, wParam, reinterpret_cast<long>(lParam))
37
38
39 #ifdef FOX_1_7_50_OR_NEWER
40 # define RxFind(rx,subj,start,beg,end,npar) (rx.search(subj,strlen(subj),start,strlen(subj),FXRex::Normal,beg,end,npar)>=0)
41 #else
42 # define RxFind(rx,subj,start,beg,end,npar) (rx.match(subj,beg,end,REX_FORWARD,npar,start))
43 #endif
44
45
46 enum {
47 SCHLP_FIXED,
48 SCHLP_ITALIC,
49 SCHLP_BOLD,
50 SCHLP_LINK,
51 SCHLP_H1,
52 SCHLP_NORMAL
53 };
54
55 #define SCHLP_FIRST SCHLP_FIXED
56 #define SCHLP_LAST SCHLP_NORMAL
57 #define SCHLP_LOGO SCHLP_LAST+1
58
59 /*
60 The help files are written in a very crude markup (markdown?) language.
61 Anything that matches one of the regex phrases below will be formatted accordingly.
62 Link text is formatted as a "hyperlink" that, when clicked, will jump to the
63 section header containing the identical text. Nested markup is not allowed!
64 */
65
66 static const char* phrases[] = {
67 "===(.+?)===", // monospace font
68 "///(.+?)///", // italic font
69 "%%%(.+?)%%%", // bold font
70 "@@@(.+?)@@@", // link text
71 "###(.+?)###", // section header
72 NULL
73 };
74
75
76 #if ((FOX_MAJOR>1) || (FOX_MINOR>7) || ((FOX_MINOR==7) && (FOX_LEVEL>=23)))
77 # define CONFIG_DIR_BASE_NAME ".config"
78 #else
79 # define CONFIG_DIR_BASE_NAME ".foxrc"
80 #endif
81
82 /*
83 Instead of "hard-coding" these strings in hundreds of places in the text files,
84 just use these generic "macros" - each regex placeholder will be replaced
85 with its corresponding "real" name.
86 */
87 static const char* replacements[] = {
88 "\\<__APP__\\>", APP_NAME,
89 "\\<__EXE__\\>", EXE_NAME,
90 "\\<__MOD__\\>", LUA_MODULE_NAME,
91 "\\<__CFG__\\>", CONFIG_DIR_BASE_NAME,
92 NULL, NULL
93 };
94
95
96
97 typedef struct _HelpLink {
98 struct _HelpLink*next;
99 int pos;
100 char* href;
101 } HelpLink;
102
103
104
105 class SciHelp: public FXScintilla {
106 FXDECLARE(SciHelp)
SciHelp()107 SciHelp(){}
108 void*links;
109 void*sects;
110 void freelists();
111 long last_pos;
112 long jump_pos;
113 bool find(const FXString &what);
114 void replace(const char*oldstr, const char*newstr);
115 public:
116 SciHelp(FXComposite*p, FXObject*tgt=NULL, FXSelector sel=0, FXuint opts=LAYOUT_FILL, bool dark=false);
117 ~SciHelp();
118 void parse(const char*txt, unsigned int size);
119 long onLeftBtnRelease(FXObject*o, FXSelector sel, void*p);
120 long onCommand(FXObject*o, FXSelector sel, void*p);
121 FXTextField *srchbox;
122 const char*loaded;
123 enum {
124 ID_SCINTILLA=FXScintilla::ID_LAST,
125 ID_GOBACK,
126 ID_SEARCH,
127 ID_ZOOMIN,
128 ID_ZOOMOUT,
129 ID_LAST
130 };
131
132 };
133
134
135
136 FXDEFMAP(SciHelp) SciHelpMap[]={
137 FXMAPFUNCS(SEL_COMMAND, SciHelp::ID_SCINTILLA,SciHelp::ID_ZOOMOUT, SciHelp::onCommand),
138 FXMAPFUNC(SEL_LEFTBUTTONRELEASE, 0, SciHelp::onLeftBtnRelease)
139 };
140
FXIMPLEMENT(SciHelp,FXScintilla,SciHelpMap,ARRAYNUMBER (SciHelpMap))141 FXIMPLEMENT(SciHelp,FXScintilla,SciHelpMap,ARRAYNUMBER(SciHelpMap))
142
143
144
145 SciHelp::SciHelp(FXComposite*p,FXObject*tgt,FXSelector sel, FXuint opts, bool dark):
146 FXScintilla(p, this, ID_SCINTILLA, opts)
147 {
148 links=NULL;
149 sects=NULL;
150 last_pos=0;
151 jump_pos=-1;
152 loaded=NULL;
153
154 sendMessage(SCI_ASSIGNCMDKEY,SCK_HOME,SCI_DOCUMENTSTART);
155 sendMessage(SCI_ASSIGNCMDKEY,SCK_END,SCI_DOCUMENTEND);
156
157 long def_fg = dark? FXRGB(250, 250, 250) : FXRGB( 80, 80, 80);
158 long def_bg = dark? FXRGB( 60, 60, 80) : FXRGB(250, 250, 250);
159
160 long lnk_fg = dark? FXRGB(128, 160, 255) : FXRGB( 0, 0, 192);
161 long lnk_bg = def_bg;
162
163 long hot_fg = dark? FXRGB(224, 224, 80) : FXRGB( 0, 0, 255);
164 long hot_bg = dark? FXRGB(128, 80, 0) : FXRGB(255, 255, 128);
165
166 long mno_fg = dark? FXRGB(255, 144, 128) : FXRGB(128, 0, 0);
167 long mno_bg=def_bg;
168
169 long hdr_fg = dark? FXRGB( 96, 96, 160) : FXRGB(228, 228, 192);
170 long hdr_bg = dark? FXRGB(192, 255, 192) : FXRGB( 0, 0, 96);
171
172 long lgo_fg = dark? FXRGB(255, 144, 128) : FXRGB(128, 0, 0);
173 long lgo_bg=def_bg;
174
175 sendMessage(SCI_SETENDATLASTLINE, false,0);
176 sendMessage(SCI_SETWRAPMODE,SC_WRAP_WORD,0);
177 sendMessage(SCI_SETMARGINWIDTHN,1,0);
178 sendMessage(SCI_SETCARETSTYLE,CARETSTYLE_INVISIBLE,0);
179 sendMessage(SCI_SETMARGINLEFT,0,4);
180 sendMessage(SCI_SETMARGINRIGHT,0,4);
181 sendMessage(SCI_SETHOTSPOTACTIVEFORE,true,hot_fg);
182 sendMessage(SCI_SETHOTSPOTACTIVEBACK,true,hot_bg);
183 sendMessage(SCI_STYLESETBACK,STYLE_DEFAULT,def_bg);
184
185 for (FXint i=SCHLP_FIRST; i<=SCHLP_LAST; i++) {
186 sendMessage(SCI_STYLESETSIZE, i, 10);
187 sendString(SCI_STYLESETFONT, i, "Sans Serif");
188 sendMessage(SCI_STYLESETFORE,i,def_fg);
189 sendMessage(SCI_STYLESETBACK,i,def_bg);
190 sendMessage(SCI_STYLESETEOLFILLED,i,true);
191 }
192
193 sendMessage(SCI_STYLESETHOTSPOT, SCHLP_LINK, true);
194 sendMessage(SCI_STYLESETUNDERLINE, SCHLP_LINK, true);
195 sendMessage(SCI_STYLESETFORE,SCHLP_LINK,lnk_fg);
196 sendMessage(SCI_STYLESETBACK,SCHLP_LINK,lnk_bg);
197
198 sendMessage(SCI_STYLESETSIZE, SCHLP_H1, 14);
199 sendMessage(SCI_STYLESETBACK,SCHLP_H1,hdr_fg);
200 sendMessage(SCI_STYLESETFORE,SCHLP_H1,hdr_bg);
201
202 sendMessage(SCI_STYLESETBOLD,SCHLP_BOLD,true);
203 sendMessage(SCI_STYLESETITALIC,SCHLP_ITALIC,true);
204
205 sendString(SCI_STYLESETFONT, SCHLP_FIXED, "Courier");
206 sendMessage(SCI_STYLESETSIZE, SCHLP_FIXED, 10);
207 sendMessage(SCI_STYLESETFORE,SCHLP_FIXED,mno_fg);
208 sendMessage(SCI_STYLESETBACK,SCHLP_FIXED,mno_bg);
209
210 sendString(SCI_STYLESETFONT, SCHLP_LOGO, "Serif");
211 sendMessage(SCI_STYLESETSIZE, SCHLP_LOGO, 10);
212 sendMessage(SCI_STYLESETFORE,SCHLP_LOGO,lgo_fg);
213 sendMessage(SCI_STYLESETBACK,SCHLP_LOGO,lgo_bg);
214
215 sendMessage(SCI_STARTSTYLING,0,0xff);
216
217 horizontalScrollBar()->hide();
218 sendMessage(SCI_SETHSCROLLBAR,false,0);
219 sendMessage(SCI_SETYCARETPOLICY,CARET_EVEN|CARET_STRICT,0);
220
221 }
222
223
224
replace(const char * oldstr,const char * newstr)225 void SciHelp::replace(const char*oldstr, const char*newstr)
226 {
227 const char*content;
228 FXint beg[3]={0,0,0};
229 FXint end[3]={0,0,0};
230 FXRex generic_rx(oldstr, REX_NORMAL|REX_NEWLINE);
231 content=(const char*)(sendMessage(SCI_GETCHARACTERPOINTER,0,0));
232 FXint n=strlen(newstr);
233 while (RxFind(generic_rx,content,0,beg,end,1)) {
234 sendMessage(SCI_SETTARGETSTART,beg[0],0);
235 sendMessage(SCI_SETTARGETEND,end[0],0);
236 sendString(SCI_REPLACETARGET,n,newstr);
237 content=(const char*)(sendMessage(SCI_GETCHARACTERPOINTER,0,0));
238 }
239 }
240
241
242
my_strndup(const char * src,int len)243 static char*my_strndup(const char*src,int len)
244 {
245 char*dst=(char*)calloc(len+1,1);
246 strncpy(dst,src,len);
247 return dst;
248 }
249
250
251
parse(const char * txt,unsigned int size)252 void SciHelp::parse(const char*txt, unsigned int size)
253 {
254 FXint beg[3]={0,0,0};
255 FXint end[3]={0,0,0};
256 loaded=txt;
257 const char *content=NULL;
258 getApp()->beginWaitCursor();
259 freelists();
260 sendMessage(SCI_SETREADONLY,false,0);
261 sendMessage(SCI_CLEARALL,0,0);
262 sendString(SCI_APPENDTEXT, size, txt);
263 sendString(SCI_APPENDTEXT, 32,FXString('\n',32).text());
264 sendMessage(SCI_SETSTYLING,sendMessage(SCI_GETLENGTH,0,0),SCHLP_NORMAL);
265 sendMessage(SCI_STARTSTYLING,0,0xff);
266 // Translate markup tags into scintilla styles...
267 for (FXint i=SCHLP_FIRST; i<SCHLP_LAST; i++) {
268 content=(const char*)(sendMessage(SCI_GETCHARACTERPOINTER,0,0));
269 FXRex rx(phrases[i], REX_CAPTURE|REX_NEWLINE);
270 while (RxFind(rx,content,0,beg,end,2)) {
271 sendMessage(SCI_SETTARGETSTART,beg[0],0);
272 sendMessage(SCI_SETTARGETEND,end[0],0);
273 char*tmp=my_strndup(content+beg[1], end[1]-beg[1]);
274 sendString(SCI_REPLACETARGET,end[1]-beg[1],tmp);
275 if ((i==SCHLP_H1)||(i==SCHLP_LINK)) {
276 HelpLink*link=(HelpLink*)calloc(1, sizeof(HelpLink));
277 link->pos=0;
278 link->href=tmp;
279 if (i==SCHLP_H1) {
280 sects=sl_push(sects,link);
281 } else {
282 links=sl_push(links,link);
283 }
284 } else {
285 free(tmp);
286 }
287 sendMessage(SCI_STARTSTYLING,beg[0],0xff);
288 sendMessage(SCI_SETSTYLING,(end[1]-beg[1])+((i==SCHLP_H1)?1:0),i);
289 content=(const char*)(sendMessage(SCI_GETCHARACTERPOINTER,0,0));
290 }
291 }
292 // replace generic placeholders with the real names...
293 for (const char**c=replacements; *c; c+=2) {
294 replace(*c,*(c+1));
295 }
296
297 // Make the editor's name stand out a little...
298 FXRex appname_rx("\\<" APP_NAME "\\>", REX_NORMAL|REX_NEWLINE);
299 content=(const char*)(sendMessage(SCI_GETCHARACTERPOINTER,0,0));
300 FXint p=0;
301 while (RxFind(appname_rx,content,p,beg,end,1)) {
302 sendMessage(SCI_STARTSTYLING,beg[0],0xff);
303 sendMessage(SCI_SETSTYLING,(end[0]-beg[0]),SCHLP_LOGO);
304 p=end[0];
305 }
306
307 sendMessage(SCI_SETREADONLY,true,0);
308
309 links=sl_reverse(links);
310 sects=sl_reverse(sects);
311
312 // Populate our list elements with their positions in the document...
313 HelpLink*link=(HelpLink*)links;
314 HelpLink*sect=(HelpLink*)sects;
315 long len=sendMessage(SCI_GETLENGTH,0,0);
316
317 for (long p1=0; p1<len;) {
318 int style=sendMessage(SCI_GETSTYLEAT,p1,0);
319 switch (style) {
320 case SCHLP_H1: {
321 sect->pos=p1;
322 sect=sect->next;
323 break;
324 }
325 case SCHLP_LINK: {
326 link->pos=p1;
327 link=link->next;
328 break;
329 }
330 }
331 while ( (sendMessage(SCI_GETSTYLEAT,p1,0)==style) && (p1<len) ) { p1++; }
332 }
333 getApp()->endWaitCursor();
334 }
335
336
337
free_link_cb(void * p)338 static void free_link_cb(void *p) {
339 if (p) {
340 HelpLink*link=(HelpLink*)p;
341 if (link->href) { delete link->href; }
342 delete link;
343 }
344 }
345
346
347
freelists()348 void SciHelp::freelists()
349 {
350 if (links) {
351 sl_free(links, free_link_cb);
352 links=NULL;
353 }
354 if (sects) {
355 sl_free(sects, free_link_cb);
356 sects=NULL;
357 }
358 last_pos=0;
359 jump_pos=-1;
360 }
361
362
363
~SciHelp()364 SciHelp::~SciHelp()
365 {
366 freelists();
367 }
368
369
370
lookup_link(void * rec,void * p)371 static int lookup_link(void *rec, void *p)
372 {
373 HelpLink*link=(HelpLink*)rec;
374 if ( link && ( link->pos == *(int*)p) ) {
375 return -1;
376 } else {
377 return 0;
378 }
379 }
380
381
382
lookup_sect(void * rec,void * p)383 static int lookup_sect(void *rec, void *p)
384 {
385 HelpLink*sect=(HelpLink*)rec;
386 char*href=(char*)p;
387 if ( sect && sect->href && ( strcmp(sect->href,href) == 0 ) ) {
388 return -1;
389 } else {
390 return 0;
391 }
392 }
393
394
395
onLeftBtnRelease(FXObject * o,FXSelector sel,void * p)396 long SciHelp::onLeftBtnRelease(FXObject*o, FXSelector sel, void*p)
397 {
398 FXint prev_x;
399 FXint prev_y;
400 getPosition(prev_x,prev_y);
401
402 long rv = FXScintilla::onLeftBtnRelease(o,sel,p);
403
404 if (jump_pos>=0) {
405 sendMessage(SCI_GOTOPOS,jump_pos,0);
406 long th=sendMessage(SCI_TEXTHEIGHT,0,0);
407 #ifdef FOX_1_6
408 setPosition(0,-((vertical->getPosition()+(getViewportHeight()/2))-th));
409 #else
410 setPosition(0,-((vertical->getPosition()+(getVisibleHeight()/2))-th));
411 #endif
412 last_pos=jump_pos;
413 jump_pos=-1;
414 } else {
415 setPosition(prev_x,prev_y);
416 }
417 return rv;
418 }
419
420
421
onCommand(FXObject * o,FXSelector sel,void * p)422 long SciHelp::onCommand(FXObject*o, FXSelector sel, void*p)
423 {
424 switch (FXSELID(sel)) {
425 case ID_SCINTILLA: {
426 SCNotification* scn = static_cast<SCNotification*>(p);
427 if ((scn->nmhdr.code==SCN_HOTSPOTCLICK) && links && sects) {
428 int pos=scn->position;
429 while (sendMessage(SCI_GETSTYLEAT, pos,0)==SCHLP_LINK) { pos--; }
430 pos++;
431 HelpLink*link = (HelpLink*) sl_map(links, lookup_link, &pos);
432 if (link && link->href) {
433 HelpLink*sect = (HelpLink*) sl_map(sects, lookup_sect, link->href);
434 if (sect) { jump_pos=sect->pos; }
435 }
436 }
437 return 0;
438 }
439 case ID_GOBACK:{
440 FXint x,y;
441 getPosition(x,y);
442 setPosition(0,last_pos);
443 last_pos=y;
444 return 1;
445 }
446 case ID_SEARCH: {
447 if (srchbox->getText().length()) {
448 find(srchbox->getText());
449 } else {
450 srchbox->setFocus();
451 }
452 return 1;
453 }
454 case ID_ZOOMIN:{
455 sendMessage(SCI_ZOOMIN,0,0);
456 return 1;
457 }
458 case ID_ZOOMOUT:{
459 sendMessage(SCI_ZOOMOUT,0,0);
460 return 1;
461 }
462 }
463 return 0;
464 }
465
466
467
find(const FXString & what)468 bool SciHelp::find(const FXString &what)
469 {
470 long start=sendMessage(SCI_GETCURRENTPOS,0,0);
471 long end=sendMessage(SCI_GETLENGTH,0,0);
472 sendMessage(SCI_SETTARGETSTART,start,0);
473 sendMessage(SCI_SETTARGETEND,end,0);
474 long found=sendString(SCI_SEARCHINTARGET,what.length(), what.text());
475 if (found==-1) {
476 start=0;
477 sendMessage(SCI_SETTARGETSTART,start,0);
478 sendMessage(SCI_SETTARGETEND,end,0);
479 found=sendString(SCI_SEARCHINTARGET,what.length(), what.text());
480 }
481 if (found>=0) {
482 sendMessage(SCI_SETSEL, sendMessage(SCI_GETTARGETSTART,0,0), sendMessage(SCI_GETTARGETEND,0,0) );
483 sendMessage(SCI_SCROLLCARET,0,0);
484 return true;
485 }
486 return false;
487 }
488
489
490
491 #define PAD(w,p) \
492 w->setPadTop(p); \
493 w->setPadBottom(p); \
494 w->setPadLeft(p); \
495 w->setPadRight(p);
496
497
498 class HelpDialog;
499
500 static HelpDialog *instance = NULL;
501
502 #define HELP_DECOR DECOR_TITLE|DECOR_BORDER|DECOR_MINIMIZE|DECOR_MAXIMIZE|DECOR_CLOSE|DECOR_RESIZE
503 #define RAISED FRAME_RAISED|FRAME_THICK
504 #define TEXTFIELD_OPTS TEXTFIELD_NORMAL|TEXTFIELD_ENTER_ONLY
505
506
507 class HelpDialog: public FXDialogBox {
508 FXDECLARE(HelpDialog)
HelpDialog()509 HelpDialog(){}
510 SciHelp*sc;
511 public:
HelpDialog(FXMainWindow * win,bool dark)512 HelpDialog(FXMainWindow*win, bool dark):FXDialogBox(win->getApp(),_(APP_NAME" Help"),HELP_DECOR) {
513 FXint w=getApp()->getRootWindow()->getWidth();
514 FXint h=getApp()->getRootWindow()->getHeight();
515 setWidth( (FXint)( w>800 ? w*0.6875 : w*0.875 ) );
516 setHeight( (FXint)( h>600 ? h*0.667 : h*0.75 ) );
517 setX((w-getWidth())/2);
518 setY((h-getHeight())/2);
519 PAD(this,1);
520 FXVerticalFrame *vbox=new FXVerticalFrame(this, LAYOUT_FILL|FRAME_NONE);
521 PAD(vbox,0);
522 vbox->setVSpacing(1);
523 FXHorizontalFrame*scfrm=new FXHorizontalFrame(vbox, LAYOUT_FILL|FRAME_SUNKEN|FRAME_THICK);
524 PAD(scfrm,0);
525 sc=new SciHelp(scfrm, NULL,0, LAYOUT_FILL|HSCROLLER_NEVER,dark);
526 setUserData(sc);
527 FXHorizontalFrame *btns=new FXHorizontalFrame(vbox, LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|RAISED);
528 new FXButton(btns,_(" &Back "), NULL, sc, SciHelp::ID_GOBACK, RAISED);
529 new FXLabel(btns, " ");
530 sc->srchbox=new FXTextField(btns,24,sc, SciHelp::ID_SEARCH,TEXTFIELD_OPTS);
531 new FXButton(btns,_(" &Find "), NULL, sc, SciHelp::ID_SEARCH, RAISED);
532 btns=new FXHorizontalFrame(btns, LAYOUT_RIGHT|LAYOUT_FILL_X|FRAME_NONE,0,0,0,0, 16,8,0,0,0,0);
533 new FXButton(btns," + ", NULL, sc, SciHelp::ID_ZOOMIN, RAISED|LAYOUT_LEFT);
534 new FXButton(btns," - ", NULL, sc, SciHelp::ID_ZOOMOUT, RAISED|LAYOUT_LEFT);
535 new FXButton(btns,_(" &Close "), NULL, this, FXDialogBox::ID_CLOSE, RAISED|LAYOUT_RIGHT);
536 setIcon(win->getIcon());
537 changeFocus(sc->srchbox);
538 create();
539 SetupXAtoms(this, "help", APP_NAME);
540 show();
541 getApp()->runWhileEvents();
542 }
onCmdClose(FXObject * o,FXSelector sel,void * p)543 long onCmdClose(FXObject*o, FXSelector sel, void*p) {
544 instance=NULL;
545 return FXDialogBox::onCmdClose(o,sel,p);
546 }
Load(FXint which)547 void Load(FXint which) {
548 sc->hide();
549 const char*todo=NULL;
550 unsigned int len=0;
551 switch (which) {
552 case 0: {
553 todo=(const char*)helptext;
554 len=sizeof(helptext);
555 break;
556 }
557 case 1: {
558 todo=(const char*)help_lua;
559 len=sizeof(help_lua);
560 break;
561 }
562 }
563 if (sc->loaded!=todo) { sc->parse(todo,len); }
564 sc->show();
565 if (shown()) { hide(); }
566 show(PLACEMENT_SCREEN);
567 }
568 };
569
570
571
572 FXDEFMAP(HelpDialog) HelpDialogMap[] = {
573 FXMAPFUNC(SEL_COMMAND, FXDialogBox::ID_CLOSE, HelpDialog::onCmdClose),
574 };
575
576
577 FXIMPLEMENT(HelpDialog,FXDialogBox,HelpDialogMap,ARRAYNUMBER(HelpDialogMap));
578
579
580
show_help(FXMainWindow * win,FXint which,bool dark)581 void show_help(FXMainWindow*win, FXint which, bool dark)
582 {
583 if (!instance) { instance=new HelpDialog(win,dark); }
584 instance->Load(which);
585 }
586
587