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