Kate
expandingwidgetmodel.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries and the Kate part. 00002 * 00003 * Copyright (C) 2007 David Nolden <david.nolden.kdevelop@art-master.de> 00004 * 00005 * This library is free software; you can redistribute it and/or 00006 * modify it under the terms of the GNU Library General Public 00007 * License as published by the Free Software Foundation; either 00008 * version 2 of the License, or (at your option) any later version. 00009 * 00010 * This library is distributed in the hope that it will be useful, 00011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 * Library General Public License for more details. 00014 * 00015 * You should have received a copy of the GNU Library General Public License 00016 * along with this library; see the file COPYING.LIB. If not, write to 00017 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00018 * Boston, MA 02110-1301, USA. 00019 */ 00020 00021 #include "expandingwidgetmodel.h" 00022 00023 #include <QTreeView> 00024 #include <QModelIndex> 00025 #include <QBrush> 00026 00027 #include <ktexteditor/codecompletionmodel.h> 00028 #include <kiconloader.h> 00029 #include <ktextedit.h> 00030 #include "kcolorutils.h" 00031 00032 #include "expandingdelegate.h" 00033 #include <qapplication.h> 00034 00035 QIcon ExpandingWidgetModel::m_expandedIcon; 00036 QIcon ExpandingWidgetModel::m_collapsedIcon; 00037 00038 using namespace KTextEditor; 00039 00040 inline QModelIndex firstColumn( const QModelIndex& index ) { 00041 return index.sibling(index.row(), 0); 00042 } 00043 00044 ExpandingWidgetModel::ExpandingWidgetModel( QWidget* parent ) : 00045 QAbstractTableModel(parent) 00046 { 00047 } 00048 00049 ExpandingWidgetModel::~ExpandingWidgetModel() { 00050 clearExpanding(); 00051 } 00052 00053 static QColor doAlternate(QColor color) { 00054 QColor background = QApplication::palette().background().color(); 00055 return KColorUtils::mix(color, background, 0.15); 00056 } 00057 00058 uint ExpandingWidgetModel::matchColor(const QModelIndex& index) const { 00059 00060 int matchQuality = contextMatchQuality( index.sibling(index.row(), 0) ); 00061 00062 if( matchQuality > 0 ) 00063 { 00064 bool alternate = index.row() & 1; 00065 00066 QColor badMatchColor(0xff00aa44); //Blue-ish green 00067 QColor goodMatchColor(0xff00ff00); //Green 00068 00069 QColor background = treeView()->palette().light().color(); 00070 00071 QColor totalColor = KColorUtils::mix(badMatchColor, goodMatchColor, ((float)matchQuality)/10.0); 00072 00073 if(alternate) 00074 totalColor = doAlternate(totalColor); 00075 00076 const float dynamicTint = 0.2; 00077 const float minimumTint = 0.2; 00078 double tintStrength = (dynamicTint*matchQuality)/10; 00079 if(tintStrength) 00080 tintStrength += minimumTint; //Some minimum tinting strength, else it's not visible any more 00081 00082 return KColorUtils::tint(background, totalColor, tintStrength ).rgb(); 00083 }else{ 00084 return 0; 00085 } 00086 } 00087 00088 QVariant ExpandingWidgetModel::data( const QModelIndex & index, int role ) const 00089 { 00090 switch( role ) { 00091 case Qt::BackgroundRole: 00092 { 00093 if( index.column() == 0 ) { 00094 //Highlight by match-quality 00095 uint color = matchColor(index); 00096 if( color ) 00097 return QBrush( color ); 00098 } 00099 //Use a special background-color for expanded items 00100 if( isExpanded(index) ) { 00101 if( index.row() & 1 ) { 00102 return doAlternate(treeView()->palette().toolTipBase().color()); 00103 } else { 00104 return treeView()->palette().toolTipBase(); 00105 } 00106 } 00107 } 00108 } 00109 return QVariant(); 00110 } 00111 00112 void ExpandingWidgetModel::clearMatchQualities() { 00113 m_contextMatchQualities.clear(); 00114 } 00115 00116 QModelIndex ExpandingWidgetModel::partiallyExpandedRow() const { 00117 if( m_partiallyExpanded.isEmpty() ) 00118 return QModelIndex(); 00119 else 00120 return m_partiallyExpanded.constBegin().key(); 00121 } 00122 00123 void ExpandingWidgetModel::clearExpanding() { 00124 00125 clearMatchQualities(); 00126 QMap<QModelIndex,ExpandingWidgetModel::ExpandingType> oldExpandState = m_expandState; 00127 foreach( const QPointer<QWidget> &widget, m_expandingWidgets ) 00128 delete widget; 00129 m_expandingWidgets.clear(); 00130 m_expandState.clear(); 00131 m_partiallyExpanded.clear(); 00132 00133 for( QMap<QModelIndex, ExpandingWidgetModel::ExpandingType>::const_iterator it = oldExpandState.constBegin(); it != oldExpandState.constEnd(); ++it ) 00134 if(it.value() == Expanded) 00135 emit dataChanged(it.key(), it.key()); 00136 } 00137 00138 ExpandingWidgetModel::ExpansionType ExpandingWidgetModel::isPartiallyExpanded(const QModelIndex& index) const { 00139 if( m_partiallyExpanded.contains(firstColumn(index)) ) 00140 return m_partiallyExpanded[firstColumn(index)]; 00141 else 00142 return NotExpanded; 00143 } 00144 00145 void ExpandingWidgetModel::partiallyUnExpand(const QModelIndex& idx_) 00146 { 00147 QModelIndex index( firstColumn(idx_) ); 00148 m_partiallyExpanded.remove(index); 00149 m_partiallyExpanded.remove(idx_); 00150 } 00151 00152 int ExpandingWidgetModel::partiallyExpandWidgetHeight() const { 00153 return 60; 00154 } 00155 00156 void ExpandingWidgetModel::rowSelected(const QModelIndex& idx_) 00157 { 00158 QModelIndex idx( firstColumn(idx_) ); 00159 if( !m_partiallyExpanded.contains( idx ) ) 00160 { 00161 QModelIndex oldIndex = partiallyExpandedRow(); 00162 //Unexpand the previous partially expanded row 00163 if( !m_partiallyExpanded.isEmpty() ) 00164 { 00165 while( !m_partiallyExpanded.isEmpty() ) 00166 m_partiallyExpanded.erase(m_partiallyExpanded.begin()); 00167 //partiallyUnExpand( m_partiallyExpanded.begin().key() ); 00168 } 00169 //Notify the underlying models that the item was selected, and eventually get back the text for the expanding widget. 00170 if( !idx.isValid() ) { 00171 //All items have been unselected 00172 if( oldIndex.isValid() ) 00173 emit dataChanged(oldIndex, oldIndex); 00174 } else { 00175 QVariant variant = data(idx, CodeCompletionModel::ItemSelected); 00176 00177 if( !isExpanded(idx) && variant.type() == QVariant::String) { 00178 00179 //Either expand upwards or downwards, choose in a way that 00180 //the visible fields of the new selected entry are not moved. 00181 if( oldIndex.isValid() && (oldIndex < idx || (!(oldIndex < idx) && oldIndex.parent() < idx.parent()) ) ) 00182 m_partiallyExpanded.insert(idx, ExpandUpwards); 00183 else 00184 m_partiallyExpanded.insert(idx, ExpandDownwards); 00185 00186 //Say that one row above until one row below has changed, so no items will need to be moved(the space that is taken from one item is given to the other) 00187 if( oldIndex.isValid() && oldIndex < idx ) { 00188 emit dataChanged(oldIndex, idx); 00189 00190 if( treeView()->verticalScrollMode() == QAbstractItemView::ScrollPerItem ) 00191 { 00192 //Qt fails to correctly scroll in ScrollPerItem mode, so the selected index is completely visible, 00193 //so we do the scrolling by hand. 00194 QRect selectedRect = treeView()->visualRect(idx); 00195 QRect frameRect = treeView()->frameRect(); 00196 00197 if( selectedRect.bottom() > frameRect.bottom() ) { 00198 int diff = selectedRect.bottom() - frameRect.bottom(); 00199 //We need to scroll down 00200 QModelIndex newTopIndex = idx; 00201 00202 QModelIndex nextTopIndex = idx; 00203 QRect nextRect = treeView()->visualRect(nextTopIndex); 00204 while( nextTopIndex.isValid() && nextRect.isValid() && nextRect.top() >= diff ) { 00205 newTopIndex = nextTopIndex; 00206 nextTopIndex = treeView()->indexAbove(nextTopIndex); 00207 if( nextTopIndex.isValid() ) 00208 nextRect = treeView()->visualRect(nextTopIndex); 00209 } 00210 treeView()->scrollTo( newTopIndex, QAbstractItemView::PositionAtTop ); 00211 } 00212 } 00213 00214 //This is needed to keep the item we are expanding completely visible. Qt does not scroll the view to keep the item visible. 00215 //But we must make sure that it isn't too expensive. 00216 //We need to make sure that scrolling is efficient, and the whole content is not repainted. 00217 //Since we are scrolling anyway, we can keep the next line visible, which might be a cool feature. 00218 00219 //Since this also doesn't work smoothly, leave it for now 00220 //treeView()->scrollTo( nextLine, QAbstractItemView::EnsureVisible ); 00221 } else if( oldIndex.isValid() && idx < oldIndex ) { 00222 emit dataChanged(idx, oldIndex); 00223 00224 //For consistency with the down-scrolling, we keep one additional line visible above the current visible. 00225 00226 //Since this also doesn't work smoothly, leave it for now 00227 /* QModelIndex prevLine = idx.sibling(idx.row()-1, idx.column()); 00228 if( prevLine.isValid() ) 00229 treeView()->scrollTo( prevLine );*/ 00230 } else 00231 emit dataChanged(idx, idx); 00232 } else if( oldIndex.isValid() ) { 00233 //We are not partially expanding a new row, but we previously had a partially expanded row. So signalize that it has been unexpanded. 00234 00235 emit dataChanged(oldIndex, oldIndex); 00236 } 00237 } 00238 }else{ 00239 kDebug( 13035 ) << "ExpandingWidgetModel::rowSelected: Row is already partially expanded"; 00240 } 00241 } 00242 00243 QString ExpandingWidgetModel::partialExpandText(const QModelIndex& idx) const { 00244 if( !idx.isValid() ) 00245 return QString(); 00246 00247 return data(firstColumn(idx), CodeCompletionModel::ItemSelected).toString(); 00248 } 00249 00250 QRect ExpandingWidgetModel::partialExpandRect(const QModelIndex& idx_) const 00251 { 00252 QModelIndex idx(firstColumn(idx_)); 00253 00254 if( !idx.isValid() ) 00255 return QRect(); 00256 00257 ExpansionType expansion = ExpandDownwards; 00258 00259 if( m_partiallyExpanded.find(idx) != m_partiallyExpanded.constEnd() ) 00260 expansion = m_partiallyExpanded[idx]; 00261 00262 //Get the whole rectangle of the row: 00263 QModelIndex rightMostIndex = idx; 00264 QModelIndex tempIndex = idx; 00265 while( (tempIndex = rightMostIndex.sibling(rightMostIndex.row(), rightMostIndex.column()+1)).isValid() ) 00266 rightMostIndex = tempIndex; 00267 00268 QRect rect = treeView()->visualRect(idx); 00269 QRect rightMostRect = treeView()->visualRect(rightMostIndex); 00270 00271 rect.setLeft( rect.left() + 20 ); 00272 rect.setRight( rightMostRect.right() - 5 ); 00273 00274 //These offsets must match exactly those used in ExpandingDelegate::sizeHint() 00275 int top = rect.top() + 5; 00276 int bottom = rightMostRect.bottom() - 5 ; 00277 00278 if( expansion == ExpandDownwards ) 00279 top += basicRowHeight(idx); 00280 else 00281 bottom -= basicRowHeight(idx); 00282 00283 rect.setTop( top ); 00284 rect.setBottom( bottom ); 00285 00286 return rect; 00287 } 00288 00289 bool ExpandingWidgetModel::isExpandable(const QModelIndex& idx_) const 00290 { 00291 QModelIndex idx(firstColumn(idx_)); 00292 00293 if( !m_expandState.contains(idx) ) 00294 { 00295 m_expandState.insert(idx, NotExpandable); 00296 QVariant v = data(idx, CodeCompletionModel::IsExpandable); 00297 if( v.canConvert<bool>() && v.value<bool>() ) 00298 m_expandState[idx] = Expandable; 00299 } 00300 00301 return m_expandState[idx] != NotExpandable; 00302 } 00303 00304 bool ExpandingWidgetModel::isExpanded(const QModelIndex& idx_) const 00305 { 00306 QModelIndex idx(firstColumn(idx_)); 00307 return m_expandState.contains(idx) && m_expandState[idx] == Expanded; 00308 } 00309 00310 void ExpandingWidgetModel::setExpanded(QModelIndex idx_, bool expanded) 00311 { 00312 QModelIndex idx(firstColumn(idx_)); 00313 00314 //kDebug( 13035 ) << "Setting expand-state of row " << idx.row() << " to " << expanded; 00315 if( !idx.isValid() ) 00316 return; 00317 00318 if( isExpandable(idx) ) { 00319 if( !expanded && m_expandingWidgets.contains(idx) && m_expandingWidgets[idx] ) { 00320 m_expandingWidgets[idx]->hide(); 00321 } 00322 00323 m_expandState[idx] = expanded ? Expanded : Expandable; 00324 00325 if( expanded ) 00326 partiallyUnExpand(idx); 00327 00328 if( expanded && !m_expandingWidgets.contains(idx) ) 00329 { 00330 QVariant v = data(idx, CodeCompletionModel::ExpandingWidget); 00331 00332 if( v.canConvert<QWidget*>() ) { 00333 m_expandingWidgets[idx] = v.value<QWidget*>(); 00334 } else if( v.canConvert<QString>() ) { 00335 //Create a html widget that shows the given string 00336 KTextEdit* edit = new KTextEdit( v.value<QString>() ); 00337 edit->setReadOnly(true); 00338 edit->resize(200, 50); //Make the widget small so it embeds nicely. 00339 m_expandingWidgets[idx] = edit; 00340 } else { 00341 m_expandingWidgets[idx] = 0; 00342 } 00343 } 00344 00345 //Eventually partially expand the row 00346 if( !expanded && firstColumn(treeView()->currentIndex()) == idx && !isPartiallyExpanded(idx) ) 00347 rowSelected(idx); //Partially expand the row. 00348 00349 emit dataChanged(idx, idx); 00350 00351 if(treeView()) 00352 treeView()->scrollTo(idx); 00353 } 00354 } 00355 00356 int ExpandingWidgetModel::basicRowHeight( const QModelIndex& idx_ ) const 00357 { 00358 QModelIndex idx(firstColumn(idx_)); 00359 00360 ExpandingDelegate* delegate = dynamic_cast<ExpandingDelegate*>( treeView()->itemDelegate(idx) ); 00361 if( !delegate || !idx.isValid() ) { 00362 kDebug( 13035 ) << "ExpandingWidgetModel::basicRowHeight: Could not get delegate"; 00363 return 15; 00364 } 00365 return delegate->basicSizeHint( idx ).height(); 00366 } 00367 00368 00369 void ExpandingWidgetModel::placeExpandingWidget(const QModelIndex& idx_) 00370 { 00371 QModelIndex idx(firstColumn(idx_)); 00372 00373 QWidget* w = 0; 00374 if( m_expandingWidgets.contains(idx) ) 00375 w = m_expandingWidgets[idx]; 00376 00377 if( w && isExpanded(idx) ) { 00378 if( !idx.isValid() ) 00379 return; 00380 00381 QRect rect = treeView()->visualRect(idx); 00382 00383 if( !rect.isValid() || rect.bottom() < 0 || rect.top() >= treeView()->height() ) { 00384 //The item is currently not visible 00385 w->hide(); 00386 return; 00387 } 00388 00389 QModelIndex rightMostIndex = idx; 00390 QModelIndex tempIndex = idx; 00391 while( (tempIndex = rightMostIndex.sibling(rightMostIndex.row(), rightMostIndex.column()+1)).isValid() ) 00392 rightMostIndex = tempIndex; 00393 00394 QRect rightMostRect = treeView()->visualRect(rightMostIndex); 00395 00396 //Find out the basic height of the row 00397 rect.setLeft( rect.left() + 20 ); 00398 rect.setRight( rightMostRect.right() - 5 ); 00399 00400 //These offsets must match exactly those used in KateCompletionDeleage::sizeHint() 00401 rect.setTop( rect.top() + basicRowHeight(idx) + 5 ); 00402 rect.setHeight( w->height() ); 00403 00404 if( w->parent() != treeView()->viewport() || w->geometry() != rect || !w->isVisible() ) { 00405 w->setParent( treeView()->viewport() ); 00406 00407 w->setGeometry(rect); 00408 w->show(); 00409 } 00410 } 00411 } 00412 00413 void ExpandingWidgetModel::placeExpandingWidgets() { 00414 for( QMap<QModelIndex, QPointer<QWidget> >::const_iterator it = m_expandingWidgets.constBegin(); it != m_expandingWidgets.constEnd(); ++it ) { 00415 placeExpandingWidget(it.key()); 00416 } 00417 } 00418 00419 int ExpandingWidgetModel::expandingWidgetsHeight() const 00420 { 00421 int sum = 0; 00422 for( QMap<QModelIndex, QPointer<QWidget> >::const_iterator it = m_expandingWidgets.constBegin(); it != m_expandingWidgets.constEnd(); ++it ) { 00423 if( isExpanded(it.key() ) && (*it) ) 00424 sum += (*it)->height(); 00425 } 00426 return sum; 00427 } 00428 00429 00430 QWidget* ExpandingWidgetModel::expandingWidget(const QModelIndex& idx_) const 00431 { 00432 QModelIndex idx(firstColumn(idx_)); 00433 00434 if( m_expandingWidgets.contains(idx) ) 00435 return m_expandingWidgets[idx]; 00436 else 00437 return 0; 00438 } 00439 00440 void ExpandingWidgetModel::cacheIcons() const { 00441 if( m_expandedIcon.isNull() ) 00442 m_expandedIcon = KIconLoader::global()->loadIcon("arrow-down", KIconLoader::Small, 10); 00443 00444 if( m_collapsedIcon.isNull() ) 00445 m_collapsedIcon = KIconLoader::global()->loadIcon("arrow-right", KIconLoader::Small, 10); 00446 } 00447 00448 QList<QVariant> mergeCustomHighlighting( int leftSize, const QList<QVariant>& left, int rightSize, const QList<QVariant>& right ) 00449 { 00450 QList<QVariant> ret = left; 00451 if( left.isEmpty() ) { 00452 ret << QVariant(0); 00453 ret << QVariant(leftSize); 00454 ret << QTextFormat(QTextFormat::CharFormat); 00455 } 00456 00457 if( right.isEmpty() ) { 00458 ret << QVariant(leftSize); 00459 ret << QVariant(rightSize); 00460 ret << QTextFormat(QTextFormat::CharFormat); 00461 } else { 00462 QList<QVariant>::const_iterator it = right.constBegin(); 00463 while( it != right.constEnd() ) { 00464 { 00465 QList<QVariant>::const_iterator testIt = it; 00466 for(int a = 0; a < 2; a++) { 00467 ++testIt; 00468 if(testIt == right.constEnd()) { 00469 kWarning() << "Length of input is not multiple of 3"; 00470 break; 00471 } 00472 } 00473 } 00474 00475 ret << QVariant( (*it).toInt() + leftSize ); 00476 ++it; 00477 ret << QVariant( (*it).toInt() ); 00478 ++it; 00479 ret << *it; 00480 if(!(*it).value<QTextFormat>().isValid()) 00481 kDebug( 13035 ) << "Text-format is invalid"; 00482 ++it; 00483 } 00484 } 00485 return ret; 00486 } 00487 00488 //It is assumed that between each two strings, one space is inserted 00489 QList<QVariant> mergeCustomHighlighting( QStringList strings, QList<QVariantList> highlights, int grapBetweenStrings ) 00490 { 00491 if(strings.isEmpty()) { 00492 kWarning() << "List of strings is empty"; 00493 return QList<QVariant>(); 00494 } 00495 00496 if(highlights.isEmpty()) { 00497 kWarning() << "List of highlightings is empty"; 00498 return QList<QVariant>(); 00499 } 00500 00501 if(strings.count() != highlights.count()) { 00502 kWarning() << "Length of string-list is " << strings.count() << " while count of highlightings is " << highlights.count() << ", should be same"; 00503 return QList<QVariant>(); 00504 } 00505 00506 //Merge them together 00507 QString totalString = strings[0]; 00508 QVariantList totalHighlighting = highlights[0]; 00509 00510 strings.pop_front(); 00511 highlights.pop_front(); 00512 00513 while( !strings.isEmpty() ) { 00514 totalHighlighting = mergeCustomHighlighting( totalString.length(), totalHighlighting, strings[0].length(), highlights[0] ); 00515 totalString += strings[0]; 00516 00517 for(int a = 0; a < grapBetweenStrings; a++) 00518 totalString += ' '; 00519 00520 strings.pop_front(); 00521 highlights.pop_front(); 00522 00523 } 00524 //Combine the custom-highlightings 00525 return totalHighlighting; 00526 } 00527 #include "expandingwidgetmodel.moc" 00528
KDE 4.6 API Reference