Branch data Line data Source code
1 : : /*************************************************************************** 2 : : qgslocatormodel.cpp 3 : : -------------------- 4 : : begin : May 2017 5 : : copyright : (C) 2017 by Nyall Dawson 6 : : email : nyall dot dawson at gmail dot com 7 : : ***************************************************************************/ 8 : : 9 : : /*************************************************************************** 10 : : * * 11 : : * This program is free software; you can redistribute it and/or modify * 12 : : * it under the terms of the GNU General Public License as published by * 13 : : * the Free Software Foundation; either version 2 of the License, or * 14 : : * (at your option) any later version. * 15 : : * * 16 : : ***************************************************************************/ 17 : : 18 : : #include <QFont> 19 : : 20 : : #include "qgslocatormodel.h" 21 : : #include "qgslocator.h" 22 : : #include "qgsapplication.h" 23 : : #include "qgslogger.h" 24 : : 25 : : 26 : : // 27 : : // QgsLocatorModel 28 : : // 29 : : 30 : 0 : QgsLocatorModel::QgsLocatorModel( QObject *parent ) 31 : 0 : : QAbstractTableModel( parent ) 32 : 0 : { 33 : 0 : mDeferredClearTimer.setInterval( 100 ); 34 : 0 : mDeferredClearTimer.setSingleShot( true ); 35 : 0 : connect( &mDeferredClearTimer, &QTimer::timeout, this, &QgsLocatorModel::clear ); 36 : 0 : } 37 : : 38 : 0 : void QgsLocatorModel::clear() 39 : : { 40 : 0 : mDeferredClearTimer.stop(); 41 : 0 : mDeferredClear = false; 42 : : 43 : 0 : beginResetModel(); 44 : 0 : mResults.clear(); 45 : 0 : mFoundResultsFromFilterNames.clear(); 46 : 0 : mFoundResultsFilterGroups.clear(); 47 : 0 : endResetModel(); 48 : 0 : } 49 : : 50 : 0 : void QgsLocatorModel::deferredClear() 51 : : { 52 : 0 : mDeferredClear = true; 53 : 0 : mDeferredClearTimer.start(); 54 : 0 : } 55 : : 56 : 0 : int QgsLocatorModel::rowCount( const QModelIndex & ) const 57 : : { 58 : 0 : return mResults.size(); 59 : : } 60 : : 61 : 0 : int QgsLocatorModel::columnCount( const QModelIndex & ) const 62 : : { 63 : 0 : return 2; 64 : : } 65 : : 66 : 0 : QVariant QgsLocatorModel::data( const QModelIndex &index, int role ) const 67 : : { 68 : 0 : if ( !index.isValid() || index.row() < 0 || index.column() < 0 || 69 : 0 : index.row() >= rowCount( QModelIndex() ) || index.column() >= columnCount( QModelIndex() ) ) 70 : 0 : return QVariant(); 71 : : 72 : 0 : switch ( role ) 73 : : { 74 : : case Qt::DisplayRole: 75 : : case Qt::EditRole: 76 : : { 77 : 0 : switch ( index.column() ) 78 : : { 79 : : case Name: 80 : 0 : if ( !mResults.at( index.row() ).filter ) 81 : 0 : return mResults.at( index.row() ).result.displayString; 82 : 0 : else if ( mResults.at( index.row() ).filter && mResults.at( index.row() ).groupSorting == 0 ) 83 : 0 : return mResults.at( index.row() ).filterTitle; 84 : : else 85 : : { 86 : 0 : QString groupTitle = mResults.at( index.row() ).groupTitle; 87 : 0 : groupTitle.prepend( " " ); 88 : 0 : return groupTitle; 89 : 0 : } 90 : : case Description: 91 : 0 : if ( !mResults.at( index.row() ).filter ) 92 : 0 : return mResults.at( index.row() ).result.description; 93 : : else 94 : 0 : return QVariant(); 95 : : } 96 : 0 : break; 97 : : } 98 : : 99 : : case Qt::FontRole: 100 : 0 : if ( index.column() == Name && !mResults.at( index.row() ).groupTitle.isEmpty() ) 101 : : { 102 : 0 : QFont font; 103 : 0 : font.setItalic( true ); 104 : 0 : return font; 105 : 0 : } 106 : : else 107 : : { 108 : 0 : return QVariant(); 109 : : } 110 : : break; 111 : : 112 : : case Qt::DecorationRole: 113 : 0 : switch ( index.column() ) 114 : 0 : { 115 : : case Name: 116 : 0 : if ( !mResults.at( index.row() ).filter ) 117 : : { 118 : 0 : QIcon icon = mResults.at( index.row() ).result.icon; 119 : 0 : if ( !icon.isNull() ) 120 : 0 : return icon; 121 : 0 : return QgsApplication::getThemeIcon( QStringLiteral( "/search.svg" ) ); 122 : 0 : } 123 : : else 124 : 0 : return QVariant(); 125 : : case Description: 126 : 0 : return QVariant(); 127 : : } 128 : 0 : break; 129 : : 130 : : case ResultDataRole: 131 : 0 : if ( !mResults.at( index.row() ).filter ) 132 : 0 : return QVariant::fromValue( mResults.at( index.row() ).result ); 133 : : else 134 : 0 : return QVariant(); 135 : : 136 : : case ResultTypeRole: 137 : : // 0 for filter title, the group otherwise, 9999 if no group 138 : 0 : return mResults.at( index.row() ).groupSorting; 139 : : 140 : : case ResultScoreRole: 141 : 0 : if ( mResults.at( index.row() ).filter ) 142 : 0 : return 0; 143 : : else 144 : 0 : return ( mResults.at( index.row() ).result.score ); 145 : : 146 : : case ResultFilterPriorityRole: 147 : 0 : if ( !mResults.at( index.row() ).filter ) 148 : 0 : return mResults.at( index.row() ).result.filter->priority(); 149 : : else 150 : 0 : return mResults.at( index.row() ).filter->priority(); 151 : : 152 : : case ResultFilterNameRole: 153 : 0 : if ( !mResults.at( index.row() ).filter ) 154 : 0 : return mResults.at( index.row() ).result.filter->displayName(); 155 : : else 156 : 0 : return mResults.at( index.row() ).filterTitle; 157 : : 158 : : case ResultFilterGroupSortingRole: 159 : 0 : if ( mResults.at( index.row() ).groupTitle.isEmpty() ) 160 : 0 : return 1; 161 : : else 162 : 0 : return 0; 163 : : 164 : : case ResultActionsRole: 165 : 0 : return QVariant::fromValue( mResults.at( index.row() ).result.actions ); 166 : : } 167 : : 168 : 0 : return QVariant(); 169 : 0 : } 170 : : 171 : 0 : Qt::ItemFlags QgsLocatorModel::flags( const QModelIndex &index ) const 172 : : { 173 : 0 : if ( !index.isValid() || index.row() < 0 || index.column() < 0 || 174 : 0 : index.row() >= rowCount( QModelIndex() ) || index.column() >= columnCount( QModelIndex() ) ) 175 : 0 : return QAbstractTableModel::flags( index ); 176 : : 177 : 0 : Qt::ItemFlags flags = QAbstractTableModel::flags( index ); 178 : 0 : if ( mResults.at( index.row() ).filter ) 179 : : { 180 : 0 : flags = flags & ~( Qt::ItemIsSelectable | Qt::ItemIsEnabled ); 181 : 0 : } 182 : 0 : return flags; 183 : 0 : } 184 : : 185 : 0 : QHash<int, QByteArray> QgsLocatorModel::roleNames() const 186 : : { 187 : 0 : QHash<int, QByteArray> roles; 188 : 0 : roles[ResultDataRole] = "ResultData"; 189 : 0 : roles[ResultTypeRole] = "ResultType"; 190 : 0 : roles[ResultFilterPriorityRole] = "ResultFilterPriority"; 191 : 0 : roles[ResultScoreRole] = "ResultScore"; 192 : 0 : roles[ResultFilterNameRole] = "ResultFilterName"; 193 : 0 : roles[ResultFilterGroupSortingRole] = "ResultFilterGroupSorting"; 194 : 0 : roles[ResultActionsRole] = "ResultContextMenuActions"; 195 : 0 : roles[Qt::DisplayRole] = "Text"; 196 : 0 : return roles; 197 : 0 : } 198 : : 199 : 0 : void QgsLocatorModel::addResult( const QgsLocatorResult &result ) 200 : : { 201 : 0 : mDeferredClearTimer.stop(); 202 : 0 : if ( mDeferredClear ) 203 : : { 204 : 0 : mFoundResultsFromFilterNames.clear(); 205 : 0 : mFoundResultsFilterGroups.clear(); 206 : 0 : } 207 : : 208 : 0 : int pos = mResults.size(); 209 : 0 : bool addingFilter = !result.filter->displayName().isEmpty() && !mFoundResultsFromFilterNames.contains( result.filter->name() ); 210 : 0 : if ( addingFilter ) 211 : 0 : mFoundResultsFromFilterNames << result.filter->name(); 212 : : 213 : 0 : bool addingGroup = !result.group.isEmpty() && ( !mFoundResultsFilterGroups.contains( result.filter ) 214 : 0 : || !mFoundResultsFilterGroups.value( result.filter ).contains( result.group ) ); 215 : 0 : if ( addingGroup ) 216 : : { 217 : 0 : if ( !mFoundResultsFilterGroups.contains( result.filter ) ) 218 : 0 : mFoundResultsFilterGroups[result.filter] = QStringList(); 219 : 0 : mFoundResultsFilterGroups[result.filter] << result.group ; 220 : 0 : } 221 : 0 : if ( mDeferredClear ) 222 : : { 223 : 0 : beginResetModel(); 224 : 0 : mResults.clear(); 225 : 0 : } 226 : : else 227 : 0 : beginInsertRows( QModelIndex(), pos, pos + ( static_cast<int>( addingFilter ) + static_cast<int>( addingGroup ) ) ); 228 : : 229 : 0 : if ( addingFilter ) 230 : : { 231 : 0 : Entry entry; 232 : 0 : entry.filterTitle = result.filter->displayName(); 233 : 0 : entry.filter = result.filter; 234 : 0 : mResults << entry; 235 : 0 : } 236 : 0 : if ( addingGroup ) 237 : : { 238 : 0 : Entry entry; 239 : 0 : entry.filterTitle = result.filter->displayName(); 240 : 0 : entry.groupTitle = result.group; 241 : : // the sorting of groups will be achieved by order of adding groups 242 : : // this could be customized by adding the extra info to QgsLocatorResult 243 : 0 : entry.groupSorting = mFoundResultsFilterGroups[result.filter].count(); 244 : 0 : entry.filter = result.filter; 245 : 0 : mResults << entry; 246 : 0 : } 247 : 0 : Entry entry; 248 : 0 : entry.result = result; 249 : : // keep the group title empty to allow differecing group title from results 250 : 0 : entry.groupSorting = result.group.isEmpty() ? NoGroup : mFoundResultsFilterGroups[result.filter].indexOf( result.group ) + 1; 251 : 0 : mResults << entry; 252 : : 253 : 0 : if ( mDeferredClear ) 254 : 0 : endResetModel(); 255 : : else 256 : 0 : endInsertRows(); 257 : : 258 : 0 : mDeferredClear = false; 259 : 0 : } 260 : : 261 : : 262 : : // 263 : : // QgsLocatorAutomaticModel 264 : : // 265 : : 266 : 0 : QgsLocatorAutomaticModel::QgsLocatorAutomaticModel( QgsLocator *locator ) 267 : 0 : : QgsLocatorModel( locator ) 268 : 0 : , mLocator( locator ) 269 : 0 : { 270 : : Q_ASSERT( mLocator ); 271 : 0 : connect( mLocator, &QgsLocator::foundResult, this, &QgsLocatorAutomaticModel::addResult ); 272 : 0 : connect( mLocator, &QgsLocator::finished, this, &QgsLocatorAutomaticModel::searchFinished ); 273 : 0 : } 274 : : 275 : 0 : QgsLocator *QgsLocatorAutomaticModel::locator() 276 : : { 277 : 0 : return mLocator; 278 : : } 279 : : 280 : 0 : void QgsLocatorAutomaticModel::search( const QString &string ) 281 : : { 282 : 0 : if ( mLocator->isRunning() ) 283 : : { 284 : : // can't do anything while a query is running, and can't block 285 : : // here waiting for the current query to cancel 286 : : // so we queue up this string until cancel has happened 287 : 0 : mLocator->cancelWithoutBlocking(); 288 : 0 : mNextRequestedString = string; 289 : 0 : mHasQueuedRequest = true; 290 : 0 : return; 291 : : } 292 : : else 293 : : { 294 : 0 : deferredClear(); 295 : 0 : mLocator->fetchResults( string, createContext() ); 296 : : } 297 : 0 : } 298 : : 299 : 0 : QgsLocatorContext QgsLocatorAutomaticModel::createContext() 300 : : { 301 : 0 : return QgsLocatorContext(); 302 : : } 303 : : 304 : 0 : void QgsLocatorAutomaticModel::searchFinished() 305 : : { 306 : 0 : if ( mHasQueuedRequest ) 307 : : { 308 : : // a queued request was waiting for this - run the queued search now 309 : 0 : QString nextSearch = mNextRequestedString; 310 : 0 : mNextRequestedString.clear(); 311 : 0 : mHasQueuedRequest = false; 312 : 0 : search( nextSearch ); 313 : 0 : } 314 : 0 : } 315 : : 316 : : 317 : : 318 : : 319 : : 320 : : // 321 : : // QgsLocatorProxyModel 322 : : // 323 : : 324 : 0 : QgsLocatorProxyModel::QgsLocatorProxyModel( QObject *parent ) 325 : 0 : : QSortFilterProxyModel( parent ) 326 : 0 : { 327 : 0 : setDynamicSortFilter( true ); 328 : 0 : setSortLocaleAware( true ); 329 : 0 : setFilterCaseSensitivity( Qt::CaseInsensitive ); 330 : 0 : sort( 0 ); 331 : 0 : } 332 : : 333 : 0 : bool QgsLocatorProxyModel::lessThan( const QModelIndex &left, const QModelIndex &right ) const 334 : : { 335 : : // first go by filter priority 336 : 0 : int leftFilterPriority = sourceModel()->data( left, QgsLocatorModel::ResultFilterPriorityRole ).toInt(); 337 : 0 : int rightFilterPriority = sourceModel()->data( right, QgsLocatorModel::ResultFilterPriorityRole ).toInt(); 338 : 0 : if ( leftFilterPriority != rightFilterPriority ) 339 : 0 : return leftFilterPriority < rightFilterPriority; 340 : : 341 : : // then filter name 342 : 0 : QString leftFilter = sourceModel()->data( left, QgsLocatorModel::ResultFilterNameRole ).toString(); 343 : 0 : QString rightFilter = sourceModel()->data( right, QgsLocatorModel::ResultFilterNameRole ).toString(); 344 : 0 : if ( leftFilter != rightFilter ) 345 : 0 : return QString::localeAwareCompare( leftFilter, rightFilter ) < 0; 346 : : 347 : : // then make sure filter title or group appears before filter's results 348 : 0 : int leftTypeRole = sourceModel()->data( left, QgsLocatorModel::ResultTypeRole ).toInt(); 349 : 0 : int rightTypeRole = sourceModel()->data( right, QgsLocatorModel::ResultTypeRole ).toInt(); 350 : 0 : if ( leftTypeRole != rightTypeRole ) 351 : 0 : return leftTypeRole < rightTypeRole; 352 : : 353 : : // make sure group title are above 354 : 0 : int leftGroupRole = sourceModel()->data( left, QgsLocatorModel::ResultFilterGroupSortingRole ).toInt(); 355 : 0 : int rightGroupRole = sourceModel()->data( right, QgsLocatorModel::ResultFilterGroupSortingRole ).toInt(); 356 : 0 : if ( leftGroupRole != rightGroupRole ) 357 : 0 : return leftGroupRole < rightGroupRole; 358 : : 359 : : // sort filter's results by score 360 : 0 : double leftScore = sourceModel()->data( left, QgsLocatorModel::ResultScoreRole ).toDouble(); 361 : 0 : double rightScore = sourceModel()->data( right, QgsLocatorModel::ResultScoreRole ).toDouble(); 362 : 0 : if ( !qgsDoubleNear( leftScore, rightScore ) ) 363 : 0 : return leftScore > rightScore; 364 : : 365 : : // lastly sort filter's results by string 366 : 0 : leftFilter = sourceModel()->data( left, Qt::DisplayRole ).toString(); 367 : 0 : rightFilter = sourceModel()->data( right, Qt::DisplayRole ).toString(); 368 : 0 : return QString::localeAwareCompare( leftFilter, rightFilter ) < 0; 369 : 0 : } 370 : : 371 : :