/* * Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "searchresultview.h" #include #include #include #include "mainwindow.h" PSearchResults SearchResultModel::addSearchResults(const QString &keyword, QSynedit::SearchOptions options, SearchFileScope scope, const QString& folder, const QString& filters, bool searchSubfolders) { int index=-1; for (int i=0;ikeyword == keyword && results->scope == scope && results->searchType == SearchType::Search) { index=i; break; } } if (index>=0) { mSearchResults.removeAt(index); } if (mSearchResults.size()>=MAX_SEARCH_RESULTS) { mSearchResults.pop_back(); } PSearchResults results = std::make_shared(); results->keyword = keyword; results->options = options; results->scope = scope; results->searchType = SearchType::Search; results->folder=folder; results->filters=filters; results->searchSubfolders=searchSubfolders; mSearchResults.push_front(results); mCurrentIndex = 0; return results; } PSearchResults SearchResultModel::addSearchResults( const QString& keyword, const QString& symbolFullname, SearchFileScope scope) { int index=-1; for (int i=0;isearchType == SearchType::FindOccurences && results->scope == scope && results->statementFullname == symbolFullname ) { index=i; break; } } if (index>=0) { mSearchResults.removeAt(index); } if (mSearchResults.size()>=MAX_SEARCH_RESULTS) { mSearchResults.pop_back(); } PSearchResults results = std::make_shared(); results->keyword = keyword; results->statementFullname = symbolFullname; results->filename = ""; results->searchType = SearchType::FindOccurences; results->scope = scope; mSearchResults.push_front(results); mCurrentIndex = 0; return results; } PSearchResults SearchResultModel::results(int index) { if (index<0 || index>=mSearchResults.size()) { return PSearchResults(); } return mSearchResults[index]; } void SearchResultModel::notifySearchResultsUpdated() { emit modelChanged(); } SearchResultModel::SearchResultModel(QObject* parent): QObject(parent), mCurrentIndex(-1) { } int SearchResultModel::currentIndex() const { return mCurrentIndex; } int SearchResultModel::resultsCount() const { return mSearchResults.count(); } PSearchResults SearchResultModel::currentResults() { return results(mCurrentIndex); } void SearchResultModel::setCurrentIndex(int index) { if (index!=mCurrentIndex && index>=0 && indexcurrentResults(); if (!results) return QModelIndex(); SearchResultTreeItem *parentItem=nullptr; PSearchResultTreeItem childItem; if (!parent.isValid()) { parentItem = nullptr; childItem = results->results[row]; } else { parentItem = static_cast(parent.internalPointer()); childItem = parentItem->results[row]; } if (childItem) return createIndex(row,column,childItem.get()); return QModelIndex(); } QModelIndex SearchResultTreeModel::parent(const QModelIndex &child) const { if (!child.isValid()) return QModelIndex(); SearchResultTreeItem* item = static_cast(child.internalPointer()); if (!item) { return QModelIndex(); } else { if (item->parent==nullptr) return QModelIndex(); SearchResultTreeItem* parent = item->parent; int row = -1; for (int i=0;iresults.count();i++) { if (parent->results[i].get()==item) { row = i; break; } } return createIndex(row,0,parent); } } int SearchResultTreeModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()){ //root PSearchResults searchResults = mSearchResultModel->currentResults(); if (!searchResults) return 0; return searchResults->results.count(); } SearchResultTreeItem* item = static_cast(parent.internalPointer()); if (!item) return 0; return item->results.count(); } int SearchResultTreeModel::columnCount(const QModelIndex &) const { return 1; } QVariant SearchResultTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()){ return QVariant(); } SearchResultTreeItem *item = static_cast(index.internalPointer()); if (!item) return QVariant(); if (role == Qt::DisplayRole) { PSearchResults results = mSearchResultModel->currentResults(); if (!results || !index.isValid() ) { // This is nothing this function is supposed to handle return QVariant(); } if (item->parent==nullptr) { //is filename return QString("%1(%2)").arg(item->filename) .arg(item->results.count()); } else { return QString("%1 %2: %3").arg(tr("Line")).arg(item->line) .arg(item->text); } } if (role == Qt::CheckStateRole && mSelectable) { PSearchResults results = mSearchResultModel->currentResults(); if (!results || !index.isValid() ) { // This is nothing this function is supposed to handle return QVariant(); } if (item->parent==nullptr) { //is filename return QVariant(); } else { return (item->selected)?Qt::Checked:Qt::Unchecked; } } return QVariant(); } SearchResultModel *SearchResultTreeModel::searchResultModel() const { return mSearchResultModel; } bool SearchResultTreeModel::getItemFileAndLineChar(const QModelIndex &index, QString &filename, int &line, int &startChar) { if (!index.isValid()){ return false; } SearchResultTreeItem *item = static_cast(index.internalPointer()); if (!item) return false; PSearchResults results = mSearchResultModel->currentResults(); if (!results ) { // This is nothing this function is supposed to handle return false; } SearchResultTreeItem *parent = item->parent; if (parent==nullptr) { //is filename return false; } else { filename = parent->filename; line = item->line; startChar = item->start; return true; } return false; } void SearchResultTreeModel::onResultModelChanged() { beginResetModel(); endResetModel(); } Qt::ItemFlags SearchResultTreeModel::flags(const QModelIndex &) const { Qt::ItemFlags flags=Qt::ItemIsEnabled | Qt::ItemIsSelectable; if (mSelectable) { flags.setFlag(Qt::ItemIsUserCheckable); } return flags; } bool SearchResultTreeModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid()){ return false; } SearchResultTreeItem *item = static_cast(index.internalPointer()); if (!item) return false; if (role == Qt::CheckStateRole && mSelectable) { PSearchResults results = mSearchResultModel->currentResults(); if (!results || !index.isValid() ) { // This is nothing this function is supposed to handle return false; } if (item->parent==nullptr) { //is filename return false; } else { item->selected = value.toBool(); return true; } } return false; } bool SearchResultTreeModel::selectable() const { return mSelectable; } void SearchResultTreeModel::setSelectable(bool newSelectable) { if (newSelectable!=mSelectable) { mSelectable = newSelectable; } beginResetModel(); if (mSelectable) { //select all items by default PSearchResults results = mSearchResultModel->currentResults(); if (results) { foreach (const PSearchResultTreeItem& file, results->results) { file->selected = false; foreach (const PSearchResultTreeItem& item, file->results) { item->selected = true; } } } } endResetModel(); } SearchResultListModel::SearchResultListModel(SearchResultModel *model, QObject *parent): QAbstractListModel(parent), mSearchResultModel(model) { connect(mSearchResultModel, &SearchResultModel::modelChanged, this, &SearchResultListModel::onResultModelChanged); } int SearchResultListModel::rowCount(const QModelIndex &) const { return mSearchResultModel->resultsCount(); } QVariant SearchResultListModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); if (role == Qt::DisplayRole) { PSearchResults results = mSearchResultModel->results(index.row()); if (!results) return QVariant(); if (results->searchType == SearchType::Search) { switch (results->scope) { case SearchFileScope::currentFile: return tr("Current File:") + QString(" \"%1\"").arg(results->keyword); case SearchFileScope::wholeProject: return tr("Files In Project:") + QString(" \"%1\"").arg(results->keyword); case SearchFileScope::openedFiles: return tr("Open Files:") + QString(" \"%1\"").arg(results->keyword); case SearchFileScope::Folder: return tr("\"%1\" in Folder \"%2\"").arg(results->keyword).arg(results->folder); } } else if (results->searchType == SearchType::FindOccurences) { if (results->scope == SearchFileScope::currentFile) { return tr("Find Usages in Current File: '%1'") .arg(results->keyword); } else { return tr("Find Usages in Project: '%1'") .arg(results->keyword); } } } return QVariant(); } void SearchResultListModel::onResultModelChanged() { beginResetModel(); endResetModel(); } /** * * see https://stackoverflow.com/questions/1956542/how-to-make-item-view-render-rich-html-text-in-qt/66412883#66412883 */ SearchResultTreeViewDelegate::SearchResultTreeViewDelegate(PSearchResultTreeModel model, QObject *parent): QStyledItemDelegate(parent), mModel(model) { } void SearchResultTreeViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optIn, const QModelIndex &index) const { QStyleOptionViewItem option = optIn; initStyleOption(&option,index); PSearchResults results = mModel->searchResultModel()->currentResults(); if (!results || !index.isValid() ) { // This is nothing this function is supposed to handle return; } QStyle *style = option.widget ? option.widget->style() : QApplication::style(); // Painting item without text (this takes care of painting e.g. the highlighted for selected // or hovered over items in an ItemView) option.text = QString(); style->drawControl(QStyle::CE_ItemViewItem, &option, painter, option.widget); SearchResultTreeItem* item = static_cast(index.internalPointer()); QString fullText; if (item->parent==nullptr) { //is filename fullText = QString("%1(%2)").arg(item->filename) .arg(item->results.count()); } else { fullText = QString("%1 %2: %3").arg(tr("Line")).arg(item->line) .arg(item->text); } // Figure out where to render the text in order to follow the requested alignment option.text = fullText; QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &option); QFontMetrics metrics = option.fontMetrics; int x=textRect.left(); int y=textRect.top() + metrics.ascent(); if (item->parent==nullptr) { //is filename painter->drawText(x,y,fullText); } else { QString s = item->text.mid(0,item->start-1); QString text = QString("%1 %2: %3").arg(tr("Line")).arg(item->line) .arg(s); painter->drawText(x,y,text); x+=metrics.horizontalAdvance(text); QFont font = option.font; font.setBold(true); text=item->text.mid(item->start-1,item->len); metrics = QFontMetrics(font); int width = metrics.horizontalAdvance(text); QFont oldFont = painter->font(); QPen oldPen = painter->pen(); painter->setPen(qApp->palette().color(QPalette::ColorRole::HighlightedText)); painter->setFont(font); QRect rect = textRect; rect.setLeft(x); rect.setWidth(width); painter->fillRect(rect,qApp->palette().color(QPalette::ColorRole::Highlight)); painter->drawText(x,y,text); metrics = QFontMetrics(font); x+=width; painter->setFont(oldFont); painter->setPen(oldPen); text = item->text.mid(item->start-1+item->len); painter->drawText(x,y,text); } }