diff --git a/src/qtgui/guiutils.cpp b/src/qtgui/guiutils.cpp index 79be8a98..88764e68 100644 --- a/src/qtgui/guiutils.cpp +++ b/src/qtgui/guiutils.cpp @@ -1,5 +1,5 @@ #ifndef lint -static char rcsid[] = "@(#$Id: guiutils.cpp,v 1.21 2006-09-23 07:39:55 dockes Exp $ (C) 2005 Jean-Francois Dockes"; +static char rcsid[] = "@(#$Id: guiutils.cpp,v 1.22 2006-10-30 12:59:44 dockes Exp $ (C) 2005 Jean-Francois Dockes"; #endif /* * This program is free software; you can redistribute it and/or modify @@ -173,6 +173,8 @@ void rwSettings(bool writing) Num, 100); SETTING_RW(prefs.sortSpec, "/Recoll/prefs/query/sortSpec", Num, 0); + SETTING_RW(prefs.termMatchType, "/Recoll/prefs/query/termMatchType", + Num, 0); // Ssearch combobox history list if (writing) { diff --git a/src/qtgui/guiutils.h b/src/qtgui/guiutils.h index 5eb3b51d..bf996037 100644 --- a/src/qtgui/guiutils.h +++ b/src/qtgui/guiutils.h @@ -17,7 +17,7 @@ #ifndef _GUIUTILS_H_INCLUDED_ #define _GUIUTILS_H_INCLUDED_ /* - * @(#$Id: guiutils.h,v 1.13 2006-09-23 07:39:55 dockes Exp $ (C) 2005 Jean-Francois Dockes + * @(#$Id: guiutils.h,v 1.14 2006-10-30 12:59:44 dockes Exp $ (C) 2005 Jean-Francois Dockes * jean-francois.dockes@wanadoo.fr * * This program is free software; you can redistribute it and/or modify @@ -91,6 +91,9 @@ class PrefsPack { int sortWidth; int sortSpec; + // Remembered term match mode + int termMatchType; + PrefsPack() : showicons(true), respagesize(8), @@ -99,7 +102,8 @@ class PrefsPack { queryBuildAbstract(true), queryReplaceAbstract(false), startWithAdvSearchOpen(false), - startWithSortToolOpen(false) + startWithSortToolOpen(false), + termMatchType(0) { } }; diff --git a/src/qtgui/rclmain_w.cpp b/src/qtgui/rclmain_w.cpp index cc96677d..065b3ed5 100644 --- a/src/qtgui/rclmain_w.cpp +++ b/src/qtgui/rclmain_w.cpp @@ -1,5 +1,5 @@ #ifndef lint -static char rcsid[] = "@(#$Id: rclmain_w.cpp,v 1.3 2006-10-15 13:07:45 dockes Exp $ (C) 2005 J.F.Dockes"; +static char rcsid[] = "@(#$Id: rclmain_w.cpp,v 1.4 2006-10-30 12:59:44 dockes Exp $ (C) 2005 J.F.Dockes"; #endif /* * This program is free software; you can redistribute it and/or modify @@ -83,6 +83,7 @@ void RclMain::init() asearchform = 0; sortform = 0; uiprefs = 0; + spellform = 0; m_searchId = 0; // Set the focus to the search terms entry: sSearch->queryText->setFocus(); diff --git a/src/qtgui/spell.ui b/src/qtgui/spell.ui index 823e8520..8c2bdeb4 100644 --- a/src/qtgui/spell.ui +++ b/src/qtgui/spell.ui @@ -8,8 +8,8 @@ 0 0 - 0 - 0 + 796 + 351 @@ -27,7 +27,7 @@ - Spelling Explorer + Term Explorer @@ -35,7 +35,7 @@ - layout3 + layout5 @@ -45,97 +45,78 @@ layout4 - + unnamed - + - layout3 + Label1 + + + NoFrame + + + Plain + + + Enter word to expand + + + baseWordLE - - - unnamed - - - - Label1 - - - NoFrame - - - Plain - - - Enter word - - - baseWordLE - - - - - baseWordLE - - - - 200 - 0 - - - - LineEditPanel - - - Sunken - - - - - expandPB - - - false - - - &Expand - - - Alt+E - - - - - clearPB - - - false - - - &Clear - - - Alt+C - - - - - dismissPB - - - true - - - &Close - - - Alt+C - - - - + + + baseWordLE + + + + 200 + 0 + + + + LineEditPanel + + + Sunken + + + + + expTypeCMB + + + + + expandPB + + + false + + + &Expand + + + Alt+E + + + + + dismissPB + + + true + + + &Close + + + Alt+C + + + diff --git a/src/qtgui/spell_w.cpp b/src/qtgui/spell_w.cpp index 10c45c16..2749e2a2 100644 --- a/src/qtgui/spell_w.cpp +++ b/src/qtgui/spell_w.cpp @@ -1,5 +1,5 @@ #ifndef lint -static char rcsid[] = "@(#$Id: spell_w.cpp,v 1.2 2006-10-15 13:07:45 dockes Exp $ (C) 2005 J.F.Dockes"; +static char rcsid[] = "@(#$Id: spell_w.cpp,v 1.3 2006-10-30 12:59:44 dockes Exp $ (C) 2005 J.F.Dockes"; #endif /* * This program is free software; you can redistribute it and/or modify @@ -30,10 +30,12 @@ static char rcsid[] = "@(#$Id: spell_w.cpp,v 1.2 2006-10-15 13:07:45 dockes Exp #include #include #include +#include #include "debuglog.h" #include "recoll.h" #include "spell_w.h" +#include "guiutils.h" #ifdef RCL_USE_ASPELL #include "rclaspell.h" @@ -41,62 +43,95 @@ static char rcsid[] = "@(#$Id: spell_w.cpp,v 1.2 2006-10-15 13:07:45 dockes Exp void SpellW::init() { + expTypeCMB->insertItem(tr("Wildcards")); + expTypeCMB->insertItem(tr("Regexp")); + int maxtyp = 1; +#ifdef RCL_USE_ASPELL + expTypeCMB->insertItem(tr("Spelling/Phonetic")); + maxtyp = 2; +#endif + int typ = prefs.termMatchType; + if (typ < 0 || typ > maxtyp) + typ = 0; + expTypeCMB->setCurrentItem(typ); + // signals and slots connections connect(baseWordLE, SIGNAL(textChanged(const QString&)), this, SLOT(wordChanged(const QString&))); connect(baseWordLE, SIGNAL(returnPressed()), this, SLOT(doExpand())); connect(expandPB, SIGNAL(clicked()), this, SLOT(doExpand())); - connect(clearPB, SIGNAL(clicked()), baseWordLE, SLOT(clear())); connect(dismissPB, SIGNAL(clicked()), this, SLOT(close())); connect(suggsTE, SIGNAL(doubleClicked(int, int)), this, SLOT(textDoubleClicked(int, int))); } +/* Expand term according to current mode */ void SpellW::doExpand() { -#ifdef RCL_USE_ASPELL + if (baseWordLE->text().isEmpty()) + return; + string reason; - if (!aspell || !maybeOpenDb(reason)) { - LOGDEB(("SpellW::doExpand: error aspell %p db: %s\n", aspell, - reason.c_str())); + if (!maybeOpenDb(reason)) { + LOGDEB(("SpellW::doExpand: db error: %s\n", reason.c_str())); return; } - if (!baseWordLE->text().isEmpty()) { - list suggs; - string word = string((const char *)baseWordLE->text().utf8()); - if (!aspell->suggest(*rcldb, word, suggs, reason)) { + + string expr = string((const char *)baseWordLE->text().utf8()); + list suggs; + prefs.termMatchType = expTypeCMB->currentItem(); + Rcl::Db::MatchType mt = Rcl::Db::ET_WILD; + switch (expTypeCMB->currentItem()) { + case 1: mt = Rcl::Db::ET_REGEXP; + /* FALLTHROUGH */ + case 0: + if (!rcldb->termMatch(mt, expr, suggs, prefs.queryStemLang.ascii(), + 200)) { + LOGERR(("SpellW::doExpand:rcldb::termMatch failed\n")); + return; + } + break; +#ifdef RCL_USE_ASPELL + case 2: { + if (!aspell) { + LOGDEB(("SpellW::doExpand: aspell init error\n")); + return; + } + if (!aspell->suggest(*rcldb, expr, suggs, reason)) { LOGERR(("SpellW::doExpand:suggest failed: %s\n", reason.c_str())); return; } - suggsTE->clear(); - if (suggs.empty()) { - suggsTE->append(tr("No spelling expansion found")); - } else { - for (list::iterator it = suggs.begin(); - it != suggs.end(); it++) { - suggsTE->append(QString::fromUtf8(it->c_str())); - } - suggsTE->setCursorPosition(0,0); - suggsTE->ensureCursorVisible(); - } + return; } #endif + } + + suggsTE->clear(); + if (suggs.empty()) { + suggsTE->append(tr("No spelling expansion found")); + } else { + for (list::iterator it = suggs.begin(); + it != suggs.end(); it++) { + suggsTE->append(QString::fromUtf8(it->c_str())); + } + suggsTE->setCursorPosition(0,0); + suggsTE->ensureCursorVisible(); + } } void SpellW::wordChanged(const QString &text) { if (text.isEmpty()) { expandPB->setEnabled(false); - clearPB->setEnabled(false); suggsTE->clear(); } else { expandPB->setEnabled(true); - clearPB->setEnabled(true); } } -void SpellW::textDoubleClicked(int, int) +void SpellW::textDoubleClicked(int para, int) { + suggsTE->setSelection(para, 0, para+1, 0); if (suggsTE->hasSelectedText()) emit(wordSelect(suggsTE->selectedText())); } diff --git a/src/qtgui/ssearch_w.cpp b/src/qtgui/ssearch_w.cpp index 29ba6ff5..9f5b43b2 100644 --- a/src/qtgui/ssearch_w.cpp +++ b/src/qtgui/ssearch_w.cpp @@ -1,5 +1,5 @@ #ifndef lint -static char rcsid[] = "@(#$Id: ssearch_w.cpp,v 1.8 2006-10-24 11:42:13 dockes Exp $ (C) 2006 J.F.Dockes"; +static char rcsid[] = "@(#$Id: ssearch_w.cpp,v 1.9 2006-10-30 12:59:44 dockes Exp $ (C) 2006 J.F.Dockes"; #endif /* * This program is free software; you can redistribute it and/or modify @@ -158,13 +158,16 @@ void SSearch::completion() QApplication::beep(); return; } - string s = txt.substr(cs); + string s = txt.substr(cs) + "*"; LOGDEB(("Completing: [%s]\n", s.c_str())); // Query database const int max = 100; - list strs = rcldb->completions(s, prefs.queryStemLang.ascii(),max); - if (strs.size() == 0) { + list strs; + + if (!rcldb->termMatch(Rcl::Db::ET_WILD, s, strs, + prefs.queryStemLang.ascii(),max) + || strs.size() == 0) { QApplication::beep(); return; } diff --git a/src/rcldb/rcldb.cpp b/src/rcldb/rcldb.cpp index 5bd7dcea..79dd7378 100644 --- a/src/rcldb/rcldb.cpp +++ b/src/rcldb/rcldb.cpp @@ -1,5 +1,5 @@ #ifndef lint -static char rcsid[] = "@(#$Id: rcldb.cpp,v 1.84 2006-10-25 10:52:02 dockes Exp $ (C) 2004 J.F.Dockes"; +static char rcsid[] = "@(#$Id: rcldb.cpp,v 1.85 2006-10-30 12:59:44 dockes Exp $ (C) 2004 J.F.Dockes"; #endif /* * This program is free software; you can redistribute it and/or modify @@ -21,6 +21,7 @@ static char rcsid[] = "@(#$Id: rcldb.cpp,v 1.84 2006-10-25 10:52:02 dockes Exp $ #include #include #include +#include #include #include @@ -1173,20 +1174,63 @@ bool Db::setQuery(AdvSearchData &sdata, int opts, const string& stemlang) return true; } -list Db::completions(const string &root, const string &lang, int max) +// Characters that can begin a wildcard or regexp expression. We use skipto +// to begin the allterms search with terms that begin with the portion of +// the input string prior to these chars. +const string wildSpecChars = "*?["; +const string regSpecChars = "(.[{^"; + +// Find all index terms that match a wildcard or regular expression +bool Db::termMatch(MatchType typ, const string &root, list& res, + const string &lang, int max) { - Xapian::Database db; - list res; if (!m_ndb || !m_ndb->m_isopen) - return res; + return false; + Xapian::Database db = m_ndb->m_iswritable ? m_ndb->wdb: m_ndb->db; + res.clear(); + // Get rid of capitals and accents string droot; dumb_string(root, droot); - db = m_ndb->m_iswritable ? m_ndb->wdb: m_ndb->db; + string nochars = typ == ET_WILD ? wildSpecChars : regSpecChars; + + regex_t reg; + int errcode; + if (typ == ET_REGEXP && (errcode=regcomp(®, droot.c_str(), 0))) { + char errbuf[200]; + regerror(errcode, ®, errbuf, 199); + LOGERR(("termMatch: regcomp failed: %s\n", errbuf)); + res.push_back(errbuf); + regfree(®); + return false; + } + + // Find the initial section before any special char + string::size_type es = droot.find_first_of(nochars); + string is; + switch (es) { + case string::npos: is = droot;break; + case 0: break; + default: is = droot.substr(0, es);break; + } + LOGDEB(("termMatch: initsec: [%s]\n", is.c_str())); + Xapian::TermIterator it = db.allterms_begin(); - it.skip_to(droot.c_str()); + if (!is.empty()) + it.skip_to(is.c_str()); for (int n = 0;it != db.allterms_end(); it++) { - if ((*it).find(droot) != 0) + // If we're beyond the terms matching the initial string, end + if (!is.empty() && (*it).find(is) != 0) break; + // Don't match special internal terms beginning with uppercase ascii + if ((*it).at(0) >= 'A' && (*it).at(0) <= 'Z') + continue; + if (typ == ET_WILD) { + if (fnmatch(droot.c_str(), (*it).c_str(), 0) == FNM_NOMATCH) + continue; + } else { + if (regexec(®, (*it).c_str(), 0, 0, 0)) + continue; + } if (lang.empty()) { res.push_back(*it); ++n; @@ -1205,7 +1249,10 @@ list Db::completions(const string &root, const string &lang, int max) } res.sort(); res.unique(); - return res; + if (typ == ET_REGEXP) { + regfree(®); + } + return true; } /** Term list walking. */ diff --git a/src/rcldb/rcldb.h b/src/rcldb/rcldb.h index 748c3a68..7e90eb5c 100644 --- a/src/rcldb/rcldb.h +++ b/src/rcldb/rcldb.h @@ -16,7 +16,7 @@ */ #ifndef _DB_H_INCLUDED_ #define _DB_H_INCLUDED_ -/* @(#$Id: rcldb.h,v 1.39 2006-10-24 09:28:31 dockes Exp $ (C) 2004 J.F.Dockes */ +/* @(#$Id: rcldb.h,v 1.40 2006-10-30 12:59:44 dockes Exp $ (C) 2004 J.F.Dockes */ #include #include @@ -160,9 +160,12 @@ class Db { bool getQueryTerms(list& terms); bool getMatchTerms(const Doc& doc, list& terms); - // Return a list of database terms that begin with the input string - // Stem expansion is performed if lang is not empty - list completions(const string &s, const string &lang, int max=20); + /** Return a list of index terms that match the input string + * Expansion is performed either with either wildcard or regexp processing + * Stem expansion is performed if lang is not empty */ + enum MatchType {ET_WILD, ET_REGEXP}; + bool termMatch(MatchType typ, const string &s, list& result, + const string &lang, int max=20); /** Add extra database for querying */ bool addQueryDb(const string &dir);