]> git.saurik.com Git - wxWidgets.git/blame - src/generic/helpext.cpp
don't assume there's always an active wxEventLoop instance
[wxWidgets.git] / src / generic / helpext.cpp
CommitLineData
f96b60aa 1/////////////////////////////////////////////////////////////////////////////
7fc65a03 2// Name: src/generic/helpext.cpp
77ffb593 3// Purpose: an external help controller for wxWidgets
f96b60aa
VZ
4// Author: Karsten Ballueder
5// Modified by:
6// Created: 04/01/98
7// RCS-ID: $Id$
8// Copyright: (c) Karsten Ballueder
65571936 9// Licence: wxWindows licence
f96b60aa
VZ
10/////////////////////////////////////////////////////////////////////////////
11
f96b60aa
VZ
12#include "wx/wxprec.h"
13
14#ifdef __BORLANDC__
15 #pragma hdrstop
16#endif
17
7e00fd89 18#if wxUSE_HELP && !defined(__WXWINCE__) && (!defined(__WXMAC__) || defined(__WXMAC_OSX__))
31528cd3 19
f96b60aa 20#ifndef WX_PRECOMP
8ecff181 21 #include "wx/list.h"
f96b60aa
VZ
22 #include "wx/string.h"
23 #include "wx/utils.h"
f96b60aa 24 #include "wx/intl.h"
29d0a26e
MB
25 #include "wx/msgdlg.h"
26 #include "wx/choicdlg.h"
1020103f 27 #include "wx/log.h"
f96b60aa
VZ
28#endif
29
249b3fe3
VZ
30#include "wx/filename.h"
31#include "wx/textfile.h"
f96b60aa
VZ
32#include "wx/generic/helpext.h"
33
34#include <stdio.h>
35#include <ctype.h>
36#include <sys/stat.h>
37
004fd0c8 38#if !defined(__WINDOWS__) && !defined(__OS2__)
f96b60aa
VZ
39 #include <unistd.h>
40#endif
e9aad10a 41
ec904840
CE
42#ifdef __WINDOWS__
43#include "wx/msw/mslu.h"
44#endif
45
583f6c5c
JS
46#ifdef __WXMSW__
47#include <windows.h>
7acf6a92 48#include "wx/msw/winundef.h"
583f6c5c
JS
49#endif
50
69108ccb
JS
51// ----------------------------------------------------------------------------
52// constants
53// ----------------------------------------------------------------------------
54
55/// Name for map file.
56#define WXEXTHELP_MAPFILE _T("wxhelp.map")
249b3fe3 57
69108ccb
JS
58/// Character introducing comments/documentation field in map file.
59#define WXEXTHELP_COMMENTCHAR ';'
60
61#define CONTENTS_ID 0
62
63IMPLEMENT_CLASS(wxExtHelpController, wxHelpControllerBase)
31528cd3 64
aa0ffd1d 65/// Name of environment variable to set help browser.
2b5f62a0 66#define WXEXTHELP_ENVVAR_BROWSER wxT("WX_HELPBROWSER")
aa0ffd1d 67/// Is browser a netscape browser?
2b5f62a0 68#define WXEXTHELP_ENVVAR_BROWSERISNETSCAPE wxT("WX_HELPBROWSER_NS")
aa0ffd1d 69
e9aad10a
KB
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
a85a25c7
VZ
76wxExtHelpController::wxExtHelpController(wxWindow* parentWindow)
77 : wxHelpControllerBase(parentWindow)
e9aad10a 78{
a85a25c7 79 m_MapList = NULL;
69108ccb 80 m_NumOfEntries = 0;
a85a25c7 81 m_BrowserIsNetscape = false;
e9aad10a 82
2b5f62a0 83 wxChar *browser = wxGetenv(WXEXTHELP_ENVVAR_BROWSER);
b8c3d630 84 if (browser)
e9aad10a
KB
85 {
86 m_BrowserName = browser;
2b5f62a0
VZ
87 browser = wxGetenv(WXEXTHELP_ENVVAR_BROWSERISNETSCAPE);
88 m_BrowserIsNetscape = browser && (wxAtoi(browser) != 0);
e9aad10a
KB
89 }
90}
91
69108ccb
JS
92wxExtHelpController::~wxExtHelpController()
93{
94 DeleteList();
95}
e9aad10a 96
69108ccb 97void wxExtHelpController::SetBrowser(const wxString& browsername, bool isNetscape)
e9aad10a
KB
98{
99 m_BrowserName = browsername;
100 m_BrowserIsNetscape = isNetscape;
101}
102
33b64e6f
JS
103// Set viewer: new, generic name for SetBrowser
104void wxExtHelpController::SetViewer(const wxString& viewer, long flags)
105{
a85a25c7 106 SetBrowser(viewer, (flags & wxHELP_NETSCAPE) != 0);
33b64e6f
JS
107}
108
b8c3d630 109bool wxExtHelpController::DisplayHelp(const wxString &relativeURL)
e9aad10a 110{
a85a25c7
VZ
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 )
ca65c044 128 return true;
a85a25c7
VZ
129 }
130 //else: either no browser explicitly specified or we failed to open it
131
132 // just use default browser
133 return wxLaunchDefaultBrowser(url);
e9aad10a
KB
134}
135
69108ccb
JS
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{
b8c3d630 148 if (m_MapList)
69108ccb 149 {
f7b83689 150 wxList::compatibility_iterator node = m_MapList->GetFirst();
69108ccb
JS
151 while (node)
152 {
153 delete (wxExtHelpMapEntry *)node->GetData();
f7b83689 154 m_MapList->Erase(node);
69108ccb
JS
155 node = m_MapList->GetFirst();
156 }
b8c3d630 157
69108ccb
JS
158 delete m_MapList;
159 m_MapList = (wxList*) NULL;
160 }
161}
162
b8c3d630
DS
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)
69108ccb
JS
167{
168 return LoadFile(file);
169}
170
249b3fe3 171bool wxExtHelpController::ParseMapFileLine(const wxString& line)
69108ccb 172{
249b3fe3
VZ
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}
69108ccb 218
249b3fe3
VZ
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();
69108ccb 224
249b3fe3 225 bool dirExists = false;
69108ccb
JS
226
227#if wxUSE_INTL
249b3fe3
VZ
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
b8c3d630 243 if ( ! dirExists )
249b3fe3
VZ
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
b8c3d630 272 if ( ! dirExists && !helpDir.DirExists() )
249b3fe3
VZ
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);
b8c3d630 280 if ( ! mapFile.FileExists() )
249b3fe3
VZ
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
2364556b 314 m_helpDir = helpDir.GetFullPath(); // now it's valid
249b3fe3 315 return true;
69108ccb
JS
316}
317
318
b8c3d630 319bool wxExtHelpController::DisplayContents()
69108ccb 320{
b8c3d630 321 if (! m_NumOfEntries)
ca65c044 322 return false;
69108ccb
JS
323
324 wxString contents;
f7b83689 325 wxList::compatibility_iterator node = m_MapList->GetFirst();
69108ccb 326 wxExtHelpMapEntry *entry;
b8c3d630 327 while (node)
69108ccb
JS
328 {
329 entry = (wxExtHelpMapEntry *)node->GetData();
b8c3d630 330 if (entry->id == CONTENTS_ID)
69108ccb
JS
331 {
332 contents = entry->url;
333 break;
334 }
b8c3d630 335
69108ccb
JS
336 node = node->GetNext();
337 }
338
ca65c044 339 bool rc = false;
69108ccb 340 wxString file;
2364556b 341 file << m_helpDir << wxFILE_SEP_PATH << contents;
b8c3d630 342 if (file.Contains(wxT('#')))
69108ccb 343 file = file.BeforeLast(wxT('#'));
b8c3d630 344 if (contents.length() && wxFileExists(file))
69108ccb
JS
345 rc = DisplaySection(CONTENTS_ID);
346
347 // if not found, open homemade toc:
ca65c044 348 return rc ? true : KeywordSearch(wxEmptyString);
69108ccb
JS
349}
350
b8c3d630 351bool wxExtHelpController::DisplaySection(int sectionNo)
69108ccb 352{
b8c3d630 353 if (! m_NumOfEntries)
ca65c044 354 return false;
69108ccb
JS
355
356 wxBusyCursor b; // display a busy cursor
f7b83689 357 wxList::compatibility_iterator node = m_MapList->GetFirst();
69108ccb 358 wxExtHelpMapEntry *entry;
b8c3d630 359 while (node)
69108ccb
JS
360 {
361 entry = (wxExtHelpMapEntry *)node->GetData();
b8c3d630 362 if (entry->id == sectionNo)
69108ccb
JS
363 return DisplayHelp(entry->url);
364 node = node->GetNext();
365 }
b8c3d630 366
ca65c044 367 return false;
69108ccb
JS
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
b8c3d630 380bool wxExtHelpController::DisplayBlock(long blockNo)
69108ccb
JS
381{
382 return DisplaySection((int)blockNo);
383}
384
b8c3d630 385bool wxExtHelpController::KeywordSearch(const wxString& k,
cb07c544 386 wxHelpSearchMode WXUNUSED(mode))
69108ccb 387{
b8c3d630 388 if (! m_NumOfEntries)
ca65c044 389 return false;
69108ccb 390
b8c3d630
DS
391 wxString *choices = new wxString[m_NumOfEntries];
392 wxString *urls = new wxString[m_NumOfEntries];
69108ccb 393
b8c3d630
DS
394 int idx = 0;
395 bool rc = false;
f50a1c3d 396 bool showAll = k.empty();
b8c3d630 397
f7b83689 398 wxList::compatibility_iterator node = m_MapList->GetFirst();
69108ccb
JS
399
400 {
b8c3d630
DS
401 // display a busy cursor
402 wxBusyCursor b;
403 wxString compA, compB;
404 wxExtHelpMapEntry *entry;
69108ccb 405
b8c3d630
DS
406 // we compare case insensitive
407 if (! showAll)
408 {
409 compA = k;
410 compA.LowerCase();
411 }
69108ccb 412
b8c3d630
DS
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;
69108ccb
JS
473}
474
475
476bool wxExtHelpController::Quit()
477{
ca65c044 478 return true;
69108ccb
JS
479}
480
481void wxExtHelpController::OnQuit()
482{
483}
484
31528cd3 485#endif // wxUSE_HELP