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