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