Use wxDELETE() and wxDELETEA() when possible.
[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 wxT("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(wxT("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 + wxT(' ') + 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 wxDELETE(m_MapList);
158 }
159 }
160
161 // This must be called to tell the controller where to find the documentation.
162 // @param file - NOT a filename, but a directory name.
163 // @return true on success
164 bool wxExtHelpController::Initialize(const wxString& file)
165 {
166 return LoadFile(file);
167 }
168
169 bool wxExtHelpController::ParseMapFileLine(const wxString& line)
170 {
171 const wxChar *p = line.c_str();
172
173 // skip whitespace
174 while ( isascii(*p) && wxIsspace(*p) )
175 p++;
176
177 // skip empty lines and comments
178 if ( *p == wxT('\0') || *p == WXEXTHELP_COMMENTCHAR )
179 return true;
180
181 // the line is of the form "num url" so we must have an integer now
182 wxChar *end;
183 const unsigned long id = wxStrtoul(p, &end, 0);
184
185 if ( end == p )
186 return false;
187
188 p = end;
189 while ( isascii(*p) && wxIsspace(*p) )
190 p++;
191
192 // next should be the URL
193 wxString url;
194 url.reserve(line.length());
195 while ( isascii(*p) && !wxIsspace(*p) )
196 url += *p++;
197
198 while ( isascii(*p) && wxIsspace(*p) )
199 p++;
200
201 // and finally the optional description of the entry after comment
202 wxString doc;
203 if ( *p == WXEXTHELP_COMMENTCHAR )
204 {
205 p++;
206 while ( isascii(*p) && wxIsspace(*p) )
207 p++;
208 doc = p;
209 }
210
211 m_MapList->Append(new wxExtHelpMapEntry(id, url, doc));
212 m_NumOfEntries++;
213
214 return true;
215 }
216
217 // file is a misnomer as it's the name of the base help directory
218 bool wxExtHelpController::LoadFile(const wxString& file)
219 {
220 wxFileName helpDir(wxFileName::DirName(file));
221 helpDir.MakeAbsolute();
222
223 bool dirExists = false;
224
225 #if wxUSE_INTL
226 // If a locale is set, look in file/localename, i.e. If passed
227 // "/usr/local/myapp/help" and the current wxLocale is set to be "de", then
228 // look in "/usr/local/myapp/help/de/" first and fall back to
229 // "/usr/local/myapp/help" if that doesn't exist.
230 const wxLocale * const loc = wxGetLocale();
231 if ( loc )
232 {
233 wxString locName = loc->GetName();
234
235 // the locale is in general of the form xx_YY.zzzz, try the full firm
236 // first and then also more general ones
237 wxFileName helpDirLoc(helpDir);
238 helpDirLoc.AppendDir(locName);
239 dirExists = helpDirLoc.DirExists();
240
241 if ( ! dirExists )
242 {
243 // try without encoding
244 const wxString locNameWithoutEncoding = locName.BeforeLast(wxT('.'));
245 if ( !locNameWithoutEncoding.empty() )
246 {
247 helpDirLoc = helpDir;
248 helpDirLoc.AppendDir(locNameWithoutEncoding);
249 dirExists = helpDirLoc.DirExists();
250 }
251 }
252
253 if ( !dirExists )
254 {
255 // try without country part
256 wxString locNameWithoutCountry = locName.BeforeLast(wxT('_'));
257 if ( !locNameWithoutCountry.empty() )
258 {
259 helpDirLoc = helpDir;
260 helpDirLoc.AppendDir(locNameWithoutCountry);
261 dirExists = helpDirLoc.DirExists();
262 }
263 }
264
265 if ( dirExists )
266 helpDir = helpDirLoc;
267 }
268 #endif // wxUSE_INTL
269
270 if ( ! dirExists && !helpDir.DirExists() )
271 {
272 wxLogError(_("Help directory \"%s\" not found."),
273 helpDir.GetFullPath().c_str());
274 return false;
275 }
276
277 const wxFileName mapFile(helpDir.GetFullPath(), WXEXTHELP_MAPFILE);
278 if ( ! mapFile.FileExists() )
279 {
280 wxLogError(_("Help file \"%s\" not found."),
281 mapFile.GetFullPath().c_str());
282 return false;
283 }
284
285 DeleteList();
286 m_MapList = new wxList;
287 m_NumOfEntries = 0;
288
289 wxTextFile input;
290 if ( !input.Open(mapFile.GetFullPath()) )
291 return false;
292
293 for ( wxString& line = input.GetFirstLine();
294 !input.Eof();
295 line = input.GetNextLine() )
296 {
297 if ( !ParseMapFileLine(line) )
298 {
299 wxLogWarning(_("Line %lu of map file \"%s\" has invalid syntax, skipped."),
300 (unsigned long)input.GetCurrentLine(),
301 mapFile.GetFullPath().c_str());
302 }
303 }
304
305 if ( !m_NumOfEntries )
306 {
307 wxLogError(_("No valid mappings found in the file \"%s\"."),
308 mapFile.GetFullPath().c_str());
309 return false;
310 }
311
312 m_helpDir = helpDir.GetFullPath(); // now it's valid
313 return true;
314 }
315
316
317 bool wxExtHelpController::DisplayContents()
318 {
319 if (! m_NumOfEntries)
320 return false;
321
322 wxString contents;
323 wxList::compatibility_iterator node = m_MapList->GetFirst();
324 wxExtHelpMapEntry *entry;
325 while (node)
326 {
327 entry = (wxExtHelpMapEntry *)node->GetData();
328 if (entry->id == WXEXTHELP_CONTENTS_ID)
329 {
330 contents = entry->url;
331 break;
332 }
333
334 node = node->GetNext();
335 }
336
337 bool rc = false;
338 wxString file;
339 file << m_helpDir << wxFILE_SEP_PATH << contents;
340 if (file.Contains(wxT('#')))
341 file = file.BeforeLast(wxT('#'));
342 if (contents.length() && wxFileExists(file))
343 rc = DisplaySection(WXEXTHELP_CONTENTS_ID);
344
345 // if not found, open homemade toc:
346 return rc ? true : KeywordSearch(wxEmptyString);
347 }
348
349 bool wxExtHelpController::DisplaySection(int sectionNo)
350 {
351 if (! m_NumOfEntries)
352 return false;
353
354 wxBusyCursor b; // display a busy cursor
355 wxList::compatibility_iterator node = m_MapList->GetFirst();
356 wxExtHelpMapEntry *entry;
357 while (node)
358 {
359 entry = (wxExtHelpMapEntry *)node->GetData();
360 if (entry->id == sectionNo)
361 return DisplayHelp(entry->url);
362 node = node->GetNext();
363 }
364
365 return false;
366 }
367
368 bool wxExtHelpController::DisplaySection(const wxString& section)
369 {
370 bool isFilename = (section.Find(wxT(".htm")) != -1);
371
372 if (isFilename)
373 return DisplayHelp(section);
374 else
375 return KeywordSearch(section);
376 }
377
378 bool wxExtHelpController::DisplayBlock(long blockNo)
379 {
380 return DisplaySection((int)blockNo);
381 }
382
383 bool wxExtHelpController::KeywordSearch(const wxString& k,
384 wxHelpSearchMode WXUNUSED(mode))
385 {
386 if (! m_NumOfEntries)
387 return false;
388
389 wxString *choices = new wxString[m_NumOfEntries];
390 wxString *urls = new wxString[m_NumOfEntries];
391
392 int idx = 0;
393 bool rc = false;
394 bool showAll = k.empty();
395
396 wxList::compatibility_iterator node = m_MapList->GetFirst();
397
398 {
399 // display a busy cursor
400 wxBusyCursor b;
401 wxString compA, compB;
402 wxExtHelpMapEntry *entry;
403
404 // we compare case insensitive
405 if (! showAll)
406 {
407 compA = k;
408 compA.LowerCase();
409 }
410
411 while (node)
412 {
413 entry = (wxExtHelpMapEntry *)node->GetData();
414 compB = entry->doc;
415
416 bool testTarget = ! compB.empty();
417 if (testTarget && ! showAll)
418 {
419 compB.LowerCase();
420 testTarget = compB.Contains(compA);
421 }
422
423 if (testTarget)
424 {
425 urls[idx] = entry->url;
426 // doesn't work:
427 // choices[idx] = (**i).doc.Contains((**i).doc.Before(WXEXTHELP_COMMENTCHAR));
428 //if (choices[idx].empty()) // didn't contain the ';'
429 // choices[idx] = (**i).doc;
430 choices[idx] = wxEmptyString;
431 for (int j=0; ; j++)
432 {
433 wxChar targetChar = entry->doc.c_str()[j];
434 if ((targetChar == 0) || (targetChar == WXEXTHELP_COMMENTCHAR))
435 break;
436
437 choices[idx] << targetChar;
438 }
439
440 idx++;
441 }
442
443 node = node->GetNext();
444 }
445 }
446
447 switch (idx)
448 {
449 case 0:
450 wxMessageBox(_("No entries found."));
451 break;
452
453 case 1:
454 rc = DisplayHelp(urls[0]);
455 break;
456
457 default:
458 if (showAll)
459 idx = wxGetSingleChoiceIndex(_("Help Index"),
460 _("Help Index"),
461 idx, choices);
462 else
463 idx = wxGetSingleChoiceIndex(_("Relevant entries:"),
464 _("Entries found"),
465 idx, choices);
466
467 if (idx >= 0)
468 rc = DisplayHelp(urls[idx]);
469 break;
470 }
471
472 delete [] urls;
473 delete [] choices;
474
475 return rc;
476 }
477
478
479 bool wxExtHelpController::Quit()
480 {
481 return true;
482 }
483
484 void wxExtHelpController::OnQuit()
485 {
486 }
487
488 #endif // wxUSE_HELP