/********* * * This file is part of BibleTime's source code, http://www.bibletime.info/. * * Copyright 1999-2006 by the BibleTime developers. * The BibleTime source code is licensed under the GNU General Public License version 2.0. * **********/ #include "csearchdialogpages.h" #include "csearchdialog.h" #include "csearchanalysis.h" #include "crangechooser.h" #include "cmoduleresultview.h" #include "csearchresultview.h" #include "cmodulechooser.h" #include "backend/cswordversekey.h" #include "backend/cdisplayrendering.h" #include "frontend/display/cdisplay.h" #include "frontend/display/creaddisplay.h" #include "util/cresmgr.h" #include "util/ctoolclass.h" //TQt includes #include #include #include #include #include //TDE includes #include #include #include #include #include #include //Lucence includes //#include "CLucene.h" //#include "CLucene/util/Reader.h" //#include "CLucene/util/Misc.h" //#include "CLucene/util/dirent.h" namespace Search { namespace Result { /******************************************** ************ StrongsResulClass ************* ********************************************/ void StrongsResultClass::initStrongsResults(void) { using namespace Rendering; CDisplayRendering render; ListCSwordModuleInfo modules; CTextRendering::KeyTreeItem::Settings settings; TQString rText, lText, key; bool found; int sIndex; int count; int index; TQString text; modules.append(srModule); sword::ListKey& result = srModule->searchResult(); count = result.Count(); if (!count) return; TDEApplication::kApplication()->processEvents( 1 ); //1 ms only srList.clear(); // for whatever reason the text "Parsing...translations." does not appear. // this is not critical but the text is necessary to get the dialog box // to be wide enough. KProgressDialog* progress = new KProgressDialog(0, "progressDialog", i18n("Parsing Stong's Numbers"), i18n("Parsing Stong's numbers for translations."), true); progress->setAllowCancel(false); progress->setMinimumDuration(0); progress->show(); progress->raise(); for (index = 0; index < count; index++){ progress->progressBar()->setProgress( int( (index*100) / count ) ); TDEApplication::kApplication()->processEvents( 1 ); //1 ms only key = TQString::fromUtf8(result.GetElement(index)->getText()); text = render.renderSingleKey(key, modules, settings); sIndex = 0; while ((rText = getStrongsNumberText(text, &sIndex)) != "") { StrongsResultList::iterator it; found = FALSE; for ( it = srList.begin(); it != srList.end(); ++it ) { lText = (*it).keyText(); if (lText == rText) { found = TRUE; (*it).addKeyName(key); break; } } if (found == FALSE) srList.append( StrongsResult(rText, key) ); } } delete progress; progress = 0; //qHeapSort(srList); } TQString StrongsResultClass::getStrongsNumberText(const TQString& verseContent, int *startIndex) { // get the strongs text int idx1, idx2, index; TQString sNumber, strongsText; //const bool cs = CSwordModuleSearch::caseSensitive; const bool cs = false; if (*startIndex == 0) { index = verseContent.find("" is found. Get the text from here to // the next "<". index = verseContent.find(">", index, cs) + 1; idx2 = verseContent.find("<", index, cs); strongsText = verseContent.mid(index, idx2 - index); index = idx2; *startIndex = index; return(strongsText); } else { index += 6; // 6 is the length of "lemma=" } } return TQString(); } /******************************************** ********** CSearchDialogResultPage ********* ********************************************/ CSearchResultPage::CSearchResultPage(TQWidget *parent, const char *name ) : SearchResultsForm(parent, name) { initView(); initConnections(); } CSearchResultPage::~CSearchResultPage() {} /** Initializes the view of this widget. */ void CSearchResultPage::initView() { TQVBoxLayout* frameLayout = new TQVBoxLayout(m_displayFrame, 0, 6, "frameLayout"); m_previewDisplay = CDisplay::createReadInstance(0, m_displayFrame); frameLayout->addWidget(m_previewDisplay->view()); } /** Sets the modules which contain the result of each. */ void CSearchResultPage::setSearchResult(ListCSwordModuleInfo modules) { const TQString searchedText = CSearchDialog::getSearchDialog()->searchText(); reset(); //clear current modules m_modules = modules; m_moduleListBox->setupTree(modules, searchedText); //have a Bible or commentary in the modules? bool enable = false; // for (modules.first(); !enable && modules.current(); modules.next()) { ListCSwordModuleInfo::iterator end_it = modules.end(); for (ListCSwordModuleInfo::iterator it(modules.begin()); it != end_it; ++it) { if ((*it)->type() == CSwordModuleInfo::Bible ) { enable = true; break; }; }; m_analyseButton->setEnabled(enable); } /** Resets the current list of modules and the displayed list of found entries. */ void CSearchResultPage::reset() { m_moduleListBox->clear(); m_resultListBox->clear(); m_previewDisplay->setText(TQString()); m_analyseButton->setEnabled(false); // m_modules.setAutoDelete(false); //make sure we don't delete modules accidentally m_modules.clear(); } /** Update the preview of the selected key. */ void CSearchResultPage::updatePreview(const TQString& key) { using namespace Rendering; CSwordModuleInfo* module = m_moduleListBox->activeModule(); if ( module ) { const TQString searchedText = CSearchDialog::getSearchDialog()->searchText(); //const int searchFlags = CSearchDialog::getSearchDialog()->searchFlags(); TQString text; CDisplayRendering render; ListCSwordModuleInfo modules; modules.append(module); CTextRendering::KeyTreeItem::Settings settings; //for bibles render 5 context verses if (module->type() == CSwordModuleInfo::Bible) { CSwordVerseKey vk(module); vk.Headings(1); vk.key(key); ((VerseKey*)(module->module()->getKey()))->Headings(1); //HACK: enable headings for VerseKeys //first go back and then go forward the keys to be in context vk.previous(CSwordVerseKey::UseVerse); vk.previous(CSwordVerseKey::UseVerse); //include Headings in display, they are indexed and searched too if (vk.Verse() == 1){ if (vk.Chapter() == 1){ vk.Chapter(0); } vk.Verse(0); } const TQString startKey = vk.key(); vk.key(key); vk.next(CSwordVerseKey::UseVerse); vk.next(CSwordVerseKey::UseVerse); const TQString endKey = vk.key(); settings.keyRenderingFace = CTextRendering::KeyTreeItem::Settings::CompleteShort; text = render.renderKeyRange(startKey, endKey, modules, key, settings); } //for commentaries only one verse, but with heading else if (module->type() == CSwordModuleInfo::Commentary) { CSwordVerseKey vk(module); vk.Headings(1); vk.key(key); ((VerseKey*)(module->module()->getKey()))->Headings(1); //HACK: enable headings for VerseKeys //include Headings in display, they are indexed and searched too if (vk.Verse() == 1){ if (vk.Chapter() == 1){ vk.Chapter(0); } vk.Verse(0); } const TQString startKey = vk.key(); vk.key(key); const TQString endKey = vk.key(); settings.keyRenderingFace = CTextRendering::KeyTreeItem::Settings::NoKey; text = render.renderKeyRange(startKey, endKey, modules, key, settings); } else { text = render.renderSingleKey(key, modules, settings); } m_previewDisplay->setText( highlightSearchedText(text, searchedText/*, searchFlags*/) ); m_previewDisplay->moveToAnchor( CDisplayRendering::keyToHTMLAnchor(key) ); } } TQStringList CSearchResultPage::QueryParser(const TQString& queryString) { TQString token; TQStringList tokenList; int cnt, pos; token = ""; cnt = 0; while(cnt < queryString.length()) { // add to token if ((queryString[cnt]).isLetterOrNumber() || (queryString[cnt] == '*')) { token = token + queryString[cnt]; cnt++; } // token break else if (queryString[cnt] == ' ') { token = token.stripWhiteSpace(); if ((token != "*") && (token != "")) tokenList.append(token); token = ""; cnt++; } // clucene appears to ignore quoted strings in the sence // that it treats all the words within quoted strings as // regular tokens and not as a single token. else if (queryString[cnt] == '"') { cnt++; } // wild card - treat as a special token break //else if (queryString[cnt] == '*') { // token = token + queryString[cnt]; // token = token.stripWhiteSpace(); // if ((token != "*") && (token != "")) // tokenList.append(token); // // start next token with wildcard (kin*m -> kin* *m) // token = "*"; // cnt++; //} // the ! token is also a token break else if (queryString[cnt] == '!') { // store away current token token = token.stripWhiteSpace(); if ((token != "*") && (token != "")) tokenList.append(token); // add the ! token tokenList.append("!"); token = ""; cnt++; } // the - token is also a token break else if (queryString[cnt] == '-') { // store away current token token = token.stripWhiteSpace(); if ((token != "*") && (token != "")) tokenList.append(token); // add the ! token tokenList.append("-"); token = ""; cnt++; } // the + token is also a token break else if (queryString[cnt] == '+') { // store away current token token = token.stripWhiteSpace(); if ((token != "*") && (token != "")) tokenList.append(token); // add the + token tokenList.append("+"); token = ""; cnt++; } // the || token is also a token break else if ((queryString[cnt] == '|') && (queryString[cnt+1] == '|')) { // store away current token token = token.stripWhiteSpace(); if ((token != "*") && (token != "")) tokenList.append(token); // add the || token tokenList.append("||"); token = ""; cnt += 2; } // the && token is also a token break else if ((queryString[cnt] == '&') && (queryString[cnt+1] == '&')) { // store away current token token = token.stripWhiteSpace(); if ((token != "*") && (token != "")) tokenList.append(token); // add the || token tokenList.append("&&"); token = ""; cnt += 2; } else cnt++; } token = token.stripWhiteSpace(); if ((token != "*") && (token != "")) tokenList.append(token); cnt = 0; TQStringList::iterator it; for ( it = tokenList.begin(); it != tokenList.end(); it++ ) { //----------------------------------------------------------- // remove all the NOT(!) tokens - these do not need to be // highlighted in the highlighter //----------------------------------------------------------- if (((*it) == "!") || ((*it) == "NOT") || ((*it) == "-")) { it = tokenList.remove(it); if (it == tokenList.end()) break; it = tokenList.remove(it); if (it == tokenList.end()) break; it--; } //----------------------------------------------------------- // remove all the operator tokens - these do not need to be // highlighted in the highlighter //----------------------------------------------------------- else if ( ((*it) == "||") || ((*it) == "OR") || ((*it) == "+") || ((*it) == "AND") || ((*it) == "&&") ) { it = tokenList.remove(it); if (it == tokenList.end()) break; it--; } // if the token contains a ^ then trim the remainder of the // token from the ^ else if ( (pos = (*it).contains("^")) >= 0 ) { (*it) = (*it).left(pos - 1); } // if the token contains a ~ then trim the remainder of the // token from the ~ else if ( (pos = (*it).contains("~")) >= 0 ) { (*it) = (*it).left(pos - 2) + "*"; } } return(tokenList); } const TQString CSearchResultPage::highlightSearchedText(const TQString& content, const TQString& searchedText/*, const int searchFlags*/) { TQString ret = content; //const bool cs = (searchFlags & CSwordModuleSearch::caseSensitive); const bool cs = false; // int index = 0; int index = ret.find(""); const TQString rep2(""); const unsigned int repLength = rep1.length() + rep1.length(); int sstIndex; // strong search text index for finding "strong:" bool inQuote; TQString newSearchText; newSearchText = searchedText; //--------------------------------------------------------------------- // find the strongs search lemma and highlight it //--------------------------------------------------------------------- // search the searched text for "strong:" until it is not found anymore sstIndex = 0; while ((sstIndex = newSearchText.find("strong:", sstIndex)) != -1) { int idx1, idx2, sTokenIndex, sTokenIndex2; TQString sNumber, lemmaText; const TQString rep3("style=\"background-color:#FFFF66;\" "); const unsigned int rep3Length = rep3.length(); int strongIndex = index; //-------------------------------------------------- // get the strongs number from the search text //-------------------------------------------------- // first find the first space after "strong:" // this should indicate a change in search token sstIndex = sstIndex + 7; sTokenIndex = newSearchText.find(" ", sstIndex); sTokenIndex2 = newSearchText.find("|", sstIndex); if ((sTokenIndex2 != -1) && (sTokenIndex2 < sTokenIndex)) { sNumber = newSearchText.mid(sstIndex, sTokenIndex2 - sstIndex); } else { sNumber = newSearchText.mid(sstIndex, sTokenIndex - sstIndex); } // remove this strong entry sstIndex -= 7; newSearchText.replace(sstIndex, sTokenIndex - sstIndex, ""); // find all the "lemma=" inside the the content while((strongIndex = ret.find("lemma=", strongIndex, cs)) != -1) { // get the strongs number after the lemma and compare it with the // strongs number we are looking for idx1 = ret.find("\"", strongIndex) + 1; idx2 = ret.find("\"", idx1 + 1); lemmaText = ret.mid(idx1, idx2 - idx1); if (lemmaText == sNumber) { // strongs number is found now we need to highlight it // I believe the easiest way is to insert rep3 just before "lemma=" ret = ret.insert(strongIndex, rep3); strongIndex += rep3Length; } strongIndex += 6; // 6 is the length of "lemma=" } } //--------------------------------------------------------------------- // now that the strong: stuff is out of the way continue with // other search options //--------------------------------------------------------------------- //----------------------------------------------------------- // try to figure out how to use the lucene query parser //----------------------------------------------------------- //using namespace lucene::queryParser; //using namespace lucene::search; //using namespace lucene::analysis; //using namespace lucene::util; //wchar_t *buf; //char buf8[1000]; //standard::WhitespaceAnalyzer analyzer; //lucene_utf8towcs(m_wcharBuffer, searchedText.utf8(), MAX_CONV_SIZE); //util::scoped_ptr q( QueryParser::parse(m_wcharBuffer, _T("content"), &analyzer) ); //StringReader reader(m_wcharBuffer); //TokenStream* tokenStream = analyzer.tokenStream( _T("field"), &reader); //Token token; //while(tokenStream->next(&token) != 0) { // lucene_wcstoutf8(buf8, token.termText(), 1000); // printf("%s\n", buf8); //} //=========================================================== // since I could not figure out the lucene query parser, I // made a simple parser. //=========================================================== TQStringList words = QueryParser(newSearchText); for ( int wi = 0; (unsigned int)wi < words.count(); ++wi ) { //search for every word in the list TQRegExp findExp; TQString word = words[ wi ]; if (word.contains("*")) { length = word.length() - 1; word.replace('*', "\\S*"); //match within a word findExp = TQRegExp(word); findExp.setMinimal(TRUE); } else { length = word.length(); findExp = TQRegExp("\\b" + word + "\\b"); } // index = 0; //for every word start at the beginning index = ret.find("connectionsProxy(), TQ_SLOT(clear())); connect(m_analyseButton, TQ_SIGNAL(clicked()), TQ_SLOT(showAnalysis())); // connect the strongs list connect(m_moduleListBox, TQ_SIGNAL(strongsSelected(CSwordModuleInfo*, TQStringList*)), m_resultListBox, TQ_SLOT(setupStrongsTree(CSwordModuleInfo*, TQStringList*))); } /** Shows a dialog with the search analysis of the current search. */ void CSearchResultPage::showAnalysis() { Analysis::CSearchAnalysisDialog dlg(m_modules, this); dlg.exec(); } } //end of namespace Search.Result /*************************/ namespace Options { CSearchOptionsPage::CSearchOptionsPage(TQWidget *parent, const char *name ) : SearchOptionsForm(parent,name) { initView(); readSettings(); } CSearchOptionsPage::~CSearchOptionsPage() { saveSettings(); } /** Returns the search text set in this page. */ const TQString CSearchOptionsPage::searchText() { return m_searchTextCombo->currentText(); } /** Sets the search text used in the page. */ void CSearchOptionsPage::setSearchText(const TQString& text) { bool found = false; int i = 0; for (i = 0; !found && i < m_searchTextCombo->count(); ++i) { if (m_searchTextCombo->text(i) == text) { found = true; } } // This is needed because in the for loop i is incremented before the comparison (++i) // As a result the index i is actually one greater than expected. i--; if (!found) { i = 0; m_searchTextCombo->insertItem( text,0 ); } m_searchTextCombo->setCurrentItem(i); m_searchTextCombo->reset(); m_searchTextCombo->setFocus(); } /** Initializes this page. */ void CSearchOptionsPage::initView() { connect( m_searchTextCombo, TQ_SIGNAL(activated( const TQString& )), m_searchTextCombo, TQ_SLOT( addToHistory( const TQString& )) ); connect( m_searchTextCombo, TQ_SIGNAL(returnPressed ( const TQString& )), m_searchTextCombo, TQ_SLOT(addToHistory(const TQString&)) ); TQToolTip::add(m_searchTextCombo, CResMgr::searchdialog::options::searchedText::tooltip); m_syntaxButton->setIconSet(SmallIconSet("contexthelp")); connect( m_syntaxButton, TQ_SIGNAL(clicked()), this, TQ_SLOT(syntaxHelp())); m_chooseModulesButton->setIconSet(SmallIconSet("wizard")); connect(m_chooseModulesButton, TQ_SIGNAL(clicked()), this, TQ_SLOT(chooseModules())); TQToolTip::add (m_chooseModulesButton, CResMgr::searchdialog::options::moduleChooserButton::tooltip); TQToolTip::add (m_rangeChooserCombo, CResMgr::searchdialog::options::chooseScope::tooltip); refreshRanges(); m_chooseRangeButton->setIconSet(SmallIconSet("configure")); connect(m_chooseRangeButton, TQ_SIGNAL(clicked()), this, TQ_SLOT(setupRanges())); //set the initial focus m_searchTextCombo->setFocus(); } /** Sets the modules used by the search. */ void CSearchOptionsPage::setModules( ListCSwordModuleInfo modules ) { TQString t = i18n("Searching in: "); m_modules.clear(); //remove old modules ListCSwordModuleInfo::iterator end_it = modules.end(); for (ListCSwordModuleInfo::iterator it(modules.begin()); it != end_it; ++it) { //ToDo: Check for containsRef compat if (*it == 0) { //don't operate on null modules. continue; } if ( !m_modules.contains(*it) ) { m_modules.append( *it ); t.append( (*it)->name() ); if (*it != modules.last()) { t += TQString::fromLatin1(", "); // so that it will become a readable list (WLC, LXX, GerLut...) } } }; m_modulesLabel->setText(t); emit( sigSetSearchButtonStatus( (modules.count() != 0) ) ); } /** Opens the modules chooser dialog. */ void CSearchOptionsPage::chooseModules() { CModuleChooserDialog* dlg = new CModuleChooserDialog(this, modules()); connect(dlg, TQ_SIGNAL(modulesChanged(ListCSwordModuleInfo)), this, TQ_SLOT(setModules(ListCSwordModuleInfo))); dlg->exec(); } /** Returns the list of used modules. */ const ListCSwordModuleInfo CSearchOptionsPage::modules() { return m_modules; } /** Sets all options back to the default. */ void CSearchOptionsPage::reset() { m_rangeChooserCombo->setCurrentItem(0); //no scope m_searchTextCombo->clearEdit(); } /** Reads the settings for the searchdialog from disk. */ void CSearchOptionsPage::saveSettings() { CBTConfig::set (CBTConfig::searchCompletionTexts, m_searchTextCombo->completionObject()->items()); CBTConfig::set (CBTConfig::searchTexts, m_searchTextCombo->historyItems()); } /** Reads the settings of the last searchdialog session. */ void CSearchOptionsPage::readSettings() { m_searchTextCombo->completionObject()->setItems( CBTConfig::get (CBTConfig::searchCompletionTexts) ); m_searchTextCombo->setHistoryItems( CBTConfig::get (CBTConfig::searchTexts) ); } void CSearchOptionsPage::aboutToShow() { m_searchTextCombo->setFocus(); } /** No descriptions */ void CSearchOptionsPage::setupRanges() { CRangeChooserDialog* chooser = new CRangeChooserDialog(this); chooser->exec(); refreshRanges(); } /** No descriptions */ void CSearchOptionsPage::syntaxHelp() { TQString syntax = i18n ( "

Enter search terms separated by spaces. By default the search " "function will return results that match any of the search terms (OR). " "To search for all the terms separate the terms by AND.

") + i18n( "

You can use wildcards: '*' matches any sequence of characters, " "while '?' matches any single character. The use of brackets allows you to " "group your search terms, e.g. '(Jesus OR spirit) AND God'.

") + i18n( "

To search text other than the main text, enter the text type followed " "by \":\", and then the search term. For example, to search for the Strong's " "number H8077, use 'strong:H8077'.

") + i18n( "

Available text types:
" "" "" "" "
heading:searches headings
footnote:searches footnotes
strong:searches Strong's Numbers
morph:searches morphology codes

") + i18n( "

BibleTime uses the Lucene search engine to perform your searches. " "It has many advanced features, and you can read more about it here: " "" "http://lucene.apache.org/java/docs/queryparsersyntax.html

"); KMessageBox::about( this, syntax, i18n("Basic Search Syntax Introduction")); } /** refreshes the listof ranges and the range combobox. */ void CSearchOptionsPage::refreshRanges() { //the first two options are fixed, the others can be edited using the "Setup ranges" button. m_rangeChooserCombo->clear(); m_rangeChooserCombo->insertItem(i18n("No search scope")); //m_rangeChooserCombo->insertItem(i18n("Last search result")); //insert the user-defined ranges CBTConfig::StringMap map = CBTConfig::get (CBTConfig::searchScopes); CBTConfig::StringMap::Iterator it; for (it = map.begin(); it != map.end(); ++it) { m_rangeChooserCombo->insertItem(it.key()); }; } /** Returns the selected search scope if a search scope was selected. */ sword::ListKey CSearchOptionsPage::searchScope() { if (m_rangeChooserCombo->currentItem() > 0) { //is not "no scope" CBTConfig::StringMap map = CBTConfig::get(CBTConfig::searchScopes); TQString scope = map[ m_rangeChooserCombo->currentText() ]; if (!scope.isEmpty()) { return sword::VerseKey().ParseVerseList( (const char*)scope.utf8(), "Genesis 1:1", true); } } return sword::ListKey(); } /*! \fn CSearchOptionsPage::hasSearchScope() */ bool CSearchOptionsPage::hasSearchScope() { return (searchScope().Count() > 0); } } //end of namespace Options } //end of namespace Search #include "csearchdialogpages.moc"