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