PageRenderTime 51ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/local/arora-0.11.0/src/history/historycompleter.cpp

http://mingw-lib.googlecode.com/
C++ | 278 lines | 179 code | 51 blank | 48 comment | 46 complexity | a403775e853f80f38d24bc8e67904234 MD5 | raw file
Possible License(s): GPL-2.0, GPL-3.0, MPL-2.0-no-copyleft-exception
  1. /*
  2. * Copyright 2009 Benjamin K. Stuhl <bks24@cornell.edu>
  3. *
  4. * This program is free software; you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation; either version 2 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program; if not, write to the Free Software
  16. * Foundation, Inc., 51 Franklin Street, Fifth Floor,
  17. * Boston, MA 02110-1301 USA
  18. */
  19. #include "historycompleter.h"
  20. #include <qevent.h>
  21. #include <qfontmetrics.h>
  22. #include <qheaderview.h>
  23. HistoryCompletionView::HistoryCompletionView(QWidget *parent)
  24. : QTableView(parent)
  25. {
  26. horizontalHeader()->hide();
  27. verticalHeader()->hide();
  28. setShowGrid(false);
  29. setSelectionBehavior(QAbstractItemView::SelectRows);
  30. setSelectionMode(QAbstractItemView::SingleSelection);
  31. setTextElideMode(Qt::ElideRight);
  32. QFontMetrics metrics = fontMetrics();
  33. verticalHeader()->setDefaultSectionSize(metrics.height());
  34. // As URLs are always LRT, this should be LRT as well
  35. setLayoutDirection(Qt::LeftToRight);
  36. }
  37. void HistoryCompletionView::resizeEvent(QResizeEvent *event)
  38. {
  39. horizontalHeader()->resizeSection(0, 0.65 * width());
  40. horizontalHeader()->setStretchLastSection(true);
  41. QTableView::resizeEvent(event);
  42. }
  43. int HistoryCompletionView::sizeHintForRow(int row) const
  44. {
  45. Q_UNUSED(row)
  46. QFontMetrics metrics = fontMetrics();
  47. return metrics.height();
  48. }
  49. HistoryCompletionModel::HistoryCompletionModel(QObject *parent)
  50. : QSortFilterProxyModel(parent)
  51. , m_searchMatcher(QString(), Qt::CaseInsensitive, QRegExp::FixedString)
  52. , m_wordMatcher(QString(), Qt::CaseInsensitive)
  53. , m_isValid(false)
  54. {
  55. setDynamicSortFilter(true);
  56. }
  57. QVariant HistoryCompletionModel::data(const QModelIndex &index, int role) const
  58. {
  59. // if we are valid, tell QCompleter that everything we have filtered matches
  60. // what the user typed; if not, nothing matches
  61. if (role == HistoryCompletionRole && index.isValid()) {
  62. if (isValid())
  63. return QLatin1String("a");
  64. else
  65. return QLatin1String("b");
  66. }
  67. if (role == Qt::FontRole && index.column() == 1) {
  68. QFont font = qvariant_cast<QFont>(QSortFilterProxyModel::data(index, role));
  69. font.setWeight(QFont::Light);
  70. return font;
  71. }
  72. if (role == Qt::DisplayRole)
  73. role = (index.column() == 0) ? HistoryModel::UrlStringRole : HistoryModel::TitleRole;
  74. return QSortFilterProxyModel::data(index, role);
  75. }
  76. QString HistoryCompletionModel::searchString() const
  77. {
  78. return m_searchString;
  79. }
  80. void HistoryCompletionModel::setSearchString(const QString &str)
  81. {
  82. if (str == m_searchString)
  83. return;
  84. m_searchString = str;
  85. m_searchMatcher.setPattern(str);
  86. m_wordMatcher.setPattern(QLatin1String("\\b") + QRegExp::escape(str));
  87. invalidateFilter();
  88. }
  89. bool HistoryCompletionModel::isValid() const
  90. {
  91. return m_isValid;
  92. }
  93. void HistoryCompletionModel::setValid(bool b)
  94. {
  95. if (b == m_isValid)
  96. return;
  97. m_isValid = b;
  98. // tell the HistoryCompleter that we've changed
  99. emit dataChanged(index(0, 0), index(0, rowCount() - 1));
  100. }
  101. bool HistoryCompletionModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
  102. {
  103. // do a case-insensitive substring match against both the url and title;
  104. // we have also made sure that the user doesn't accidentally use regexp
  105. // metacharacters
  106. QModelIndex idx = sourceModel()->index(source_row, 0, source_parent);
  107. QString url = sourceModel()->data(idx, HistoryModel::UrlStringRole).toString();
  108. if (m_searchMatcher.indexIn(url) != -1)
  109. return true;
  110. QString title = sourceModel()->data(idx, HistoryModel::TitleRole).toString();
  111. if (m_searchMatcher.indexIn(title) != -1)
  112. return true;
  113. return false;
  114. }
  115. bool HistoryCompletionModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
  116. {
  117. // We give a bonus to hits that match on a word boundary so that e.g. "dot.kde.org"
  118. // is a better result for typing "dot" than "slashdot.org". However, we only look
  119. // for the string in the host name, not the entire url, since while it makes sense
  120. // to e.g. give "www.phoronix.com" a bonus for "ph", it does _not_ make sense to
  121. // give "www.yadda.com/foo.php" the bonus.
  122. int frecency_l = sourceModel()->data(left, HistoryFilterModel::FrecencyRole).toInt();
  123. QString url_l = sourceModel()->data(left, HistoryModel::UrlRole).toUrl().host();
  124. QString title_l = sourceModel()->data(left, HistoryModel::TitleRole).toString();
  125. if (m_wordMatcher.indexIn(url_l) != -1 || m_wordMatcher.indexIn(title_l) != -1)
  126. frecency_l *= 2;
  127. int frecency_r = sourceModel()->data(right, HistoryFilterModel::FrecencyRole).toInt();
  128. QString url_r = sourceModel()->data(right, HistoryModel::UrlRole).toUrl().host();
  129. QString title_r = sourceModel()->data(right, HistoryModel::TitleRole).toString();
  130. if (m_wordMatcher.indexIn(url_r) != -1 || m_wordMatcher.indexIn(title_r) != -1)
  131. frecency_r *= 2;
  132. // sort results in descending frecency-derived score
  133. return (frecency_r < frecency_l);
  134. }
  135. HistoryCompleter::HistoryCompleter(QObject *parent)
  136. : QCompleter(parent)
  137. {
  138. init();
  139. }
  140. HistoryCompleter::HistoryCompleter(QAbstractItemModel *m, QObject *parent)
  141. : QCompleter(m, parent)
  142. {
  143. init();
  144. }
  145. void HistoryCompleter::init()
  146. {
  147. setPopup(new HistoryCompletionView());
  148. // we want to complete against our own faked role
  149. setCompletionRole(HistoryCompletionModel::HistoryCompletionRole);
  150. // and since we fake our completion role, we can take
  151. // advantage of the sorted-model optimizations in QCompleter
  152. setCaseSensitivity(Qt::CaseSensitive);
  153. setModelSorting(QCompleter::CaseSensitivelySortedModel);
  154. m_filterTimer.setSingleShot(true);
  155. connect(&m_filterTimer, SIGNAL(timeout()), this, SLOT(updateFilter()));
  156. }
  157. QString HistoryCompleter::pathFromIndex(const QModelIndex &index) const
  158. {
  159. // we want to return the actual url from the history for the
  160. // data the QCompleter finally returns
  161. return model()->data(index, HistoryModel::UrlStringRole).toString();
  162. }
  163. QStringList HistoryCompleter::splitPath(const QString &path) const
  164. {
  165. if (path == m_searchString)
  166. return QStringList() << QLatin1String("a");
  167. // queue an update to our search string
  168. // We will wait a bit so that if the user is quickly typing,
  169. // we don't try to complete until they pause.
  170. if (m_filterTimer.isActive())
  171. m_filterTimer.stop();
  172. m_filterTimer.start(150);
  173. // if the previous search results are not a superset of
  174. // the current search results, tell the model that it is not valid yet
  175. if (!path.startsWith(m_searchString)) {
  176. HistoryCompletionModel *completionModel = qobject_cast<HistoryCompletionModel*>(model());
  177. Q_ASSERT(completionModel);
  178. completionModel->setValid(false);
  179. }
  180. m_searchString = path;
  181. // the actual filtering is done by the HistoryCompletionModel; we just
  182. // return a short dummy here so that QCompleter thinks we match everything
  183. return QStringList() << QLatin1String("a");
  184. }
  185. bool HistoryCompleter::eventFilter(QObject *obj, QEvent *event)
  186. {
  187. if (event->type() == QEvent::KeyPress && popup()->isVisible()) {
  188. QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
  189. if (keyEvent->key() == Qt::Key_Tab) {
  190. QKeyEvent *newEvent = new QKeyEvent(QEvent::KeyPress,
  191. Qt::Key_Down,
  192. keyEvent->modifiers(),
  193. QString());
  194. if (!QCompleter::eventFilter(obj, newEvent))
  195. obj->event(newEvent);
  196. return true;
  197. } else if (keyEvent->key() == Qt::Key_Backtab) {
  198. QKeyEvent *newEvent = new QKeyEvent(QEvent::KeyPress,
  199. Qt::Key_Up,
  200. keyEvent->modifiers(),
  201. keyEvent->text(),
  202. keyEvent->isAutoRepeat(),
  203. keyEvent->count());
  204. if (!QCompleter::eventFilter(obj, newEvent))
  205. obj->event(newEvent);
  206. return true;
  207. } else if (keyEvent->key() == Qt::Key_Escape) {
  208. popup()->hide();
  209. }
  210. }
  211. return QCompleter::eventFilter(obj, event);
  212. }
  213. void HistoryCompleter::updateFilter()
  214. {
  215. HistoryCompletionModel *completionModel = qobject_cast<HistoryCompletionModel*>(model());
  216. Q_ASSERT(completionModel);
  217. // tell the HistoryCompletionModel about the new search string
  218. completionModel->setSearchString(m_searchString);
  219. // sort the model
  220. completionModel->sort(0);
  221. // mark it valid
  222. completionModel->setValid(true);
  223. // and now update the QCompleter widget, but only if the user is still
  224. // typing a url
  225. if (widget() && widget()->hasFocus())
  226. complete();
  227. }