]> git.saurik.com Git - wxWidgets.git/blame - src/generic/helpext.cpp
Handle Shift-TAB correctly in wxOSX/Carbon wxComboBox.
[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 55// Name for map file.
9a83f860 56#define WXEXTHELP_MAPFILE wxT("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 109 // construct hte URL to open -- it's just a file
9a83f860 110 wxString url(wxT("file://") + m_helpDir);
a85a25c7
VZ
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
9a83f860 125 if ( wxExecute(m_BrowserName + wxT(' ') + 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:
6485c8d7 137 int entryid;
5c7b5061
FM
138 wxString url;
139 wxString doc;
140
141 wxExtHelpMapEntry(int iid, wxString const &iurl, wxString const &idoc)
6485c8d7 142 { entryid = 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
5276b0a5 157 wxDELETE(m_MapList);
5c7b5061 158 }
69108ccb
JS
159}
160
b8c3d630
DS
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
164bool wxExtHelpController::Initialize(const wxString& file)
69108ccb 165{
5c7b5061 166 return LoadFile(file);
69108ccb
JS
167}
168
249b3fe3 169bool wxExtHelpController::ParseMapFileLine(const wxString& line)
69108ccb 170{
249b3fe3
VZ
171 const wxChar *p = line.c_str();
172
173 // skip whitespace
7a0079d5 174 while ( isascii(*p) && wxIsspace(*p) )
249b3fe3
VZ
175 p++;
176
177 // skip empty lines and comments
9a83f860 178 if ( *p == wxT('\0') || *p == WXEXTHELP_COMMENTCHAR )
249b3fe3
VZ
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;
7a0079d5 189 while ( isascii(*p) && wxIsspace(*p) )
249b3fe3
VZ
190 p++;
191
192 // next should be the URL
193 wxString url;
194 url.reserve(line.length());
7a0079d5 195 while ( isascii(*p) && !wxIsspace(*p) )
249b3fe3
VZ
196 url += *p++;
197
7a0079d5 198 while ( isascii(*p) && wxIsspace(*p) )
249b3fe3
VZ
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++;
7a0079d5 206 while ( isascii(*p) && wxIsspace(*p) )
249b3fe3
VZ
207 p++;
208 doc = p;
209 }
210
211 m_MapList->Append(new wxExtHelpMapEntry(id, url, doc));
212 m_NumOfEntries++;
213
214 return true;
215}
69108ccb 216
249b3fe3
VZ
217// file is a misnomer as it's the name of the base help directory
218bool wxExtHelpController::LoadFile(const wxString& file)
219{
220 wxFileName helpDir(wxFileName::DirName(file));
221 helpDir.MakeAbsolute();
69108ccb 222
249b3fe3 223 bool dirExists = false;
69108ccb
JS
224
225#if wxUSE_INTL
249b3fe3
VZ
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
b8c3d630 241 if ( ! dirExists )
249b3fe3
VZ
242 {
243 // try without encoding
9a83f860 244 const wxString locNameWithoutEncoding = locName.BeforeLast(wxT('.'));
249b3fe3
VZ
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
9a83f860 256 wxString locNameWithoutCountry = locName.BeforeLast(wxT('_'));
249b3fe3
VZ
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
b8c3d630 270 if ( ! dirExists && !helpDir.DirExists() )
249b3fe3
VZ
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);
b8c3d630 278 if ( ! mapFile.FileExists() )
249b3fe3
VZ
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
2364556b 312 m_helpDir = helpDir.GetFullPath(); // now it's valid
249b3fe3 313 return true;
69108ccb
JS
314}
315
316
b8c3d630 317bool wxExtHelpController::DisplayContents()
69108ccb 318{
5c7b5061
FM
319 if (! m_NumOfEntries)
320 return false;
69108ccb 321
5c7b5061
FM
322 wxString contents;
323 wxList::compatibility_iterator node = m_MapList->GetFirst();
324 wxExtHelpMapEntry *entry;
325 while (node)
326 {
327 entry = (wxExtHelpMapEntry *)node->GetData();
6485c8d7 328 if (entry->entryid == WXEXTHELP_CONTENTS_ID)
5c7b5061
FM
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('#'));
6636ef8d 342 if ( wxFileExists(file) )
5c7b5061
FM
343 rc = DisplaySection(WXEXTHELP_CONTENTS_ID);
344
345 // if not found, open homemade toc:
346 return rc ? true : KeywordSearch(wxEmptyString);
69108ccb
JS
347}
348
b8c3d630 349bool wxExtHelpController::DisplaySection(int sectionNo)
69108ccb 350{
5c7b5061
FM
351 if (! m_NumOfEntries)
352 return false;
69108ccb 353
5c7b5061
FM
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();
6485c8d7 360 if (entry->entryid == sectionNo)
5c7b5061
FM
361 return DisplayHelp(entry->url);
362 node = node->GetNext();
363 }
b8c3d630 364
5c7b5061 365 return false;
69108ccb
JS
366}
367
368bool 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
b8c3d630 378bool wxExtHelpController::DisplayBlock(long blockNo)
69108ccb 379{
5c7b5061 380 return DisplaySection((int)blockNo);
69108ccb
JS
381}
382
b8c3d630 383bool wxExtHelpController::KeywordSearch(const wxString& k,
cb07c544 384 wxHelpSearchMode WXUNUSED(mode))
69108ccb 385{
b8c3d630 386 if (! m_NumOfEntries)
ca65c044 387 return false;
69108ccb 388
b8c3d630
DS
389 wxString *choices = new wxString[m_NumOfEntries];
390 wxString *urls = new wxString[m_NumOfEntries];
69108ccb 391
b8c3d630
DS
392 int idx = 0;
393 bool rc = false;
f50a1c3d 394 bool showAll = k.empty();
b8c3d630 395
f7b83689 396 wxList::compatibility_iterator node = m_MapList->GetFirst();
69108ccb
JS
397
398 {
b8c3d630
DS
399 // display a busy cursor
400 wxBusyCursor b;
401 wxString compA, compB;
402 wxExtHelpMapEntry *entry;
69108ccb 403
b8c3d630
DS
404 // we compare case insensitive
405 if (! showAll)
406 {
407 compA = k;
408 compA.LowerCase();
409 }
69108ccb 410
b8c3d630
DS
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:
c9f78968
VS
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
b8c3d630
DS
467 if (idx >= 0)
468 rc = DisplayHelp(urls[idx]);
469 break;
470 }
471
472 delete [] urls;
473 delete [] choices;
474
475 return rc;
69108ccb
JS
476}
477
478
479bool wxExtHelpController::Quit()
480{
ca65c044 481 return true;
69108ccb
JS
482}
483
484void wxExtHelpController::OnQuit()
485{
486}
487
31528cd3 488#endif // wxUSE_HELP