1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/generic/helpext.cpp
3 // Purpose:     an external help controller for wxWidgets
4 // Author:      Karsten Ballueder
5 // Modified by:
6 // Created:     04/01/98
7 // RCS-ID:      $Id: helpext.cpp 38857 2006-04-20 07:31:44Z ABX $
8 // Copyright:   (c) Karsten Ballueder
9 // Licence:     wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11 
12 #include "wx/wxprec.h"
13 
14 #ifdef __BORLANDC__
15     #pragma hdrstop
16 #endif
17 
18 #if wxUSE_HELP && !defined(__WXWINCE__) && (!defined(__WXMAC__) || defined(__WXMAC_OSX__))
19 
20 #ifndef WX_PRECOMP
21     #include "wx/list.h"
22     #include "wx/string.h"
23     #include "wx/utils.h"
24     #include "wx/intl.h"
25     #include "wx/msgdlg.h"
26     #include "wx/choicdlg.h"
27     #include "wx/log.h"
28 #endif
29 
30 #include "wx/filename.h"
31 #include "wx/textfile.h"
32 #include "wx/generic/helpext.h"
33 
34 #include <stdio.h>
35 #include <ctype.h>
36 #include <sys/stat.h>
37 
38 #if !defined(__WINDOWS__) && !defined(__OS2__)
39     #include   <unistd.h>
40 #endif
41 
42 #ifdef __WINDOWS__
43 #include "wx/msw/mslu.h"
44 #endif
45 
46 #ifdef __WXMSW__
47 #include <windows.h>
48 #include "wx/msw/winundef.h"
49 #endif
50 
51 // ----------------------------------------------------------------------------
52 // constants
53 // ----------------------------------------------------------------------------
54 
55 /// Name for map file.
56 #define WXEXTHELP_MAPFILE   _T("wxhelp.map")
57 
58 /// Character introducing comments/documentation field in map file.
59 #define WXEXTHELP_COMMENTCHAR   ';'
60 
61 #define CONTENTS_ID   0
62 
IMPLEMENT_CLASS(wxExtHelpController,wxHelpControllerBase)63 IMPLEMENT_CLASS(wxExtHelpController, wxHelpControllerBase)
64 
65 /// Name of environment variable to set help browser.
66 #define   WXEXTHELP_ENVVAR_BROWSER   wxT("WX_HELPBROWSER")
67 /// Is browser a netscape browser?
68 #define   WXEXTHELP_ENVVAR_BROWSERISNETSCAPE wxT("WX_HELPBROWSER_NS")
69 
70 /**
71    This class implements help via an external browser.
72    It requires the name of a directory containing the documentation
73    and a file mapping numerical Section numbers to relative URLS.
74 */
75 
76 wxExtHelpController::wxExtHelpController(wxWindow* parentWindow)
77                    : wxHelpControllerBase(parentWindow)
78 {
79    m_MapList = NULL;
80    m_NumOfEntries = 0;
81    m_BrowserIsNetscape = false;
82 
83    wxChar *browser = wxGetenv(WXEXTHELP_ENVVAR_BROWSER);
84    if (browser)
85    {
86       m_BrowserName = browser;
87       browser = wxGetenv(WXEXTHELP_ENVVAR_BROWSERISNETSCAPE);
88       m_BrowserIsNetscape = browser && (wxAtoi(browser) != 0);
89    }
90 }
91 
~wxExtHelpController()92 wxExtHelpController::~wxExtHelpController()
93 {
94    DeleteList();
95 }
96 
SetBrowser(const wxString & browsername,bool isNetscape)97 void wxExtHelpController::SetBrowser(const wxString& browsername, bool isNetscape)
98 {
99    m_BrowserName = browsername;
100    m_BrowserIsNetscape = isNetscape;
101 }
102 
103 // Set viewer: new, generic name for SetBrowser
SetViewer(const wxString & viewer,long flags)104 void wxExtHelpController::SetViewer(const wxString& viewer, long flags)
105 {
106     SetBrowser(viewer, (flags & wxHELP_NETSCAPE) != 0);
107 }
108 
DisplayHelp(const wxString & relativeURL)109 bool wxExtHelpController::DisplayHelp(const wxString &relativeURL)
110 {
111     // construct hte URL to open -- it's just a file
112     wxString url(_T("file://") + m_helpDir);
113     url << wxFILE_SEP_PATH << relativeURL;
114 
115     // use the explicit browser program if specified
116     if ( !m_BrowserName.empty() )
117     {
118         if ( m_BrowserIsNetscape )
119         {
120             wxString command;
121             command << m_BrowserName
122                     << wxT(" -remote openURL(") << url << wxT(')');
123             if ( wxExecute(command, wxEXEC_SYNC) != -1 )
124                 return true;
125         }
126 
127         if ( wxExecute(m_BrowserName + _T(' ') + url, wxEXEC_SYNC) != -1 )
128             return true;
129     }
130     //else: either no browser explicitly specified or we failed to open it
131 
132     // just use default browser
133     return wxLaunchDefaultBrowser(url);
134 }
135 
136 class wxExtHelpMapEntry : public wxObject
137 {
138 public:
139    int      id;
140    wxString url;
141    wxString doc;
wxExtHelpMapEntry(int iid,wxString const & iurl,wxString const & idoc)142    wxExtHelpMapEntry(int iid, wxString const &iurl, wxString const &idoc)
143       { id = iid; url = iurl; doc = idoc; }
144 };
145 
DeleteList()146 void wxExtHelpController::DeleteList()
147 {
148    if (m_MapList)
149    {
150       wxList::compatibility_iterator node = m_MapList->GetFirst();
151       while (node)
152       {
153          delete (wxExtHelpMapEntry *)node->GetData();
154          m_MapList->Erase(node);
155          node = m_MapList->GetFirst();
156       }
157 
158       delete m_MapList;
159       m_MapList = (wxList*) NULL;
160    }
161 }
162 
163 // This must be called to tell the controller where to find the documentation.
164 //  @param file - NOT a filename, but a directory name.
165 //  @return true on success
Initialize(const wxString & file)166 bool wxExtHelpController::Initialize(const wxString& file)
167 {
168    return LoadFile(file);
169 }
170 
ParseMapFileLine(const wxString & line)171 bool wxExtHelpController::ParseMapFileLine(const wxString& line)
172 {
173     const wxChar *p = line.c_str();
174 
175     // skip whitespace
176     while ( isascii(*p) && isspace(*p) )
177         p++;
178 
179     // skip empty lines and comments
180     if ( *p == _T('\0') || *p == WXEXTHELP_COMMENTCHAR )
181         return true;
182 
183     // the line is of the form "num url" so we must have an integer now
184     wxChar *end;
185     const unsigned long id = wxStrtoul(p, &end, 0);
186 
187     if ( end == p )
188         return false;
189 
190     p = end;
191     while ( isascii(*p) && isspace(*p) )
192         p++;
193 
194     // next should be the URL
195     wxString url;
196     url.reserve(line.length());
197     while ( isascii(*p) && !isspace(*p) )
198         url += *p++;
199 
200     while ( isascii(*p) && isspace(*p) )
201         p++;
202 
203     // and finally the optional description of the entry after comment
204     wxString doc;
205     if ( *p == WXEXTHELP_COMMENTCHAR )
206     {
207         p++;
208         while ( isascii(*p) && isspace(*p) )
209             p++;
210         doc = p;
211     }
212 
213     m_MapList->Append(new wxExtHelpMapEntry(id, url, doc));
214     m_NumOfEntries++;
215 
216     return true;
217 }
218 
219 // file is a misnomer as it's the name of the base help directory
LoadFile(const wxString & file)220 bool wxExtHelpController::LoadFile(const wxString& file)
221 {
222     wxFileName helpDir(wxFileName::DirName(file));
223     helpDir.MakeAbsolute();
224 
225     bool dirExists = false;
226 
227 #if wxUSE_INTL
228     // If a locale is set, look in file/localename, i.e. If passed
229     // "/usr/local/myapp/help" and the current wxLocale is set to be "de", then
230     // look in "/usr/local/myapp/help/de/" first and fall back to
231     // "/usr/local/myapp/help" if that doesn't exist.
232     const wxLocale * const loc = wxGetLocale();
233     if ( loc )
234     {
235         wxString locName = loc->GetName();
236 
237         // the locale is in general of the form xx_YY.zzzz, try the full firm
238         // first and then also more general ones
239         wxFileName helpDirLoc(helpDir);
240         helpDirLoc.AppendDir(locName);
241         dirExists = helpDirLoc.DirExists();
242 
243         if ( ! dirExists )
244         {
245             // try without encoding
246             const wxString locNameWithoutEncoding = locName.BeforeLast(_T('.'));
247             if ( !locNameWithoutEncoding.empty() )
248             {
249                 helpDirLoc = helpDir;
250                 helpDirLoc.AppendDir(locNameWithoutEncoding);
251                 dirExists = helpDirLoc.DirExists();
252             }
253         }
254 
255         if ( !dirExists )
256         {
257             // try without country part
258             wxString locNameWithoutCountry = locName.BeforeLast(_T('_'));
259             if ( !locNameWithoutCountry.empty() )
260             {
261                 helpDirLoc = helpDir;
262                 helpDirLoc.AppendDir(locNameWithoutCountry);
263                 dirExists = helpDirLoc.DirExists();
264             }
265         }
266 
267         if ( dirExists )
268             helpDir = helpDirLoc;
269     }
270 #endif // wxUSE_INTL
271 
272     if ( ! dirExists && !helpDir.DirExists() )
273     {
274         wxLogError(_("Help directory \"%s\" not found."),
275                    helpDir.GetFullPath().c_str());
276         return false;
277     }
278 
279     const wxFileName mapFile(helpDir.GetFullPath(), WXEXTHELP_MAPFILE);
280     if ( ! mapFile.FileExists() )
281     {
282         wxLogError(_("Help file \"%s\" not found."),
283                    mapFile.GetFullPath().c_str());
284         return false;
285     }
286 
287     DeleteList();
288     m_MapList = new wxList;
289     m_NumOfEntries = 0;
290 
291     wxTextFile input;
292     if ( !input.Open(mapFile.GetFullPath()) )
293         return false;
294 
295     for ( wxString& line = input.GetFirstLine();
296           !input.Eof();
297           line = input.GetNextLine() )
298     {
299         if ( !ParseMapFileLine(line) )
300         {
301             wxLogWarning(_("Line %lu of map file \"%s\" has invalid syntax, skipped."),
302                          (unsigned long)input.GetCurrentLine(),
303                          mapFile.GetFullPath().c_str());
304         }
305     }
306 
307     if ( !m_NumOfEntries )
308     {
309         wxLogError(_("No valid mappings found in the file \"%s\"."),
310                    mapFile.GetFullPath().c_str());
311         return false;
312     }
313 
314     m_helpDir = helpDir.GetFullPath(); // now it's valid
315     return true;
316 }
317 
318 
DisplayContents()319 bool wxExtHelpController::DisplayContents()
320 {
321    if (! m_NumOfEntries)
322       return false;
323 
324    wxString contents;
325    wxList::compatibility_iterator node = m_MapList->GetFirst();
326    wxExtHelpMapEntry *entry;
327    while (node)
328    {
329       entry = (wxExtHelpMapEntry *)node->GetData();
330       if (entry->id == CONTENTS_ID)
331       {
332          contents = entry->url;
333          break;
334       }
335 
336       node = node->GetNext();
337    }
338 
339    bool rc = false;
340    wxString file;
341    file << m_helpDir << wxFILE_SEP_PATH << contents;
342    if (file.Contains(wxT('#')))
343       file = file.BeforeLast(wxT('#'));
344    if (contents.length() && wxFileExists(file))
345       rc = DisplaySection(CONTENTS_ID);
346 
347    // if not found, open homemade toc:
348    return rc ? true : KeywordSearch(wxEmptyString);
349 }
350 
DisplaySection(int sectionNo)351 bool wxExtHelpController::DisplaySection(int sectionNo)
352 {
353    if (! m_NumOfEntries)
354       return false;
355 
356    wxBusyCursor b; // display a busy cursor
357    wxList::compatibility_iterator node = m_MapList->GetFirst();
358    wxExtHelpMapEntry *entry;
359    while (node)
360    {
361       entry = (wxExtHelpMapEntry *)node->GetData();
362       if (entry->id == sectionNo)
363          return DisplayHelp(entry->url);
364       node = node->GetNext();
365    }
366 
367    return false;
368 }
369 
DisplaySection(const wxString & section)370 bool wxExtHelpController::DisplaySection(const wxString& section)
371 {
372     bool isFilename = (section.Find(wxT(".htm")) != -1);
373 
374     if (isFilename)
375         return DisplayHelp(section);
376     else
377         return KeywordSearch(section);
378 }
379 
DisplayBlock(long blockNo)380 bool wxExtHelpController::DisplayBlock(long blockNo)
381 {
382    return DisplaySection((int)blockNo);
383 }
384 
KeywordSearch(const wxString & k,wxHelpSearchMode WXUNUSED (mode))385 bool wxExtHelpController::KeywordSearch(const wxString& k,
386                                    wxHelpSearchMode WXUNUSED(mode))
387 {
388    if (! m_NumOfEntries)
389       return false;
390 
391    wxString *choices = new wxString[m_NumOfEntries];
392    wxString *urls = new wxString[m_NumOfEntries];
393 
394    int          idx = 0;
395    bool         rc = false;
396    bool         showAll = k.empty();
397 
398    wxList::compatibility_iterator node = m_MapList->GetFirst();
399 
400    {
401         // display a busy cursor
402         wxBusyCursor b;
403         wxString compA, compB;
404         wxExtHelpMapEntry *entry;
405 
406         // we compare case insensitive
407         if (! showAll)
408         {
409             compA = k;
410             compA.LowerCase();
411         }
412 
413         while (node)
414         {
415             entry = (wxExtHelpMapEntry *)node->GetData();
416             compB = entry->doc;
417 
418             bool testTarget = ! compB.empty();
419             if (testTarget && ! showAll)
420             {
421                 compB.LowerCase();
422                 testTarget = compB.Contains(compA);
423             }
424 
425             if (testTarget)
426             {
427                 urls[idx] = entry->url;
428                 // doesn't work:
429                 // choices[idx] = (**i).doc.Contains((**i).doc.Before(WXEXTHELP_COMMENTCHAR));
430                 //if (choices[idx].empty()) // didn't contain the ';'
431                 //   choices[idx] = (**i).doc;
432                 choices[idx] = wxEmptyString;
433                 for (int j=0; ; j++)
434                 {
435                     wxChar targetChar = entry->doc.c_str()[j];
436                     if ((targetChar == 0) || (targetChar == WXEXTHELP_COMMENTCHAR))
437                         break;
438 
439                     choices[idx] << targetChar;
440                 }
441 
442                 idx++;
443             }
444 
445             node = node->GetNext();
446         }
447     }
448 
449     switch (idx)
450     {
451     case 0:
452         wxMessageBox(_("No entries found."));
453         break;
454 
455     case 1:
456         rc = DisplayHelp(urls[0]);
457         break;
458 
459     default:
460         idx = wxGetSingleChoiceIndex(
461             showAll ? _("Help Index") : _("Relevant entries:"),
462             showAll ? _("Help Index") : _("Entries found"),
463             idx, choices);
464         if (idx >= 0)
465             rc = DisplayHelp(urls[idx]);
466         break;
467     }
468 
469     delete [] urls;
470     delete [] choices;
471 
472     return rc;
473 }
474 
475 
Quit()476 bool wxExtHelpController::Quit()
477 {
478    return true;
479 }
480 
OnQuit()481 void wxExtHelpController::OnQuit()
482 {
483 }
484 
485 #endif // wxUSE_HELP
486