Added wxExtHelpController: wxHelpController implementation for external
[wxWidgets.git] / src / generic / helpext.cpp
1 /*-*- c++ -*-********************************************************
2 * wxexthlp.cpp - an external help controller for wxWindows *
3 * *
4 * (C) 1998 by Karsten Ballüder (Ballueder@usa.net) *
5 * *
6 * $Id$
7 *******************************************************************/
8 #ifdef __GNUG__
9 # pragma implementation "wxexthlp.h"
10 #endif
11
12 #include "wx/setup.h"
13 #include "wx/helpbase.h"
14 #include "wx/generic/helpext.h"
15 #include "wx/string.h"
16 #include "wx/utils.h"
17 #include <stdio.h>
18 #include <ctype.h>
19 #include <sys/stat.h>
20 #include <unistd.h>
21
22
23 #define WXEXTHELP_INCLUDE_KBLIST
24 /**
25 ** This class uses kbList, a simple linked list. Until someone
26 ** rewrites it to use wxList instead, I include the relevant bits and
27 ** pieces of kbList here. It's a tiny class anyway, so it won't make
28 ** a big difference. The comments probably take up more space than
29 ** its code.
30 **/
31
32 #ifdef WXEXTHELP_INCLUDE_KBLIST
33
34 /********************* kbList.h, verbose copy: ****************************/
35 /**@name Double linked list implementation. */
36 //@{
37
38 /** kbListNode is a class used by kbList. It represents a single
39 element in the list. It is not intended for general use outside
40 kbList functions.
41 */
42 struct kbListNode
43 {
44 /// pointer to next node or NULL
45 struct kbListNode *next;
46 /// pointer to previous node or NULL
47 struct kbListNode *prev;
48 /// pointer to the actual data
49 void *element;
50 /** Constructor - it automatically links the node into the list, if
51 the iprev, inext parameters are given.
52 @param ielement pointer to the data for this node (i.e. the data itself)
53 @param iprev if not NULL, use this as previous element in list
54 @param inext if not NULL, use this as next element in list
55 */
56 kbListNode( void *ielement,
57 kbListNode *iprev = NULL,
58 kbListNode *inext = NULL);
59 /// Destructor.
60 ~kbListNode();
61 };
62
63 /** The main list class, handling void pointers as data.
64 */
65
66 class kbList
67 {
68 public:
69 /// An iterator class for kbList, just like for the STL classes.
70 class iterator
71 {
72 protected:
73 /// the node to which this iterator points
74 kbListNode *node;
75 friend class kbList;
76 public:
77 /** Constructor.
78 @param n if not NULL, the node to which to point
79 */
80 iterator(kbListNode *n = NULL);
81 /** Dereference operator.
82 @return the data pointer of the node belonging to this
83 iterator
84 */
85 void * operator*();
86
87 /** This operator allows us to write if(i). It is <em>not</em> a
88 dereference operator and the result is always useless apart
89 from its logical value!
90 */
91 operator void*() const { return node == NULL ? (void*)0 : (void*)(-1); }
92
93 /** Increment operator - prefix, goes to next node in list.
94 @return itself
95 */
96 iterator & operator++();
97
98 /** Decrement operator - prefix, goes to previous node in list.
99 @return itself
100 */
101 iterator & operator--();
102
103 /** Increment operator - prefix, goes to next node in list.
104 @return itself
105 */
106 iterator & operator++(int); //postfix
107
108 /** Decrement operator - prefix, goes to previous node in list.
109 @return itself
110 */
111 iterator & operator--(int); //postfix
112
113 /** Comparison operator.
114 @return true if not equal.
115 */
116 bool operator !=(iterator const &) const;
117
118 /* Comparison operator.
119 @return true if equal
120 */
121 bool operator ==(iterator const &) const;
122
123 /** Returns a pointer to the node associated with this iterator.
124 This function is not for general use and should be
125 protected. However, if protected, it cannot be called from
126 derived classes' iterators. (Is this a bug in gcc/egcs?)
127 @return the node pointer
128 */
129 inline kbListNode * Node(void) const
130 { return node; }
131 };
132
133 /** Constructor.
134 @param ownsEntriesFlag if true, the list owns the entries and
135 will issue a delete on each of them when deleting them. If
136 false, the entries themselves will not get deleted. Do not use
137 this with array types!
138 */
139 kbList(bool ownsEntriesFlag = true);
140
141 /** Destructor.
142 If entries are owned, they will all get deleted from here.
143 */
144 ~kbList();
145
146 /** Tell list whether it owns objects. If owned, they can be
147 deleted by list. See the constructor for more details.
148 @param ownsflag if true, list will own entries
149 */
150 void ownsObjects(bool ownsflag = true)
151 { ownsEntries = ownsflag; }
152
153 /** Query whether list owns entries.
154 @return true if list owns entries
155 */
156 bool ownsObjects(void)
157 { return ownsEntries; }
158
159 /** Add an entry at the end of the list.
160 @param element pointer to data
161 */
162 void push_back(void *element);
163
164 /** Add an entry at the head of the list.
165 @param element pointer to data
166 */
167 void push_front(void *element);
168
169 /** Get element from end of the list and delete it.
170 NOTE: In this case the element's data will not get deleted by
171 the list. It is the responsibility of the caller to free it.
172 @return the element data
173 */
174 void *pop_back(void);
175
176 /** Get element from head of the list and delete it.
177 NOTE: In this case the element's data will not get deleted by
178 the list. It is the responsibility of the caller to free it.
179 @return the element data
180 */
181 void *pop_front(void);
182
183 /** Insert an element into the list.
184 @param i an iterator pointing to the element, before which the new one should be inserted
185 @param element the element data
186 */
187 void insert(iterator & i, void *element);
188
189 /** Remove an element from the list _without_ deleting the object.
190 @param i iterator pointing to the element to be deleted
191 @return the value of the element just removed
192 */
193 void *remove(iterator& i) { void *p = *i; doErase(i); return p; }
194
195 /** Erase an element, move iterator to following element.
196 @param i iterator pointing to the element to be deleted
197 */
198 void erase(iterator & i) { deleteContent(i); doErase(i); }
199
200 /* Get head of list.
201 @return iterator pointing to head of list
202 */
203 iterator begin(void) const;
204
205 /* Get end of list.
206 @return iterator pointing after the end of the list. This is an
207 invalid iterator which cannot be dereferenced or decremented. It is
208 only of use in comparisons. NOTE: this is different from STL!
209 @see tail
210 */
211 iterator end(void) const;
212
213 /* Get last element in list.
214 @return iterator pointing to the last element in the list.
215 @see end
216 */
217 iterator tail(void) const;
218
219 /* Get the number of elements in the list.
220 @return number of elements in the list
221 */
222 unsigned size(void) const;
223
224 /* Query whether list is empty.
225 @return true if list is empty
226 */
227 inline bool empty(void) const
228 { return first == NULL ; }
229
230 protected:
231 /// if true, list owns entries
232 bool ownsEntries;
233 /// pointer to first element in list
234 kbListNode *first;
235 /// pointer to last element in list
236 kbListNode *last;
237 protected:
238 /** Erase an element, move iterator to following element.
239 @param i iterator pointing to the element to be deleted
240 */
241 void doErase(iterator & i);
242
243 /** Deletes the actual content if ownsflag is set.
244 param iterator i
245 */
246 inline void deleteContent(iterator i)
247 { if(ownsEntries) delete *i; }
248
249
250 private:
251 /// forbid copy construction
252 kbList(kbList const &foo);
253 /// forbid assignments
254 kbList& operator=(const kbList& foo);
255 };
256
257 /** Macro to define a kbList with a given name, having elements of
258 pointer to the given type. I.e. KBLIST_DEFINE(Int,int) would
259 create a kbListInt type holding int pointers.
260 */
261 #define KBLIST_DEFINE(name,type) \
262 class name : public kbList \
263 { \
264 public: \
265 class iterator : public kbList::iterator \
266 { \
267 protected: \
268 inline iterator(kbList::iterator const & i) \
269 { node = i.Node(); } \
270 friend class name; \
271 public: \
272 inline iterator(kbListNode *n = NULL) \
273 : kbList::iterator(n) {} \
274 inline type * operator*() \
275 /* the cast is needed for MS VC++ 5.0 */ \
276 { return (type *)((kbList::iterator *)this)->operator*() ; } \
277 }; \
278 inline name(bool ownsEntriesFlag = TRUE) \
279 : kbList(ownsEntriesFlag) {} \
280 \
281 inline type *pop_back(void) \
282 { return (type *) kbList::pop_back(); } \
283 \
284 inline type *pop_front(void) \
285 { return (type *) kbList::pop_front(); } \
286 \
287 type *remove(iterator& i) \
288 { return (type *)kbList::remove(i); } \
289 inline void erase(iterator & i) \
290 { deleteContent(i); kbList::erase(i); } \
291 \
292 inline iterator begin(void) const \
293 { return kbList::begin(); } \
294 \
295 inline iterator end(void) const \
296 { return kbList::end(); } \
297 \
298 inline iterator tail(void) const \
299 { return kbList::tail(); } \
300 ~name() \
301 { \
302 kbListNode *next; \
303 while ( first != NULL ) \
304 { \
305 next = first->next; \
306 if(ownsEntries) \
307 delete (type *)first->element; \
308 delete first; \
309 first = next; \
310 } \
311 } \
312 protected: \
313 inline void deleteContent(iterator i) \
314 { if(ownsEntries) delete *i; } \
315 }
316
317
318 /************************* copy of kbList.cpp: ****************************/
319 kbListNode::kbListNode( void *ielement,
320 kbListNode *iprev,
321 kbListNode *inext)
322 {
323 next = inext;
324 prev = iprev;
325 if(prev)
326 prev->next = this;
327 if(next)
328 next->prev = this;
329 element = ielement;
330 }
331
332 kbListNode::~kbListNode()
333 {
334 if(prev)
335 prev->next = next;
336 if(next)
337 next->prev = prev;
338 }
339
340
341 kbList::iterator::iterator(kbListNode *n)
342 {
343 node = n;
344 }
345
346 void *
347 kbList::iterator::operator*()
348 {
349 return node->element;
350 }
351
352 kbList::iterator &
353 kbList::iterator::operator++()
354 {
355 node = node ? node->next : NULL;
356 return *this;
357 }
358
359 kbList::iterator &
360 kbList::iterator::operator--()
361 {
362 node = node ? node->prev : NULL;
363 return *this;
364 }
365 kbList::iterator &
366 kbList::iterator::operator++(int /* foo */)
367 {
368 return operator++();
369 }
370
371 kbList::iterator &
372 kbList::iterator::operator--(int /* bar */)
373 {
374 return operator--();
375 }
376
377
378 bool
379 kbList::iterator::operator !=(kbList::iterator const & i) const
380 {
381 return node != i.node;
382 }
383
384 bool
385 kbList::iterator::operator ==(kbList::iterator const & i) const
386 {
387 return node == i.node;
388 }
389
390 kbList::kbList(bool ownsEntriesFlag)
391 {
392 first = NULL;
393 last = NULL;
394 ownsEntries = ownsEntriesFlag;
395 }
396
397 void
398 kbList::push_back(void *element)
399 {
400 if(! first) // special case of empty list
401 {
402 first = new kbListNode(element);
403 last = first;
404 return;
405 }
406 else
407 last = new kbListNode(element, last);
408 }
409
410 void
411 kbList::push_front(void *element)
412 {
413 if(! first) // special case of empty list
414 {
415 push_back(element);
416 return;
417 }
418 else
419 first = new kbListNode(element, NULL, first);
420 }
421
422 void *
423 kbList::pop_back(void)
424 {
425 iterator i;
426 void *data;
427 bool ownsFlagBak = ownsEntries;
428 i = tail();
429 data = *i;
430 ownsEntries = false;
431 erase(i);
432 ownsEntries = ownsFlagBak;
433 return data;
434 }
435
436 void *
437 kbList::pop_front(void)
438 {
439 iterator i;
440 void *data;
441 bool ownsFlagBak = ownsEntries;
442
443 i = begin();
444 data = *i;
445 ownsEntries = false;
446 erase(i);
447 ownsEntries = ownsFlagBak;
448 return data;
449
450 }
451
452 void
453 kbList::insert(kbList::iterator & i, void *element)
454 {
455 if(! i.Node())
456 return;
457 else if(i.Node() == first)
458 {
459 push_front(element);
460 i = first;
461 return;
462 }
463 i = kbList::iterator(new kbListNode(element, i.Node()->prev, i.Node()));
464 }
465
466 void
467 kbList::doErase(kbList::iterator & i)
468 {
469 kbListNode
470 *node = i.Node(),
471 *prev, *next;
472
473 if(! node) // illegal iterator
474 return;
475
476 prev = node->prev;
477 next = node->next;
478
479 // correct first/last:
480 if(node == first)
481 first = node->next;
482 if(node == last) // don't put else here!
483 last = node->prev;
484
485 // build new links:
486 if(prev)
487 prev->next = next;
488 if(next)
489 next->prev = prev;
490
491 // delete this node and contents:
492 // now done separately
493 //if(ownsEntries)
494 //delete *i;
495 delete i.Node();
496
497 // change the iterator to next element:
498 i = kbList::iterator(next);
499 }
500
501 kbList::~kbList()
502 {
503 kbListNode *next;
504
505 while ( first != NULL )
506 {
507 next = first->next;
508 if(ownsEntries)
509 delete first->element;
510 delete first;
511 first = next;
512 }
513 }
514
515 kbList::iterator
516 kbList::begin(void) const
517 {
518 return kbList::iterator(first);
519 }
520
521 kbList::iterator
522 kbList::tail(void) const
523 {
524 return kbList::iterator(last);
525 }
526
527 kbList::iterator
528 kbList::end(void) const
529 {
530 return kbList::iterator(NULL); // the one after the last
531 }
532
533 unsigned
534 kbList::size(void) const // inefficient
535 {
536 unsigned count = 0;
537 kbList::iterator i;
538 for(i = begin(); i != end(); i++, count++)
539 ;
540 return count;
541 }
542
543 #endif
544 /************************* end of kbList code *****************************/
545
546 struct wxExtHelpMapEntry
547 {
548 int id;
549 wxString url;
550 wxString doc;
551 wxExtHelpMapEntry(int iid, wxString const &iurl, wxString const &idoc)
552 { id = iid; url = iurl; doc = idoc; }
553 };
554 KBLIST_DEFINE(wxExtHelpMapList, wxExtHelpMapEntry);
555
556
557 struct wxBusyCursor
558 {
559 wxBusyCursor() { wxBeginBusyCursor(); }
560 ~wxBusyCursor() { wxEndBusyCursor(); }
561 };
562
563 IMPLEMENT_CLASS(wxExtHelpController, wxHelpControllerBase)
564
565 /**
566 This class implements help via an external browser.
567 It requires the name of a directory containing the documentation
568 and a file mapping numerical Section numbers to relative URLS.
569 */
570
571 wxExtHelpController::wxExtHelpController(void)
572 {
573 m_MapList = NULL;
574 m_BrowserName = WXEXTHELP_DEFAULTBROWSER;
575 m_BrowserIsNetscape = WXEXTHELP_DEFAULTBROWSER_IS_NETSCAPE;
576
577 char *browser = getenv(WXEXTHELP_ENVVAR_BROWSER);
578 if(browser)
579 {
580 m_BrowserName = browser;
581 browser = getenv(WXEXTHELP_ENVVAR_BROWSERISNETSCAPE);
582 m_BrowserIsNetscape = browser && (atoi(browser) != 0);
583 }
584 }
585
586 wxExtHelpController::~wxExtHelpController(void)
587 {
588 if(m_MapList) delete m_MapList;
589 }
590
591 void
592 wxExtHelpController::SetBrowser(wxString const & browsername, bool isNetscape)
593 {
594 m_BrowserName = browsername;
595 m_BrowserIsNetscape = isNetscape;
596 }
597
598 /** This must be called to tell the controller where to find the
599 documentation.
600 @param file - NOT a filename, but a directory name.
601 @return true on success
602 */
603 bool
604 wxExtHelpController::Initialize(const wxString& file)
605 {
606 return LoadFile(file);
607 }
608
609
610 bool
611 wxExtHelpController::LoadFile(const wxString& ifile = "")
612 {
613 wxString mapFile, file, url, doc;
614 int id,i,len;
615 char buffer[WXEXTHELP_BUFLEN];
616
617 wxBusyCursor b; // display a busy cursor
618
619 if(! ifile.IsEmpty())
620 {
621 file = ifile;
622 if(! wxIsAbsolutePath(file))
623 {
624 file = wxGetWorkingDirectory();
625 file << WXEXTHELP_SEPARATOR << ifile;
626 }
627 else
628 file = ifile;
629
630 if(! wxDirExists(file))
631 return false;
632
633 mapFile << file << WXEXTHELP_SEPARATOR << WXEXTHELP_MAPFILE;
634 }
635 else // try to reload old file
636 mapFile = m_MapFile;
637
638 if(! wxFileExists(mapFile))
639 return false;
640
641 if(m_MapList) delete m_MapList;
642 m_MapList = new wxExtHelpMapList;
643 m_NumOfEntries = 0;
644
645 FILE *input = fopen(mapFile.c_str(),"rt");
646 if(! input)
647 return false;
648 do
649 {
650 if(fgets(buffer,WXEXTHELP_BUFLEN,input) && *buffer != WXEXTHELP_COMMENTCHAR)
651 {
652 len = strlen(buffer);
653 if(buffer[len-1] == '\n')
654 buffer[len-1] = '\0'; // cut of trailing newline
655 if(sscanf(buffer,"%d", &id) != 1)
656 break; // error
657 for(i=0; isdigit(buffer[i])||isspace(buffer[i]); i++)
658 ; // find begin of URL
659 url = "";
660 while(buffer[i] && ! isspace(buffer[i]) && buffer[i] !=
661 WXEXTHELP_COMMENTCHAR)
662 url << buffer[i++];
663 while(buffer[i] && buffer[i] != WXEXTHELP_COMMENTCHAR)
664 i++;
665 doc = "";
666 if(buffer[i])
667 doc = (buffer + i + 1); // skip the comment character
668 m_MapList->push_back(new wxExtHelpMapEntry(id,url,doc));
669 m_NumOfEntries++;
670 }
671 else
672 perror("");
673 }while(! feof(input));
674 fclose(input);
675
676 m_MapFile = file; // now it's valid
677 return true;
678 }
679
680 bool
681 wxExtHelpController::CallBrowser(wxString const &relativeURL)
682 {
683 wxBusyCursor b; // display a busy cursor
684 wxString command;
685
686 if(m_BrowserIsNetscape) // try re-loading first
687 {
688 wxString lockfile;
689 wxGetHomeDir(&lockfile);
690 lockfile << WXEXTHELP_SEPARATOR << ".netscape/lock";
691 struct stat statbuf;
692 if(lstat(lockfile.c_str(), &statbuf) == 0)
693 // cannot use wxFileExists, because it's a link pointing to a
694 // non-existing location if(wxFileExists(lockfile))
695 {
696 long success;
697 command << m_BrowserName << " -remote openURL("
698 << "file://" << m_MapFile
699 << WXEXTHELP_SEPARATOR << relativeURL << ")";
700 success = wxExecute(command);
701 if(success != 0 ) // returns PID on success
702 return true;
703 }
704 }
705 command = m_BrowserName;
706 command << " file://"
707 << m_MapFile << WXEXTHELP_SEPARATOR << relativeURL;
708 return wxExecute(command) != 0;
709 }
710
711 bool
712 wxExtHelpController::DisplayContents(void)
713 {
714 if(! m_NumOfEntries)
715 return false;
716 wxBusyCursor b; // display a busy cursor
717 return KeywordSearch("");
718 }
719
720 bool
721 wxExtHelpController::DisplaySection(int sectionNo)
722 {
723 if(! m_NumOfEntries)
724 return false;
725
726 wxBusyCursor b; // display a busy cursor
727 wxExtHelpMapList::iterator i = m_MapList->begin();
728 while(i != m_MapList->end())
729 {
730 if((**i).id == sectionNo)
731 return CallBrowser((**i).url);
732 i++;
733 }
734 return false;
735 }
736
737 bool
738 wxExtHelpController::DisplayBlock(long blockNo)
739 {
740 return DisplaySection((int)blockNo);
741 }
742
743 bool
744 wxExtHelpController::KeywordSearch(const wxString& k)
745 {
746 if(! m_NumOfEntries)
747 return false;
748
749 wxBusyCursor b; // display a busy cursor
750 wxString *choices = new wxString[m_NumOfEntries];
751 wxString *urls = new wxString[m_NumOfEntries];
752 wxString compA, compB;
753
754 int idx = 0, j;
755 bool rc;
756 bool showAll = k.IsEmpty();
757 wxExtHelpMapList::iterator i = m_MapList->begin();
758
759 compA = k; compA.LowerCase(); // we compare case insensitive
760 while(i != m_MapList->end())
761 {
762 compB = (**i).doc; compB.LowerCase();
763 if((showAll || compB.Contains(k)) && ! compB.IsEmpty())
764 {
765 urls[idx] = (**i).url;
766 // doesn't work:
767 // choices[idx] = (**i).doc.Contains((**i).doc.Before(WXEXTHELP_COMMENTCHAR));
768 //if(choices[idx].IsEmpty()) // didn't contain the ';'
769 // choices[idx] = (**i).doc;
770 choices[idx] = "";
771 for(j=0;(**i).doc.c_str()[j]
772 && (**i).doc.c_str()[j] != WXEXTHELP_COMMENTCHAR; j++)
773 choices[idx] << (**i).doc.c_str()[j];
774 idx++;
775 }
776 i++;
777 }
778
779 if(idx == 1)
780 rc = CallBrowser(urls[0]);
781 else if(idx == 0)
782 {
783 wxMessageBox(_("No entries found."));
784 rc = false;
785 }
786 else
787 {
788 idx = wxGetSingleChoiceIndex(showAll ? _("Help Index") : _("Relevant entries:"),
789 showAll ? _("Help Index") : _("Entries found"),
790 idx,choices);
791 if(idx != -1)
792 rc = CallBrowser(urls[idx]);
793 else
794 rc = false;
795 }
796 delete[] urls;
797 delete[] choices;
798
799 return rc;
800 }
801
802
803 bool
804 wxExtHelpController::Quit(void)
805 {
806 return true;
807 }
808
809 void
810 wxExtHelpController::OnQuit(void)
811 {
812 }
813