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
63IMPLEMENT_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
76wxExtHelpController::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
92wxExtHelpController::~wxExtHelpController()
93{
94   DeleteList();
95}
96
97void 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
104void wxExtHelpController::SetViewer(const wxString& viewer, long flags)
105{
106    SetBrowser(viewer, (flags & wxHELP_NETSCAPE) != 0);
107}
108
109bool 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
136class wxExtHelpMapEntry : public wxObject
137{
138public:
139   int      id;
140   wxString url;
141   wxString doc;
142   wxExtHelpMapEntry(int iid, wxString const &iurl, wxString const &idoc)
143      { id = iid; url = iurl; doc = idoc; }
144};
145
146void 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
166bool wxExtHelpController::Initialize(const wxString& file)
167{
168   return LoadFile(file);
169}
170
171bool 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
220bool 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
319bool 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
351bool 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
370bool 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
380bool wxExtHelpController::DisplayBlock(long blockNo)
381{
382   return DisplaySection((int)blockNo);
383}
384
385bool 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
476bool wxExtHelpController::Quit()
477{
478   return true;
479}
480
481void wxExtHelpController::OnQuit()
482{
483}
484
485#endif // wxUSE_HELP
486