From 4170bec663f7268053b4d24138d09b4f243d555d Mon Sep 17 00:00:00 2001 From: Jean-Francois Dockes Date: Sat, 1 Dec 2018 08:46:34 +0100 Subject: [PATCH] GUI simple search: reworked completion and history access - Completion in the simple search line editor, much simplified, using a standard qcompleter - Easier access to index term than the previous awkward ESC-space - Completion with history entries --- src/qtgui/guiutils.cpp | 145 +++++++- src/qtgui/guiutils.h | 2 - src/qtgui/images/clock.png | Bin 0 -> 365 bytes src/qtgui/images/interro.png | Bin 0 -> 397 bytes src/qtgui/rclmain_w.cpp | 9 +- src/qtgui/recoll.h | 4 + src/qtgui/recoll.qrc | 2 + src/qtgui/ssearch_w.cpp | 617 +++++++++++------------------------ src/qtgui/ssearch_w.h | 76 +++-- src/qtgui/ssearchb.ui | 14 +- src/qtgui/uiprefs.ui | 20 -- src/qtgui/uiprefs_w.cpp | 8 - 12 files changed, 389 insertions(+), 508 deletions(-) create mode 100644 src/qtgui/images/clock.png create mode 100644 src/qtgui/images/interro.png diff --git a/src/qtgui/guiutils.cpp b/src/qtgui/guiutils.cpp index d5b0a12b..9590d964 100644 --- a/src/qtgui/guiutils.cpp +++ b/src/qtgui/guiutils.cpp @@ -105,7 +105,7 @@ void rwSettings(bool writing) } } QString ascdflt; - SETTING_RW(advSearchClauses, "/Recoll/prefs/adv/clauseList", String, ascdflt); + SETTING_RW(advSearchClauses,"/Recoll/prefs/adv/clauseList", String, ascdflt); if (!writing) { vector clauses; stringToStrings(qs2utf8s(advSearchClauses), clauses); @@ -126,12 +126,8 @@ void rwSettings(bool writing) } } - SETTING_RW(prefs.ssearchOnWS, "/Recoll/prefs/reslist/autoSearchOnWS", - Bool, false); SETTING_RW(prefs.ssearchNoComplete, "/Recoll/prefs/ssearch/noComplete", Bool, false); - SETTING_RW(prefs.ssearchAsYouType, - "/Recoll/prefs/ssearch/asYouType", Bool, false); SETTING_RW(prefs.filterCtlStyle, "/Recoll/prefs/filterCtlStyle", Int, 0); SETTING_RW(prefs.ssearchAutoPhrase, "/Recoll/prefs/ssearchAutoPhrase", Bool, true); @@ -443,3 +439,142 @@ string PrefsPack::stemlang() return stemLang; } +#ifdef SHOWEVENTS +const char *eventTypeToStr(int tp) +{ + switch (tp) { + case 0: return "None"; + case 1: return "Timer"; + case 2: return "MouseButtonPress"; + case 3: return "MouseButtonRelease"; + case 4: return "MouseButtonDblClick"; + case 5: return "MouseMove"; + case 6: return "KeyPress"; + case 7: return "KeyRelease"; + case 8: return "FocusIn"; + case 9: return "FocusOut"; + case 10: return "Enter"; + case 11: return "Leave"; + case 12: return "Paint"; + case 13: return "Move"; + case 14: return "Resize"; + case 15: return "Create"; + case 16: return "Destroy"; + case 17: return "Show"; + case 18: return "Hide"; + case 19: return "Close"; + case 20: return "Quit"; + case 21: return "ParentChange"; + case 131: return "ParentAboutToChange"; + case 22: return "ThreadChange"; + case 24: return "WindowActivate"; + case 25: return "WindowDeactivate"; + case 26: return "ShowToParent"; + case 27: return "HideToParent"; + case 31: return "Wheel"; + case 33: return "WindowTitleChange"; + case 34: return "WindowIconChange"; + case 35: return "ApplicationWindowIconChange"; + case 36: return "ApplicationFontChange"; + case 37: return "ApplicationLayoutDirectionChange"; + case 38: return "ApplicationPaletteChange"; + case 39: return "PaletteChange"; + case 40: return "Clipboard"; + case 42: return "Speech"; + case 43: return "MetaCall"; + case 50: return "SockAct"; + case 132: return "WinEventAct"; + case 52: return "DeferredDelete"; + case 60: return "DragEnter"; + case 61: return "DragMove"; + case 62: return "DragLeave"; + case 63: return "Drop"; + case 64: return "DragResponse"; + case 68: return "ChildAdded"; + case 69: return "ChildPolished"; + case 70: return "ChildInserted"; + case 72: return "LayoutHint"; + case 71: return "ChildRemoved"; + case 73: return "ShowWindowRequest"; + case 74: return "PolishRequest"; + case 75: return "Polish"; + case 76: return "LayoutRequest"; + case 77: return "UpdateRequest"; + case 78: return "UpdateLater"; + case 79: return "EmbeddingControl"; + case 80: return "ActivateControl"; + case 81: return "DeactivateControl"; + case 82: return "ContextMenu"; + case 83: return "InputMethod"; + case 86: return "AccessibilityPrepare"; + case 87: return "TabletMove"; + case 88: return "LocaleChange"; + case 89: return "LanguageChange"; + case 90: return "LayoutDirectionChange"; + case 91: return "Style"; + case 92: return "TabletPress"; + case 93: return "TabletRelease"; + case 94: return "OkRequest"; + case 95: return "HelpRequest"; + case 96: return "IconDrag"; + case 97: return "FontChange"; + case 98: return "EnabledChange"; + case 99: return "ActivationChange"; + case 100: return "StyleChange"; + case 101: return "IconTextChange"; + case 102: return "ModifiedChange"; + case 109: return "MouseTrackingChange"; + case 103: return "WindowBlocked"; + case 104: return "WindowUnblocked"; + case 105: return "WindowStateChange"; + case 110: return "ToolTip"; + case 111: return "WhatsThis"; + case 112: return "StatusTip"; + case 113: return "ActionChanged"; + case 114: return "ActionAdded"; + case 115: return "ActionRemoved"; + case 116: return "FileOpen"; + case 117: return "Shortcut"; + case 51: return "ShortcutOverride"; + case 30: return "Accel"; + case 32: return "AccelAvailable"; + case 118: return "WhatsThisClicked"; + case 120: return "ToolBarChange"; + case 121: return "ApplicationActivated"; + case 122: return "ApplicationDeactivated"; + case 123: return "QueryWhatsThis"; + case 124: return "EnterWhatsThisMode"; + case 125: return "LeaveWhatsThisMode"; + case 126: return "ZOrderChange"; + case 127: return "HoverEnter"; + case 128: return "HoverLeave"; + case 129: return "HoverMove"; + case 119: return "AccessibilityHelp"; + case 130: return "AccessibilityDescription"; + case 150: return "EnterEditFocus"; + case 151: return "LeaveEditFocus"; + case 152: return "AcceptDropsChange"; + case 153: return "MenubarUpdated"; + case 154: return "ZeroTimerEvent"; + case 155: return "GraphicsSceneMouseMove"; + case 156: return "GraphicsSceneMousePress"; + case 157: return "GraphicsSceneMouseRelease"; + case 158: return "GraphicsSceneMouseDoubleClick"; + case 159: return "GraphicsSceneContextMenu"; + case 160: return "GraphicsSceneHoverEnter"; + case 161: return "GraphicsSceneHoverMove"; + case 162: return "GraphicsSceneHoverLeave"; + case 163: return "GraphicsSceneHelp"; + case 164: return "GraphicsSceneDragEnter"; + case 165: return "GraphicsSceneDragMove"; + case 166: return "GraphicsSceneDragLeave"; + case 167: return "GraphicsSceneDrop"; + case 168: return "GraphicsSceneWheel"; + case 169: return "KeyboardLayoutChange"; + case 170: return "DynamicPropertyChange"; + case 171: return "TabletEnterProximity"; + case 172: return "TabletLeaveProximity"; + default: return "UnknownEvent"; + } +} +#endif diff --git a/src/qtgui/guiutils.h b/src/qtgui/guiutils.h index 93b38b5b..78de5529 100644 --- a/src/qtgui/guiutils.h +++ b/src/qtgui/guiutils.h @@ -40,9 +40,7 @@ using std::vector; class PrefsPack { public: // Simple search entry behaviour - bool ssearchOnWS; bool ssearchNoComplete; - bool ssearchAsYouType; // Decide if we display the doc category filter control as a // toolbar+combobox or as a button group under simple search enum FilterCtlStyle {FCS_BT, FCS_CMB, FCS_MN}; diff --git a/src/qtgui/images/clock.png b/src/qtgui/images/clock.png new file mode 100644 index 0000000000000000000000000000000000000000..536582f1b1d7bf8da52b958cf3d8f9391019af73 GIT binary patch literal 365 zcmV-z0h0cSP)42H>4nD4rZQI(m z9mjE5763_-q-k1JRTM>MPH=YU`@U(Kecu5nr2tIR1V9K;*L6`8M+Gx8voVHfGysoR1Qc5|`vyYxMGtcwv*>zoy0UH>l2@>d@s(pocf2*6q^ zg!rEF4?H!FBY+Tqb1q4e*J>)Iyma9$ErfV{!CL!+W9B^1iHKi-BJ5*WDiO6#Z>$1Xs6u^I2=1a{%Hv&a&*O`TmJdChaLL!FFr$00000 LNkvXXu0mjflXj8< literal 0 HcmV?d00001 diff --git a/src/qtgui/images/interro.png b/src/qtgui/images/interro.png new file mode 100644 index 0000000000000000000000000000000000000000..d9f36959b96e0c3c194af1b82f70f8672692dcf5 GIT binary patch literal 397 zcmV;80doF{P) zS)S)z*8yORxvuMZ9yXy2!_YL%?RFbem%$jjTrPx=H3-A7>pDuQ5W=!70BzeQN%DHV z7-K;YEW!SCIu%7hDfNB-a5xY`2qC6va?Z=L?EBs@jN|b*fnA>G0E7^$(u5Ep1b{JN z24m~CZExor+gr`H!>`bF{R1$@0Nn5QZED-LQ=J)b&Hvg-huj^B`Uqjb*%d$AqueryText->setFocus(); + sSearch->takeFocus(); enbSynAction->setDisabled(prefs.synFile.isEmpty()); enbSynAction->setChecked(prefs.synFileEnable); @@ -973,7 +973,7 @@ void RclMain::docExpand(Rcl::Doc doc) } // We need to insert item here, its not auto-done like when the user types // CR - sSearch->queryText->setEditText(text); + sSearch->setSearchString(text); sSearch->setAnyTermMode(); sSearch->startSimpleSearch(); } @@ -1025,7 +1025,7 @@ void RclMain::eraseSearchHistory() { prefs.ssearchHistory.clear(); if (sSearch) - sSearch->queryText->clear(); + sSearch->clearAll(); if (g_advshistory) g_advshistory->clear(); } @@ -1138,7 +1138,7 @@ void RclMain::toggleFullScreen() void RclMain::showEvent(QShowEvent *ev) { - sSearch->queryText->setFocus(); + sSearch->takeFocus(); QMainWindow::showEvent(ev); } @@ -1146,4 +1146,3 @@ void RclMain::applyStyleSheet() { ::applyStyleSheet(prefs.qssFile); } - diff --git a/src/qtgui/recoll.h b/src/qtgui/recoll.h index b7f03c74..187cbec7 100644 --- a/src/qtgui/recoll.h +++ b/src/qtgui/recoll.h @@ -50,6 +50,10 @@ inline std::string qs2utf8s(const QString& qs) { return std::string((const char *)qs.toUtf8()); } +inline std::string qs2u8s(const QString& qs) +{ + return std::string((const char *)qs.toUtf8()); +} inline QString u8s2qs(const std::string us) { return QString::fromUtf8(us.c_str()); diff --git a/src/qtgui/recoll.qrc b/src/qtgui/recoll.qrc index 53013260..766b66f5 100644 --- a/src/qtgui/recoll.qrc +++ b/src/qtgui/recoll.qrc @@ -14,5 +14,7 @@ images/up.png images/down.png images/recoll.png + images/interro.png + images/clock.png diff --git a/src/qtgui/ssearch_w.cpp b/src/qtgui/ssearch_w.cpp index b4f69d25..fcb5a878 100644 --- a/src/qtgui/ssearch_w.cpp +++ b/src/qtgui/ssearch_w.cpp @@ -18,6 +18,8 @@ #include #include +#include +#include #include #include @@ -30,9 +32,12 @@ #include #include #include -#include #include #include +#include +#include +#include +#include #include "log.h" #include "guiutils.h" @@ -43,79 +48,195 @@ #include "rclhelp.h" #include "xmltosd.h" #include "smallut.h" +#include "rcldb.h" +#include "recoll.h" using namespace std; -// Typing interval after which we consider starting autosearch: no sense to do -// this is user is typing fast and continuously -static const int strokeTimeoutMS = 250; +// Max search history matches displayed in completer +static const int maxhistmatch = 10; +// Max db term matches fetched from the index +static const int maxdbtermmatch = 20; +// Visible rows for the completer listview +static const int completervisibleitems = 20; + +void RclCompleterModel::init() +{ + if (!clockPixmap.load(":/images/clock.png") || + !interroPixmap.load(":/images/interro.png")) { + LOGERR("SSearch: pixmap loading failed\n"); + } +} + +int RclCompleterModel::rowCount(const QModelIndex &) const +{ + LOGDEB1("RclCompleterModel::rowCount: " << currentlist.size() << "\n"); + return currentlist.size(); +} + +QVariant RclCompleterModel::data(const QModelIndex &index, int role) const +{ + LOGDEB1("RclCompleterModel::data: row: " << index.row() << " role " << + role << "\n"); + if (role != Qt::DisplayRole && role != Qt::EditRole && + role != Qt::DecorationRole) { + return QVariant(); + } + if (index.row() < 0 || index.row() >= int(currentlist.size())) { + return QVariant(); + } + + if (role == Qt::DecorationRole) { + LOGDEB1("RclCompleterModel::data: returning pixmap\n"); + return index.row() < firstfromindex ? QVariant(clockPixmap) : + QVariant(interroPixmap); + } else { + LOGDEB1("RclCompleterModel::data: return: " << + qs2u8s(currentlist[index.row()]) << endl); + return QVariant(currentlist[index.row()]); + } +} + +void RclCompleterModel::onPartialWord( + int tp, const QString& qtext, const QString& qpartial) +{ + string partial = qs2u8s(qpartial); + LOGDEB1("RclCompleterModel::onPartialWord: [" << partial << "]\n"); + + bool onlyspace = qtext.trimmed().isEmpty(); + + currentlist.clear(); + beginResetModel(); + if ((prefs.ssearchNoComplete && !onlyspace) || tp == SSearch::SST_FNM) { + // Nocomplete: only look at history by entering space + // Filename: no completion for now. We'd need to termatch with + // the right prefix? + endResetModel(); + return; + } + + int histmatch = 0; + // Look for matches between the full entry and the search history + // (anywhere in the string) + for (int i = 0; i < prefs.ssearchHistory.count(); i++) { + LOGDEB1("[" << qs2u8s(prefs.ssearchHistory[i]) << "] contains [" << + qs2u8s(qtext) << "] ?\n"); + if (prefs.ssearchHistory[i].contains(qtext, Qt::CaseInsensitive)) { + LOGDEB1("YES\n"); + currentlist.push_back(prefs.ssearchHistory[i]); + if (!onlyspace && ++histmatch >= maxhistmatch) + break; + } else { + LOGDEB1("NO\n"); + } + } + firstfromindex = currentlist.size(); + + // Look for Recoll terms beginning with the partial word + if (!partial.empty() && partial.compare(" ")) { + Rcl::TermMatchResult rclmatches; + if (!rcldb->termMatch(Rcl::Db::ET_WILD, string(), + partial + "*", rclmatches, maxdbtermmatch)) { + LOGDEB1("RclCompleterModel: termMatch failed: [" << partial + "*" << + "]\n"); + } else { + LOGDEB1("RclCompleterModel: termMatch cnt: " << + rclmatches.entries.size() << endl); + } + for (const auto& entry : rclmatches.entries) { + LOGDEB1("RclCompleterModel: match " << entry.term << endl); + string data = entry.term; + currentlist.push_back(u8s2qs(data)); + } + } + endResetModel(); +} void SSearch::init() { - // See enum above and keep in order ! + // See enum in .h and keep in order ! searchTypCMB->addItem(tr("Any term")); searchTypCMB->addItem(tr("All terms")); searchTypCMB->addItem(tr("File name")); searchTypCMB->addItem(tr("Query language")); - // We'd like to use QComboBox::InsertAtTop but it doesn't do lru - // (existing item stays at its place instead of jumping at top) - queryText->setInsertPolicy(QComboBox::NoInsert); - - queryText->addItems(prefs.ssearchHistory); - queryText->setEditText(""); - connect(queryText->lineEdit(), SIGNAL(returnPressed()), - this, SLOT(startSimpleSearch())); - connect(queryText->lineEdit(), SIGNAL(textChanged(const QString&)), + connect(queryText, SIGNAL(returnPressed()), this, SLOT(startSimpleSearch())); + connect(queryText, SIGNAL(textChanged(const QString&)), this, SLOT(searchTextChanged(const QString&))); - connect(clearqPB, SIGNAL(clicked()), - queryText->lineEdit(), SLOT(clear())); + connect(queryText, SIGNAL(textEdited(const QString&)), + this, SLOT(searchTextEdited(const QString&))); + connect(clearqPB, SIGNAL(clicked()), queryText, SLOT(clear())); connect(searchPB, SIGNAL(clicked()), this, SLOT(startSimpleSearch())); connect(searchTypCMB, SIGNAL(activated(int)), this, SLOT(searchTypeChanged(int))); - queryText->installEventFilter(this); - queryText->view()->installEventFilter(this); - queryText->setInsertPolicy(QComboBox::NoInsert); - // Note: we can't do the obvious and save the completer instead because - // the combobox lineedit will delete the completer on setCompleter(0). - // But the model does not belong to the completer so it's not deleted... - m_savedModel = queryText->completer()->model(); - if (prefs.ssearchNoComplete) - queryText->completer()->setModel(0); - // Recoll searches are always case-sensitive because of the use of - // capitalization to suppress stemming - queryText->completer()->setCaseSensitivity(Qt::CaseSensitive); - m_displayingCompletions = false; - m_escape = false; - m_disableAutosearch = true; - m_stroketimeout = new QTimer(this); - m_stroketimeout->setSingleShot(true); - connect(m_stroketimeout, SIGNAL(timeout()), this, SLOT(timerDone())); - m_keystroke = false; + RclCompleterModel *completermodel = new RclCompleterModel(this); + QCompleter *completer = new QCompleter(completermodel, this); + completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion); + completer->setFilterMode(Qt::MatchContains); + completer->setCaseSensitivity(Qt::CaseInsensitive); + completer->setMaxVisibleItems(completervisibleitems); + queryText->setCompleter(completer); + connect( + this, SIGNAL(partialWord(int, const QString&, const QString&)), + completermodel, SLOT(onPartialWord(int,const QString&,const QString&))); + connect(completer, SIGNAL(activated(const QString&)), this, + SLOT(onCompletionActivated(const QString&))); } void SSearch::takeFocus() { - LOGDEB2("SSearch: take focus\n"); + LOGDEB1("SSearch: take focus\n"); queryText->setFocus(Qt::ShortcutFocusReason); // If the focus was already in the search entry, the text is not selected. // Do it for consistency - queryText->lineEdit()->selectAll(); + queryText->selectAll(); } -void SSearch::timerDone() +QString SSearch::currentText() { - QString qs = queryText->currentText(); - LOGDEB1("SSearch::timerDone: qs [" << qs2utf8s(qs) << "]\n"); - searchTextChanged(qs); + return queryText->text(); +} + +void SSearch::clearAll() +{ + queryText->clear(); +} + +void SSearch::restoreText() +{ + queryText->setText(m_savedEditText); +} + +void SSearch::onCompletionActivated(const QString& text) +{ + LOGDEB1("SSearch::onCompletionActivated: current text " << + qs2u8s(currentText()) << endl); + m_savedEditText = m_savedEditText + text; + QTimer::singleShot(0, this, SLOT(restoreText())); +} + +void SSearch::searchTextEdited(const QString& text) +{ + LOGDEB1("SSearch::searchTextEdited: text [" << qs2u8s(text) << "]\n"); + QString pword; + int cs = getPartialWord(pword); + int tp = searchTypCMB->currentIndex(); + + m_savedEditText = text.left(cs); + LOGDEB1("SSearch::searchTextEdited: cs " <= 0) { + emit partialWord(tp, currentText(), pword); + } else { + emit partialWord(tp, currentText(), " "); + } } void SSearch::searchTextChanged(const QString& text) { - QString qs = queryText->currentText(); - LOGDEB1("SSearch::searchTextChanged. ks " << m_keystroke << " qs [" << - qs2utf8s(text) << "]\n"); + LOGDEB1("SSearch::searchTextChanged: text [" << qs2u8s(text) << "]\n"); + if (text.isEmpty()) { searchPB->setEnabled(false); clearqPB->setEnabled(false); @@ -124,31 +245,12 @@ void SSearch::searchTextChanged(const QString& text) } else { searchPB->setEnabled(true); clearqPB->setEnabled(true); - if (m_keystroke) { - m_tstartqs = qs; - } - if (prefs.ssearchAsYouType && !m_disableAutosearch && - !m_keystroke && m_tstartqs == qs) { - m_disableAutosearch = true; - string s; - int cs = partialWord(s); - LOGDEB1("SSearch::searchTextChanged: autosearch. cs " << cs << - " s [" << s << "]\n"); - if (cs < 0) { - startSimpleSearch(); - } else if (!m_stroketimeout->isActive() && s.size() >= 2) { - s = qs2utf8s(queryText->currentText()); - s += "*"; - startSimpleSearch(s, 20); - } - } } - m_keystroke = false; } void SSearch::searchTypeChanged(int typ) { - LOGDEB("Search type now " << typ << "\n"); + LOGDEB1("Search type now " << typ << "\n"); // Adjust context help if (typ == SST_LANG) { HelpClient::installMap((const char *)this->objectName().toUtf8(), @@ -191,13 +293,10 @@ void SSearch::searchTypeChanged(int typ) void SSearch::startSimpleSearch() { - QString qs = queryText->currentText(); - LOGDEB("SSearch::startSimpleSearch(): qs [" << qs2utf8s(qs) << "]\n"); - if (qs.length() == 0) + if (queryText->completer()->popup()->isVisible()) { return; - - string u8 = (const char *)queryText->currentText().toUtf8(); - + } + string u8 = qs2u8s(queryText->text()); trimstring(u8); if (u8.length() == 0) return; @@ -205,38 +304,17 @@ void SSearch::startSimpleSearch() if (!startSimpleSearch(u8)) return; - LOGDEB("startSimpleSearch: updating history\n"); - // Search terms history: - // We want to have the new text at the top and any older identical - // entry to be erased. There is no standard qt policy to do this ? - // So do it by hand. - QString txt = queryText->currentText(); - QString txtt = txt.trimmed(); - int index = queryText->findText(txtt); - if (index > 0) { - queryText->removeItem(index); - } - if (index != 0) { - queryText->insertItem(0, txtt); - queryText->setEditText(txt); - } - m_disableAutosearch = true; - m_stroketimeout->stop(); - - // Save the current state of the listbox list to the prefs (will - // go to disk) - prefs.ssearchHistory.clear(); - for (int index = 0; index < queryText->count(); index++) { - prefs.ssearchHistory.push_back(queryText->itemText(index)); - } + // Search terms history. + // New text at the front and erase any older identical entry + QString txt = currentText().trimmed(); + if (txt.isEmpty()) + return; + prefs.ssearchHistory.insert(0, txt); + prefs.ssearchHistory.removeDuplicates(); } + void SSearch::setPrefs() { - if (prefs.ssearchNoComplete) { - queryText->completer()->setModel(0); - } else { - queryText->completer()->setModel(m_savedModel); - } } string SSearch::asXML() @@ -264,7 +342,7 @@ bool SSearch::startSimpleSearch(const string& u8, int maxexp) sdata = wasaStringToRcl(theconfig, stemlang, u8, reason, (const char *)prefs.autoSuffs.toUtf8()); if (!prefs.autoSuffs.isEmpty()) { - xml << " " << qs2utf8s(prefs.autoSuffs) << "\n"; + xml << " " << qs2u8s(prefs.autoSuffs) << "\n"; } } else { sdata = wasaStringToRcl(theconfig, stemlang, u8, reason); @@ -339,7 +417,7 @@ bool SSearch::fromXML(const SSearchDef& fxml) } // Same for autosuffs - stringToStrings(qs2utf8s(prefs.autoSuffs), cur); + stringToStrings(qs2u8s(prefs.autoSuffs), cur); stored = set(fxml.autosuffs.begin(), fxml.autosuffs.end()); stringsToString(fxml.stemlangs, asString); if (cur != stored) { @@ -378,14 +456,12 @@ bool SSearch::fromXML(const SSearchDef& fxml) void SSearch::setSearchString(const QString& txt) { - m_disableAutosearch = true; - m_stroketimeout->stop(); - queryText->setEditText(txt); + queryText->setText(txt); } bool SSearch::hasSearchString() { - return !queryText->lineEdit()->text().isEmpty(); + return !currentText().isEmpty(); } // Add term to simple search. Term comes out of double-click in @@ -398,7 +474,7 @@ bool SSearch::hasSearchString() static const char* punct = " \t()<>\"'[]{}!^*.,:;\n\r"; void SSearch::addTerm(QString term) { - LOGDEB("SSearch::AddTerm: [" << qs2utf8s(term) << "]\n"); + LOGDEB("SSearch::AddTerm: [" << qs2u8s(term) << "]\n"); string t = (const char *)term.toUtf8(); string::size_type pos = t.find_last_not_of(punct); if (pos == string::npos) @@ -411,24 +487,20 @@ void SSearch::addTerm(QString term) return; term = QString::fromUtf8(t.c_str()); - QString text = queryText->currentText(); + QString text = currentText(); text += QString::fromLatin1(" ") + term; - queryText->setEditText(text); - m_disableAutosearch = true; - m_stroketimeout->stop(); + queryText->setText(text); } void SSearch::onWordReplace(const QString& o, const QString& n) { - LOGDEB("SSearch::onWordReplace: o [" << qs2utf8s(o) << "] n [" << - qs2utf8s(n) << "]\n"); - QString txt = queryText->currentText(); + LOGDEB("SSearch::onWordReplace: o [" << qs2u8s(o) << "] n [" << + qs2u8s(n) << "]\n"); + QString txt = currentText(); QRegExp exp = QRegExp(QString("\\b") + o + QString("\\b")); exp.setCaseSensitivity(Qt::CaseInsensitive); txt.replace(exp, n); - queryText->setEditText(txt); - m_disableAutosearch = true; - m_stroketimeout->stop(); + queryText->setText(txt); Qt::KeyboardModifiers mods = QApplication::keyboardModifiers (); if (mods == Qt::NoModifier) startSimpleSearch(); @@ -441,10 +513,10 @@ void SSearch::setAnyTermMode() // If text does not end with space, return last (partial) word and >0 // else return -1 -int SSearch::partialWord(string& s) +int SSearch::getPartialWord(QString& word) { // Extract last word in text - QString txt = queryText->currentText(); + QString txt = currentText(); int cs = txt.lastIndexOf(" "); if (cs == -1) cs = 0; @@ -453,313 +525,6 @@ int SSearch::partialWord(string& s) if (txt.size() == 0 || cs == txt.size()) { return -1; } - s = qs2utf8s(txt.right(txt.size() - cs)); + word = txt.right(txt.size() - cs); return cs; } - -// Create completion list for term by adding a joker at the end and calling -// rcldb->termMatch(). -int SSearch::completionList(string s, QStringList& lst, int max) -{ - if (!rcldb) - return -1; - if (s.empty()) - return 0; - // Query database for completions - s += "*"; - Rcl::TermMatchResult tmres; - if (!rcldb->termMatch(Rcl::Db::ET_WILD, "", s, tmres, max) || - tmres.entries.size() == 0) { - return 0; - } - for (const auto& entry: tmres.entries) { - lst.push_back(u8s2qs(entry.term)); - } - return lst.size(); -} - -// Complete last word in input by querying db for all possible terms. -void SSearch::completion() -{ - LOGDEB("SSearch::completion\n"); - - m_disableAutosearch = true; - m_stroketimeout->stop(); - - if (!rcldb) - return; - if (searchTypCMB->currentIndex() == SST_FNM) { - // Filename: no completion - QApplication::beep(); - return; - } - - // Extract last word in text - string s; - int cs = partialWord(s); - if (cs < 0) { - QApplication::beep(); - return; - } - - // Query database for completions - QStringList lst; - const int maxdpy = 80; - const int maxwalked = 10000; - if (completionList(s, lst, maxwalked) <= 0) { - QApplication::beep(); - return; - } - if (lst.size() >= maxdpy) { - LOGDEB0("SSearch::completion(): truncating list\n"); - lst = lst.mid(0, maxdpy); - lst.append("[...]"); - } - - // If list from db is single word, insert it, else popup the listview - if (lst.size() == 1) { - QString txt = queryText->currentText(); - txt.truncate(cs); - txt.append(lst[0]); - queryText->setEditText(txt); - } else { - m_savedEditText = queryText->currentText(); - m_displayingCompletions = true; - m_chosenCompletion.clear(); - m_completedWordStart = cs; - - queryText->clear(); - queryText->addItems(lst); - queryText->showPopup(); - - connect(queryText, SIGNAL(activated(const QString&)), this, - SLOT(completionTermChosen(const QString&))); - } -} - -void SSearch::completionTermChosen(const QString& text) -{ - if (text != "[...]") - m_chosenCompletion = text; - else - m_chosenCompletion.clear(); -} - -void SSearch::wrapupCompletion() -{ - LOGDEB("SSearch::wrapupCompletion\n"); - - queryText->clear(); - queryText->addItems(prefs.ssearchHistory); - if (!m_chosenCompletion.isEmpty()) { - m_savedEditText.truncate(m_completedWordStart); - m_savedEditText.append(m_chosenCompletion); - } - queryText->setEditText(m_savedEditText); - m_disableAutosearch = true; - m_savedEditText.clear(); - m_chosenCompletion.clear(); - m_displayingCompletions = false; - disconnect(queryText, SIGNAL(activated(const QString&)), this, - SLOT(completionTermChosen(const QString&))); -} - -#undef SHOWEVENTS -#if defined(SHOWEVENTS) -const char *eventTypeToStr(int tp) -{ - switch (tp) { - case 0: return "None"; - case 1: return "Timer"; - case 2: return "MouseButtonPress"; - case 3: return "MouseButtonRelease"; - case 4: return "MouseButtonDblClick"; - case 5: return "MouseMove"; - case 6: return "KeyPress"; - case 7: return "KeyRelease"; - case 8: return "FocusIn"; - case 9: return "FocusOut"; - case 10: return "Enter"; - case 11: return "Leave"; - case 12: return "Paint"; - case 13: return "Move"; - case 14: return "Resize"; - case 15: return "Create"; - case 16: return "Destroy"; - case 17: return "Show"; - case 18: return "Hide"; - case 19: return "Close"; - case 20: return "Quit"; - case 21: return "ParentChange"; - case 131: return "ParentAboutToChange"; - case 22: return "ThreadChange"; - case 24: return "WindowActivate"; - case 25: return "WindowDeactivate"; - case 26: return "ShowToParent"; - case 27: return "HideToParent"; - case 31: return "Wheel"; - case 33: return "WindowTitleChange"; - case 34: return "WindowIconChange"; - case 35: return "ApplicationWindowIconChange"; - case 36: return "ApplicationFontChange"; - case 37: return "ApplicationLayoutDirectionChange"; - case 38: return "ApplicationPaletteChange"; - case 39: return "PaletteChange"; - case 40: return "Clipboard"; - case 42: return "Speech"; - case 43: return "MetaCall"; - case 50: return "SockAct"; - case 132: return "WinEventAct"; - case 52: return "DeferredDelete"; - case 60: return "DragEnter"; - case 61: return "DragMove"; - case 62: return "DragLeave"; - case 63: return "Drop"; - case 64: return "DragResponse"; - case 68: return "ChildAdded"; - case 69: return "ChildPolished"; - case 70: return "ChildInserted"; - case 72: return "LayoutHint"; - case 71: return "ChildRemoved"; - case 73: return "ShowWindowRequest"; - case 74: return "PolishRequest"; - case 75: return "Polish"; - case 76: return "LayoutRequest"; - case 77: return "UpdateRequest"; - case 78: return "UpdateLater"; - case 79: return "EmbeddingControl"; - case 80: return "ActivateControl"; - case 81: return "DeactivateControl"; - case 82: return "ContextMenu"; - case 83: return "InputMethod"; - case 86: return "AccessibilityPrepare"; - case 87: return "TabletMove"; - case 88: return "LocaleChange"; - case 89: return "LanguageChange"; - case 90: return "LayoutDirectionChange"; - case 91: return "Style"; - case 92: return "TabletPress"; - case 93: return "TabletRelease"; - case 94: return "OkRequest"; - case 95: return "HelpRequest"; - case 96: return "IconDrag"; - case 97: return "FontChange"; - case 98: return "EnabledChange"; - case 99: return "ActivationChange"; - case 100: return "StyleChange"; - case 101: return "IconTextChange"; - case 102: return "ModifiedChange"; - case 109: return "MouseTrackingChange"; - case 103: return "WindowBlocked"; - case 104: return "WindowUnblocked"; - case 105: return "WindowStateChange"; - case 110: return "ToolTip"; - case 111: return "WhatsThis"; - case 112: return "StatusTip"; - case 113: return "ActionChanged"; - case 114: return "ActionAdded"; - case 115: return "ActionRemoved"; - case 116: return "FileOpen"; - case 117: return "Shortcut"; - case 51: return "ShortcutOverride"; - case 30: return "Accel"; - case 32: return "AccelAvailable"; - case 118: return "WhatsThisClicked"; - case 120: return "ToolBarChange"; - case 121: return "ApplicationActivated"; - case 122: return "ApplicationDeactivated"; - case 123: return "QueryWhatsThis"; - case 124: return "EnterWhatsThisMode"; - case 125: return "LeaveWhatsThisMode"; - case 126: return "ZOrderChange"; - case 127: return "HoverEnter"; - case 128: return "HoverLeave"; - case 129: return "HoverMove"; - case 119: return "AccessibilityHelp"; - case 130: return "AccessibilityDescription"; - case 150: return "EnterEditFocus"; - case 151: return "LeaveEditFocus"; - case 152: return "AcceptDropsChange"; - case 153: return "MenubarUpdated"; - case 154: return "ZeroTimerEvent"; - case 155: return "GraphicsSceneMouseMove"; - case 156: return "GraphicsSceneMousePress"; - case 157: return "GraphicsSceneMouseRelease"; - case 158: return "GraphicsSceneMouseDoubleClick"; - case 159: return "GraphicsSceneContextMenu"; - case 160: return "GraphicsSceneHoverEnter"; - case 161: return "GraphicsSceneHoverMove"; - case 162: return "GraphicsSceneHoverLeave"; - case 163: return "GraphicsSceneHelp"; - case 164: return "GraphicsSceneDragEnter"; - case 165: return "GraphicsSceneDragMove"; - case 166: return "GraphicsSceneDragLeave"; - case 167: return "GraphicsSceneDrop"; - case 168: return "GraphicsSceneWheel"; - case 169: return "KeyboardLayoutChange"; - case 170: return "DynamicPropertyChange"; - case 171: return "TabletEnterProximity"; - case 172: return "TabletLeaveProximity"; - default: return "UnknownEvent"; - } -} -#endif - -bool SSearch::eventFilter(QObject *target, QEvent *event) -{ -#if defined(SHOWEVENTS) - if (event->type() == QEvent::Timer || - event->type() == QEvent::UpdateRequest || - event->type() == QEvent::Paint) - return false; - LOGDEB2("SSearch::eventFilter: target " << target << " (" << - queryText->lineEdit() << ") type " << - eventTypeToStr(event->type()) << "\n"); -#endif - - if (target == (QObject*)(queryText->view())) { - if (event->type() == QEvent::Hide) { - // List was closed. If we were displaying completions, need - // to reset state. - if (m_displayingCompletions) { - QTimer::singleShot(0, this, SLOT(wrapupCompletion())); - } - } - return false; - } - - if (event->type() == QEvent::KeyPress) { - QKeyEvent *ke = (QKeyEvent *)event; - LOGDEB1("SSearch::eventFilter: keyPress (m_escape " << m_escape << - ") key " << ke->key() << "\n"); - if (ke->key() == Qt::Key_Escape) { - LOGDEB("Escape\n"); - m_escape = true; - m_disableAutosearch = true; - m_stroketimeout->stop(); - return true; - } else if (m_escape && ke->key() == Qt::Key_Space) { - LOGDEB("Escape space\n"); - ke->accept(); - completion(); - m_escape = false; - m_disableAutosearch = true; - m_stroketimeout->stop(); - return true; - } else if (ke->key() == Qt::Key_Space) { - if (prefs.ssearchOnWS) - startSimpleSearch(); - } else { - m_escape = false; - m_keystroke = true; - if (prefs.ssearchAsYouType) { - m_disableAutosearch = false; - QString qs = queryText->currentText(); - LOGDEB0("SSearch::eventFilter: start timer, qs [" << - qs2utf8s(qs) << "]\n"); - m_stroketimeout->start(strokeTimeoutMS); - } - } - } - return false; -} diff --git a/src/qtgui/ssearch_w.h b/src/qtgui/ssearch_w.h index 91d721eb..56f73255 100644 --- a/src/qtgui/ssearch_w.h +++ b/src/qtgui/ssearch_w.h @@ -22,6 +22,9 @@ #include #include +#include +#include +#include class QTimer; @@ -33,8 +36,28 @@ class QTimer; struct SSearchDef; -class SSearch : public QWidget, public Ui::SSearchBase -{ +class RclCompleterModel : public QAbstractListModel { + Q_OBJECT + +public: + RclCompleterModel(QWidget *parent = 0) + : QAbstractListModel(parent) { + init(); + } + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, + int role = Qt::DisplayRole) const override; +public slots: + virtual void onPartialWord(int, const QString&, const QString&); +private: + void init(); + vector currentlist; + int firstfromindex; + QPixmap clockPixmap; + QPixmap interroPixmap; +}; + +class SSearch : public QWidget, public Ui::SSearchBase { Q_OBJECT public: @@ -43,56 +66,51 @@ public: enum SSearchType {SST_ANY = 0, SST_ALL = 1, SST_FNM = 2, SST_LANG = 3}; SSearch(QWidget* parent = 0, const char * = 0) - : QWidget(parent) - { - setupUi(this); - init(); + : QWidget(parent) { + setupUi(this); + init(); } virtual void init(); virtual void setAnyTermMode(); - virtual void completion(); - virtual bool eventFilter(QObject *target, QEvent *event); virtual bool hasSearchString(); virtual void setPrefs(); // Return last performed search as XML text. virtual std::string asXML(); // Restore ssearch UI from saved search virtual bool fromXML(const SSearchDef& fxml); - + virtual QString currentText(); + public slots: - virtual void searchTextChanged(const QString & text); virtual void searchTypeChanged(int); virtual void setSearchString(const QString& text); virtual void startSimpleSearch(); virtual void addTerm(QString); virtual void onWordReplace(const QString&, const QString&); - virtual void completionTermChosen(const QString& text); - virtual void wrapupCompletion(); - virtual void timerDone(); virtual void takeFocus(); + // Forget current entry and any state (history) + virtual void clearAll(); +private slots: + virtual void searchTextChanged(const QString&); + virtual void searchTextEdited(const QString&); + virtual void onCompletionActivated(const QString&); + virtual void restoreText(); + signals: void startSearch(std::shared_ptr, bool); void clearSearch(); - private: - bool m_escape; + void partialWord(int, const QString& text, const QString &partial); - bool m_displayingCompletions; - QString m_chosenCompletion; - QString m_savedEditText; - unsigned int m_completedWordStart; - - bool m_disableAutosearch; - QTimer *m_stroketimeout; - bool m_keystroke; - QString m_tstartqs; - QAbstractItemModel *m_savedModel; - std::string m_xml; /* Saved xml version of the search, as we start it */ - - int partialWord(string& s); - int completionList(string s, QStringList& lst, int max = 100); +private: + int getPartialWord(QString& word); bool startSimpleSearch(const string& q, int maxexp = -1); + + /* We save multiword entries because the completer replaces them with + the completion */ + QString m_savedEditText; + /* Saved xml version of the search, as we start it */ + std::string m_xml; }; diff --git a/src/qtgui/ssearchb.ui b/src/qtgui/ssearchb.ui index d143591c..45972aa5 100644 --- a/src/qtgui/ssearchb.ui +++ b/src/qtgui/ssearchb.ui @@ -65,7 +65,7 @@ - + 8 @@ -81,18 +81,6 @@ Enter search terms here. Type ESC SPC for completions of current term. - - true - - - 200 - - - QComboBox::NoInsert - - - false - diff --git a/src/qtgui/uiprefs.ui b/src/qtgui/uiprefs.ui index e79be779..847f2270 100644 --- a/src/qtgui/uiprefs.ui +++ b/src/qtgui/uiprefs.ui @@ -267,26 +267,6 @@ - - - - Auto-start simple search on whitespace entry. - - - false - - - - - - - Search as you type. - - - false - - - diff --git a/src/qtgui/uiprefs_w.cpp b/src/qtgui/uiprefs_w.cpp index 012f7766..38754c31 100644 --- a/src/qtgui/uiprefs_w.cpp +++ b/src/qtgui/uiprefs_w.cpp @@ -95,10 +95,6 @@ void UIPrefsDialog::init() connect(buttonCancel, SIGNAL(clicked()), this, SLOT(reject())); connect(buildAbsCB, SIGNAL(toggled(bool)), replAbsCB, SLOT(setEnabled(bool))); - connect(ssAutoAllCB, SIGNAL(toggled(bool)), - ssAutoSpaceCB, SLOT(setDisabled(bool))); - connect(ssAutoAllCB, SIGNAL(toggled(bool)), - ssAutoSpaceCB, SLOT(setChecked(bool))); setFromPrefs(); } @@ -128,9 +124,7 @@ void UIPrefsDialog::setFromPrefs() filterBT_RB->setChecked(1); break; } - ssAutoSpaceCB->setChecked(prefs.ssearchOnWS); ssNoCompleteCB->setChecked(prefs.ssearchNoComplete); - ssAutoAllCB->setChecked(prefs.ssearchAsYouType); syntlenSB->setValue(prefs.syntAbsLen); syntctxSB->setValue(prefs.syntAbsCtx); @@ -261,9 +255,7 @@ void UIPrefsDialog::setupReslistFontPB() void UIPrefsDialog::accept() { - prefs.ssearchOnWS = ssAutoSpaceCB->isChecked(); prefs.ssearchNoComplete = ssNoCompleteCB->isChecked(); - prefs.ssearchAsYouType = ssAutoAllCB->isChecked(); if (ssearchTypCMB->currentIndex() == 4) { prefs.ssearchTypSav = true;