• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • Contact Us
 

Kate

expandingwidgetmodel.cpp

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

Kate

Skip menu "Kate"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.7
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal