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: helpext.cpp 38857 2006-04-20 07:31:44Z ABX $ 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__) && (!defined(__WXMAC__) || defined(__WXMAC_OSX__)) 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 _T("wxhelp.map") 57 58/// Character introducing comments/documentation field in map file. 59#define WXEXTHELP_COMMENTCHAR ';' 60 61#define CONTENTS_ID 0 62 63IMPLEMENT_CLASS(wxExtHelpController, wxHelpControllerBase) 64 65/// Name of environment variable to set help browser. 66#define WXEXTHELP_ENVVAR_BROWSER wxT("WX_HELPBROWSER") 67/// Is browser a netscape browser? 68#define WXEXTHELP_ENVVAR_BROWSERISNETSCAPE wxT("WX_HELPBROWSER_NS") 69 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 76wxExtHelpController::wxExtHelpController(wxWindow* parentWindow) 77 : wxHelpControllerBase(parentWindow) 78{ 79 m_MapList = NULL; 80 m_NumOfEntries = 0; 81 m_BrowserIsNetscape = false; 82 83 wxChar *browser = wxGetenv(WXEXTHELP_ENVVAR_BROWSER); 84 if (browser) 85 { 86 m_BrowserName = browser; 87 browser = wxGetenv(WXEXTHELP_ENVVAR_BROWSERISNETSCAPE); 88 m_BrowserIsNetscape = browser && (wxAtoi(browser) != 0); 89 } 90} 91 92wxExtHelpController::~wxExtHelpController() 93{ 94 DeleteList(); 95} 96 97void wxExtHelpController::SetBrowser(const wxString& browsername, bool isNetscape) 98{ 99 m_BrowserName = browsername; 100 m_BrowserIsNetscape = isNetscape; 101} 102 103// Set viewer: new, generic name for SetBrowser 104void wxExtHelpController::SetViewer(const wxString& viewer, long flags) 105{ 106 SetBrowser(viewer, (flags & wxHELP_NETSCAPE) != 0); 107} 108 109bool wxExtHelpController::DisplayHelp(const wxString &relativeURL) 110{ 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 ) 128 return true; 129 } 130 //else: either no browser explicitly specified or we failed to open it 131 132 // just use default browser 133 return wxLaunchDefaultBrowser(url); 134} 135 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{ 148 if (m_MapList) 149 { 150 wxList::compatibility_iterator node = m_MapList->GetFirst(); 151 while (node) 152 { 153 delete (wxExtHelpMapEntry *)node->GetData(); 154 m_MapList->Erase(node); 155 node = m_MapList->GetFirst(); 156 } 157 158 delete m_MapList; 159 m_MapList = (wxList*) NULL; 160 } 161} 162 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) 167{ 168 return LoadFile(file); 169} 170 171bool wxExtHelpController::ParseMapFileLine(const wxString& line) 172{ 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} 218 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(); 224 225 bool dirExists = false; 226 227#if wxUSE_INTL 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 243 if ( ! dirExists ) 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 272 if ( ! dirExists && !helpDir.DirExists() ) 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); 280 if ( ! mapFile.FileExists() ) 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 314 m_helpDir = helpDir.GetFullPath(); // now it's valid 315 return true; 316} 317 318 319bool wxExtHelpController::DisplayContents() 320{ 321 if (! m_NumOfEntries) 322 return false; 323 324 wxString contents; 325 wxList::compatibility_iterator node = m_MapList->GetFirst(); 326 wxExtHelpMapEntry *entry; 327 while (node) 328 { 329 entry = (wxExtHelpMapEntry *)node->GetData(); 330 if (entry->id == CONTENTS_ID) 331 { 332 contents = entry->url; 333 break; 334 } 335 336 node = node->GetNext(); 337 } 338 339 bool rc = false; 340 wxString file; 341 file << m_helpDir << wxFILE_SEP_PATH << contents; 342 if (file.Contains(wxT('#'))) 343 file = file.BeforeLast(wxT('#')); 344 if (contents.length() && wxFileExists(file)) 345 rc = DisplaySection(CONTENTS_ID); 346 347 // if not found, open homemade toc: 348 return rc ? true : KeywordSearch(wxEmptyString); 349} 350 351bool wxExtHelpController::DisplaySection(int sectionNo) 352{ 353 if (! m_NumOfEntries) 354 return false; 355 356 wxBusyCursor b; // display a busy cursor 357 wxList::compatibility_iterator node = m_MapList->GetFirst(); 358 wxExtHelpMapEntry *entry; 359 while (node) 360 { 361 entry = (wxExtHelpMapEntry *)node->GetData(); 362 if (entry->id == sectionNo) 363 return DisplayHelp(entry->url); 364 node = node->GetNext(); 365 } 366 367 return false; 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 380bool wxExtHelpController::DisplayBlock(long blockNo) 381{ 382 return DisplaySection((int)blockNo); 383} 384 385bool wxExtHelpController::KeywordSearch(const wxString& k, 386 wxHelpSearchMode WXUNUSED(mode)) 387{ 388 if (! m_NumOfEntries) 389 return false; 390 391 wxString *choices = new wxString[m_NumOfEntries]; 392 wxString *urls = new wxString[m_NumOfEntries]; 393 394 int idx = 0; 395 bool rc = false; 396 bool showAll = k.empty(); 397 398 wxList::compatibility_iterator node = m_MapList->GetFirst(); 399 400 { 401 // display a busy cursor 402 wxBusyCursor b; 403 wxString compA, compB; 404 wxExtHelpMapEntry *entry; 405 406 // we compare case insensitive 407 if (! showAll) 408 { 409 compA = k; 410 compA.LowerCase(); 411 } 412 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; 473} 474 475 476bool wxExtHelpController::Quit() 477{ 478 return true; 479} 480 481void wxExtHelpController::OnQuit() 482{ 483} 484 485#endif // wxUSE_HELP 486