no real change; just add the standard separator where it's missing
[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
9553702e 18#if wxUSE_HELP && !defined(__WXWINCE__)
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
5c7b5061
FM
55// Name for map file.
56#define WXEXTHELP_MAPFILE _T("wxhelp.map")
249b3fe3 57
5c7b5061
FM
58// Character introducing comments/documentation field in map file.
59#define WXEXTHELP_COMMENTCHAR ';'
69108ccb 60
5c7b5061
FM
61// The ID of the Contents section
62#define WXEXTHELP_CONTENTS_ID 0
69108ccb 63
5c7b5061
FM
64// Name of environment variable to set help browser.
65#define WXEXTHELP_ENVVAR_BROWSER wxT("WX_HELPBROWSER")
31528cd3 66
5c7b5061
FM
67// Is browser a netscape browser?
68#define WXEXTHELP_ENVVAR_BROWSERISNETSCAPE wxT("WX_HELPBROWSER_NS")
aa0ffd1d 69
5c7b5061 70IMPLEMENT_CLASS(wxExtHelpController, wxHelpControllerBase)
e9aad10a 71
a85a25c7
VZ
72wxExtHelpController::wxExtHelpController(wxWindow* parentWindow)
73 : wxHelpControllerBase(parentWindow)
e9aad10a 74{
5c7b5061
FM
75 m_MapList = NULL;
76 m_NumOfEntries = 0;
77 m_BrowserIsNetscape = false;
e9aad10a 78
5c7b5061
FM
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 }
e9aad10a
KB
86}
87
69108ccb
JS
88wxExtHelpController::~wxExtHelpController()
89{
5c7b5061 90 DeleteList();
69108ccb 91}
e9aad10a 92
878a936e 93#if WXWIN_COMPATIBILITY_2_8
69108ccb 94void wxExtHelpController::SetBrowser(const wxString& browsername, bool isNetscape)
e9aad10a 95{
5c7b5061
FM
96 m_BrowserName = browsername;
97 m_BrowserIsNetscape = isNetscape;
e9aad10a 98}
878a936e 99#endif
e9aad10a 100
33b64e6f
JS
101void wxExtHelpController::SetViewer(const wxString& viewer, long flags)
102{
5c7b5061
FM
103 m_BrowserName = viewer;
104 m_BrowserIsNetscape = (flags & wxHELP_NETSCAPE) != 0;
33b64e6f
JS
105}
106
b8c3d630 107bool wxExtHelpController::DisplayHelp(const wxString &relativeURL)
e9aad10a 108{
a85a25c7
VZ
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 )
ca65c044 126 return true;
a85a25c7
VZ
127 }
128 //else: either no browser explicitly specified or we failed to open it
129
130 // just use default browser
131 return wxLaunchDefaultBrowser(url);
e9aad10a
KB
132}
133
69108ccb
JS
134class wxExtHelpMapEntry : public wxObject
135{
136public:
5c7b5061
FM
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; }
69108ccb
JS
143};
144
145void wxExtHelpController::DeleteList()
146{
5c7b5061
FM
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;
d3b9f782 158 m_MapList = NULL;
5c7b5061 159 }
69108ccb
JS
160}
161
b8c3d630
DS
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
165bool wxExtHelpController::Initialize(const wxString& file)
69108ccb 166{
5c7b5061 167 return LoadFile(file);
69108ccb
JS
168}
169
249b3fe3 170bool wxExtHelpController::ParseMapFileLine(const wxString& line)
69108ccb 171{
249b3fe3
VZ
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}
69108ccb 217
249b3fe3
VZ
218// file is a misnomer as it's the name of the base help directory
219bool wxExtHelpController::LoadFile(const wxString& file)
220{
221 wxFileName helpDir(wxFileName::DirName(file));
222 helpDir.MakeAbsolute();
69108ccb 223
249b3fe3 224 bool dirExists = false;
69108ccb
JS
225
226#if wxUSE_INTL
249b3fe3
VZ
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
b8c3d630 242 if ( ! dirExists )
249b3fe3
VZ
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
b8c3d630 271 if ( ! dirExists && !helpDir.DirExists() )
249b3fe3
VZ
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);
b8c3d630 279 if ( ! mapFile.FileExists() )
249b3fe3
VZ
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
2364556b 313 m_helpDir = helpDir.GetFullPath(); // now it's valid
249b3fe3 314 return true;
69108ccb
JS
315}
316
317
b8c3d630 318bool wxExtHelpController::DisplayContents()
69108ccb 319{
5c7b5061
FM
320 if (! m_NumOfEntries)
321 return false;
69108ccb 322
5c7b5061
FM
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);
69108ccb
JS
348}
349
b8c3d630 350bool wxExtHelpController::DisplaySection(int sectionNo)
69108ccb 351{
5c7b5061
FM
352 if (! m_NumOfEntries)
353 return false;
69108ccb 354
5c7b5061
FM
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 }
b8c3d630 365
5c7b5061 366 return false;
69108ccb
JS
367}
368
369bool 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
b8c3d630 379bool wxExtHelpController::DisplayBlock(long blockNo)
69108ccb 380{
5c7b5061 381 return DisplaySection((int)blockNo);
69108ccb
JS
382}
383
b8c3d630 384bool wxExtHelpController::KeywordSearch(const wxString& k,
cb07c544 385 wxHelpSearchMode WXUNUSED(mode))
69108ccb 386{
b8c3d630 387 if (! m_NumOfEntries)
ca65c044 388 return false;
69108ccb 389
b8c3d630
DS
390 wxString *choices = new wxString[m_NumOfEntries];
391 wxString *urls = new wxString[m_NumOfEntries];
69108ccb 392
b8c3d630
DS
393 int idx = 0;
394 bool rc = false;
f50a1c3d 395 bool showAll = k.empty();
b8c3d630 396
f7b83689 397 wxList::compatibility_iterator node = m_MapList->GetFirst();
69108ccb
JS
398
399 {
b8c3d630
DS
400 // display a busy cursor
401 wxBusyCursor b;
402 wxString compA, compB;
403 wxExtHelpMapEntry *entry;
69108ccb 404
b8c3d630
DS
405 // we compare case insensitive
406 if (! showAll)
407 {
408 compA = k;
409 compA.LowerCase();
410 }
69108ccb 411
b8c3d630
DS
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:
c9f78968
VS
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
b8c3d630
DS
468 if (idx >= 0)
469 rc = DisplayHelp(urls[idx]);
470 break;
471 }
472
473 delete [] urls;
474 delete [] choices;
475
476 return rc;
69108ccb
JS
477}
478
479
480bool wxExtHelpController::Quit()
481{
ca65c044 482 return true;
69108ccb
JS
483}
484
485void wxExtHelpController::OnQuit()
486{
487}
488
31528cd3 489#endif // wxUSE_HELP