00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019 #include "expandingwidgetmodel.h"
00020
00021 #include <QTreeView>
00022 #include <QModelIndex>
00023 #include <QBrush>
00024
00025 #include <ktexteditor/codecompletionmodel.h>
00026 #include <kiconloader.h>
00027 #include <ktextedit.h>
00028 #include "kcolorutils.h"
00029
00030 #include "expandingdelegate.h"
00031 #include <qapplication.h>
00032
00033 QIcon ExpandingWidgetModel::m_expandedIcon;
00034 QIcon ExpandingWidgetModel::m_collapsedIcon;
00035
00036 using namespace KTextEditor;
00037
00038 inline QModelIndex firstColumn( const QModelIndex& index ) {
00039 return index.sibling(index.row(), 0);
00040 }
00041
00042 ExpandingWidgetModel::ExpandingWidgetModel( QWidget* parent ) :
00043 QAbstractTableModel(parent)
00044 {
00045 }
00046
00047 ExpandingWidgetModel::~ExpandingWidgetModel() {
00048 clearExpanding();
00049 }
00050
00051 static QColor doAlternate(QColor color) {
00052 QColor background = QApplication::palette().background().color();
00053 return KColorUtils::mix(color, background, 0.15);
00054 }
00055
00056 uint ExpandingWidgetModel::matchColor(const QModelIndex& index) const {
00057
00058 int matchQuality = contextMatchQuality( index.sibling(index.row(), 0) );
00059
00060 if( matchQuality > 0 )
00061 {
00062 bool alternate = index.row() & 1;
00063
00064 QColor badMatchColor(0xff00aa44);
00065 QColor goodMatchColor(0xff00ff00);
00066
00067 QColor background = treeView()->palette().light().color();
00068
00069 QColor totalColor = KColorUtils::mix(badMatchColor, goodMatchColor, ((float)matchQuality)/10.0);
00070
00071 if(alternate)
00072 totalColor = doAlternate(totalColor);
00073
00074 const float dynamicTint = 0.2;
00075 const float minimumTint = 0.2;
00076 double tintStrength = (dynamicTint*matchQuality)/10;
00077 if(tintStrength)
00078 tintStrength += minimumTint;
00079
00080 return KColorUtils::tint(background, totalColor, tintStrength ).rgb();
00081 }else{
00082 return 0;
00083 }
00084 }
00085
00086 QVariant ExpandingWidgetModel::data( const QModelIndex & index, int role ) const
00087 {
00088 switch( role ) {
00089 case Qt::BackgroundRole:
00090 {
00091 if( index.column() == 0 ) {
00092
00093 uint color = matchColor(index);
00094 if( color )
00095 return QBrush( color );
00096 }
00097
00098 if( isExpanded(index) ) {
00099 if( index.row() & 1 ) {
00100 return doAlternate(treeView()->palette().toolTipBase().color());
00101 } else {
00102 return treeView()->palette().toolTipBase();
00103 }
00104 }
00105 }
00106 }
00107 return QVariant();
00108 }
00109
00110 void ExpandingWidgetModel::clearMatchQualities() {
00111 m_contextMatchQualities.clear();
00112 }
00113
00114 QModelIndex ExpandingWidgetModel::partiallyExpandedRow() const {
00115 if( m_partiallyExpanded.isEmpty() )
00116 return QModelIndex();
00117 else
00118 return m_partiallyExpanded.constBegin().key();
00119 }
00120
00121 void ExpandingWidgetModel::clearExpanding() {
00122
00123 clearMatchQualities();
00124 QMap<QModelIndex,ExpandingWidgetModel::ExpandingType> oldExpandState = m_expandState;
00125 foreach( QPointer<QWidget> widget, m_expandingWidgets )
00126 delete widget;
00127 m_expandingWidgets.clear();
00128 m_expandState.clear();
00129 m_partiallyExpanded.clear();
00130
00131 for( QMap<QModelIndex, ExpandingWidgetModel::ExpandingType>::const_iterator it = oldExpandState.constBegin(); it != oldExpandState.constEnd(); ++it )
00132 if(it.value() == Expanded)
00133 emit dataChanged(it.key(), it.key());
00134 }
00135
00136 ExpandingWidgetModel::ExpansionType ExpandingWidgetModel::isPartiallyExpanded(const QModelIndex& index) const {
00137 if( m_partiallyExpanded.contains(firstColumn(index)) )
00138 return m_partiallyExpanded[firstColumn(index)];
00139 else
00140 return NotExpanded;
00141 }
00142
00143 void ExpandingWidgetModel::partiallyUnExpand(const QModelIndex& idx_)
00144 {
00145 QModelIndex index( firstColumn(idx_) );
00146 m_partiallyExpanded.remove(index);
00147 m_partiallyExpanded.remove(idx_);
00148 }
00149
00150 int ExpandingWidgetModel::partiallyExpandWidgetHeight() const {
00151 return 60;
00152 }
00153
00154 void ExpandingWidgetModel::rowSelected(const QModelIndex& idx_)
00155 {
00156 QModelIndex idx( firstColumn(idx_) );
00157 if( !m_partiallyExpanded.contains( idx ) )
00158 {
00159 QModelIndex oldIndex = partiallyExpandedRow();
00160
00161 if( !m_partiallyExpanded.isEmpty() )
00162 {
00163 while( !m_partiallyExpanded.isEmpty() )
00164 m_partiallyExpanded.erase(m_partiallyExpanded.begin());
00165
00166 }
00167
00168 if( !idx.isValid() ) {
00169
00170 if( oldIndex.isValid() )
00171 emit dataChanged(oldIndex, oldIndex);
00172 } else {
00173 QVariant variant = data(idx, CodeCompletionModel::ItemSelected);
00174
00175 if( !isExpanded(idx) && variant.type() == QVariant::String) {
00176
00177
00178
00179 if( oldIndex.isValid() && (oldIndex < idx || (!(oldIndex < idx) && oldIndex.parent() < idx.parent()) ) )
00180 m_partiallyExpanded.insert(idx, ExpandUpwards);
00181 else
00182 m_partiallyExpanded.insert(idx, ExpandDownwards);
00183
00184
00185 if( oldIndex.isValid() && oldIndex < idx ) {
00186 emit dataChanged(oldIndex, idx);
00187
00188 if( treeView()->verticalScrollMode() == QAbstractItemView::ScrollPerItem )
00189 {
00190
00191
00192 QRect selectedRect = treeView()->visualRect(idx);
00193 QRect frameRect = treeView()->frameRect();
00194
00195 if( selectedRect.bottom() > frameRect.bottom() ) {
00196 int diff = selectedRect.bottom() - frameRect.bottom();
00197
00198 QModelIndex newTopIndex = idx;
00199
00200 QModelIndex nextTopIndex = idx;
00201 QRect nextRect = treeView()->visualRect(nextTopIndex);
00202 while( nextTopIndex.isValid() && nextRect.isValid() && nextRect.top() >= diff ) {
00203 newTopIndex = nextTopIndex;
00204 nextTopIndex = treeView()->indexAbove(nextTopIndex);
00205 if( nextTopIndex.isValid() )
00206 nextRect = treeView()->visualRect(nextTopIndex);
00207 }
00208 treeView()->scrollTo( newTopIndex, QAbstractItemView::PositionAtTop );
00209 }
00210 }
00211
00212
00213
00214
00215
00216
00217
00218
00219 } else if( oldIndex.isValid() && idx < oldIndex ) {
00220 emit dataChanged(idx, oldIndex);
00221
00222
00223
00224
00225
00226
00227
00228 } else
00229 emit dataChanged(idx, idx);
00230 } else if( oldIndex.isValid() ) {
00231
00232
00233 emit dataChanged(oldIndex, oldIndex);
00234 }
00235 }
00236 }else{
00237 kDebug( 13035 ) << "ExpandingWidgetModel::rowSelected: Row is already partially expanded";
00238 }
00239 }
00240
00241 QString ExpandingWidgetModel::partialExpandText(const QModelIndex& idx) const {
00242 if( !idx.isValid() )
00243 return QString();
00244
00245 return data(firstColumn(idx), CodeCompletionModel::ItemSelected).toString();
00246 }
00247
00248 QRect ExpandingWidgetModel::partialExpandRect(const QModelIndex& idx_) const
00249 {
00250 QModelIndex idx(firstColumn(idx_));
00251
00252 if( !idx.isValid() )
00253 return QRect();
00254
00255 ExpansionType expansion = ExpandDownwards;
00256
00257 if( m_partiallyExpanded.find(idx) != m_partiallyExpanded.constEnd() )
00258 expansion = m_partiallyExpanded[idx];
00259
00260
00261 QModelIndex rightMostIndex = idx;
00262 QModelIndex tempIndex = idx;
00263 while( (tempIndex = rightMostIndex.sibling(rightMostIndex.row(), rightMostIndex.column()+1)).isValid() )
00264 rightMostIndex = tempIndex;
00265
00266 QRect rect = treeView()->visualRect(idx);
00267 QRect rightMostRect = treeView()->visualRect(rightMostIndex);
00268
00269 rect.setLeft( rect.left() + 20 );
00270 rect.setRight( rightMostRect.right() - 5 );
00271
00272
00273 int top = rect.top() + 5;
00274 int bottom = rightMostRect.bottom() - 5 ;
00275
00276 if( expansion == ExpandDownwards )
00277 top += basicRowHeight(idx);
00278 else
00279 bottom -= basicRowHeight(idx);
00280
00281 rect.setTop( top );
00282 rect.setBottom( bottom );
00283
00284 return rect;
00285 }
00286
00287 bool ExpandingWidgetModel::isExpandable(const QModelIndex& idx_) const
00288 {
00289 QModelIndex idx(firstColumn(idx_));
00290
00291 if( !m_expandState.contains(idx) )
00292 {
00293 m_expandState.insert(idx, NotExpandable);
00294 QVariant v = data(idx, CodeCompletionModel::IsExpandable);
00295 if( v.canConvert<bool>() && v.value<bool>() )
00296 m_expandState[idx] = Expandable;
00297 }
00298
00299 return m_expandState[idx] != NotExpandable;
00300 }
00301
00302 bool ExpandingWidgetModel::isExpanded(const QModelIndex& idx_) const
00303 {
00304 QModelIndex idx(firstColumn(idx_));
00305 return m_expandState.contains(idx) && m_expandState[idx] == Expanded;
00306 }
00307
00308 void ExpandingWidgetModel::setExpanded(QModelIndex idx_, bool expanded)
00309 {
00310 QModelIndex idx(firstColumn(idx_));
00311
00312
00313 if( !idx.isValid() )
00314 return;
00315
00316 if( isExpandable(idx) ) {
00317 if( !expanded && m_expandingWidgets.contains(idx) && m_expandingWidgets[idx] ) {
00318 m_expandingWidgets[idx]->hide();
00319 }
00320
00321 m_expandState[idx] = expanded ? Expanded : Expandable;
00322
00323 if( expanded )
00324 partiallyUnExpand(idx);
00325
00326 if( expanded && !m_expandingWidgets.contains(idx) )
00327 {
00328 QVariant v = data(idx, CodeCompletionModel::ExpandingWidget);
00329
00330 if( v.canConvert<QWidget*>() ) {
00331 m_expandingWidgets[idx] = v.value<QWidget*>();
00332 } else if( v.canConvert<QString>() ) {
00333
00334 KTextEdit* edit = new KTextEdit( v.value<QString>() );
00335 edit->setReadOnly(true);
00336 edit->resize(200, 50);
00337 m_expandingWidgets[idx] = edit;
00338 } else {
00339 m_expandingWidgets[idx] = 0;
00340 }
00341 }
00342
00343
00344 if( !expanded && firstColumn(treeView()->currentIndex()) == idx && !isPartiallyExpanded(idx) )
00345 rowSelected(idx);
00346
00347 emit dataChanged(idx, idx);
00348
00349 if(treeView())
00350 treeView()->scrollTo(idx);
00351 }
00352 }
00353
00354 int ExpandingWidgetModel::basicRowHeight( const QModelIndex& idx_ ) const
00355 {
00356 QModelIndex idx(firstColumn(idx_));
00357
00358 ExpandingDelegate* delegate = dynamic_cast<ExpandingDelegate*>( treeView()->itemDelegate(idx) );
00359 if( !delegate || !idx.isValid() ) {
00360 kDebug( 13035 ) << "ExpandingWidgetModel::basicRowHeight: Could not get delegate";
00361 return 15;
00362 }
00363 return delegate->basicSizeHint( idx ).height();
00364 }
00365
00366
00367 void ExpandingWidgetModel::placeExpandingWidget(const QModelIndex& idx_)
00368 {
00369 QModelIndex idx(firstColumn(idx_));
00370
00371 QWidget* w = 0;
00372 if( m_expandingWidgets.contains(idx) )
00373 w = m_expandingWidgets[idx];
00374
00375 if( w && isExpanded(idx) ) {
00376 if( !idx.isValid() )
00377 return;
00378
00379 QRect rect = treeView()->visualRect(idx);
00380
00381 if( !rect.isValid() || rect.bottom() < 0 || rect.top() >= treeView()->height() ) {
00382
00383 w->hide();
00384 return;
00385 }
00386
00387 QModelIndex rightMostIndex = idx;
00388 QModelIndex tempIndex = idx;
00389 while( (tempIndex = rightMostIndex.sibling(rightMostIndex.row(), rightMostIndex.column()+1)).isValid() )
00390 rightMostIndex = tempIndex;
00391
00392 QRect rightMostRect = treeView()->visualRect(rightMostIndex);
00393
00394
00395 rect.setLeft( rect.left() + 20 );
00396 rect.setRight( rightMostRect.right() - 5 );
00397
00398
00399 rect.setTop( rect.top() + basicRowHeight(idx) + 5 );
00400 rect.setHeight( w->height() );
00401
00402 if( w->parent() != treeView()->viewport() || w->geometry() != rect || !w->isVisible() ) {
00403 w->setParent( treeView()->viewport() );
00404
00405 w->setGeometry(rect);
00406 w->show();
00407 }
00408 }
00409 }
00410
00411 void ExpandingWidgetModel::placeExpandingWidgets() {
00412 for( QMap<QModelIndex, QPointer<QWidget> >::const_iterator it = m_expandingWidgets.constBegin(); it != m_expandingWidgets.constEnd(); ++it ) {
00413 placeExpandingWidget(it.key());
00414 }
00415 }
00416
00417 int ExpandingWidgetModel::expandingWidgetsHeight() const
00418 {
00419 int sum = 0;
00420 for( QMap<QModelIndex, QPointer<QWidget> >::const_iterator it = m_expandingWidgets.constBegin(); it != m_expandingWidgets.constEnd(); ++it ) {
00421 if( isExpanded(it.key() ) && (*it) )
00422 sum += (*it)->height();
00423 }
00424 return sum;
00425 }
00426
00427
00428 QWidget* ExpandingWidgetModel::expandingWidget(const QModelIndex& idx_) const
00429 {
00430 QModelIndex idx(firstColumn(idx_));
00431
00432 if( m_expandingWidgets.contains(idx) )
00433 return m_expandingWidgets[idx];
00434 else
00435 return 0;
00436 }
00437
00438 void ExpandingWidgetModel::cacheIcons() const {
00439 if( m_expandedIcon.isNull() )
00440 m_expandedIcon = KIconLoader::global()->loadIcon("arrow-down", KIconLoader::Small, 10);
00441
00442 if( m_collapsedIcon.isNull() )
00443 m_collapsedIcon = KIconLoader::global()->loadIcon("arrow-right", KIconLoader::Small, 10);
00444 }
00445
00446 QList<QVariant> mergeCustomHighlighting( int leftSize, const QList<QVariant>& left, int rightSize, const QList<QVariant>& right )
00447 {
00448 QList<QVariant> ret = left;
00449 if( left.isEmpty() ) {
00450 ret << QVariant(0);
00451 ret << QVariant(leftSize);
00452 ret << QTextFormat(QTextFormat::CharFormat);
00453 }
00454
00455 if( right.isEmpty() ) {
00456 ret << QVariant(leftSize);
00457 ret << QVariant(rightSize);
00458 ret << QTextFormat(QTextFormat::CharFormat);
00459 } else {
00460 QList<QVariant>::const_iterator it = right.constBegin();
00461 while( it != right.constEnd() ) {
00462 {
00463 QList<QVariant>::const_iterator testIt = it;
00464 for(int a = 0; a < 2; a++) {
00465 ++testIt;
00466 if(testIt == right.constEnd()) {
00467 kWarning() << "Length of input is not multiple of 3";
00468 break;
00469 }
00470 }
00471 }
00472
00473 ret << QVariant( (*it).toInt() + leftSize );
00474 ++it;
00475 ret << QVariant( (*it).toInt() );
00476 ++it;
00477 ret << *it;
00478 if(!(*it).value<QTextFormat>().isValid())
00479 kDebug( 13035 ) << "Text-format is invalid";
00480 ++it;
00481 }
00482 }
00483 return ret;
00484 }
00485
00486
00487 QList<QVariant> mergeCustomHighlighting( QStringList strings, QList<QVariantList> highlights, int grapBetweenStrings )
00488 {
00489 if(strings.isEmpty()) {
00490 kWarning() << "List of strings is empty";
00491 return QList<QVariant>();
00492 }
00493
00494 if(highlights.isEmpty()) {
00495 kWarning() << "List of highlightings is empty";
00496 return QList<QVariant>();
00497 }
00498
00499 if(strings.count() != highlights.count()) {
00500 kWarning() << "Length of string-list is " << strings.count() << " while count of highlightings is " << highlights.count() << ", should be same";
00501 return QList<QVariant>();
00502 }
00503
00504
00505 QString totalString = strings[0];
00506 QVariantList totalHighlighting = highlights[0];
00507
00508 strings.pop_front();
00509 highlights.pop_front();
00510
00511 while( !strings.isEmpty() ) {
00512 totalHighlighting = mergeCustomHighlighting( totalString.length(), totalHighlighting, strings[0].length(), highlights[0] );
00513 totalString += strings[0];
00514
00515 for(int a = 0; a < grapBetweenStrings; a++)
00516 totalString += ' ';
00517
00518 strings.pop_front();
00519 highlights.pop_front();
00520
00521 }
00522
00523 return totalHighlighting;
00524 }
00525 #include "expandingwidgetmodel.moc"
00526