• Skip to content
  • Skip to link menu
KDE 4.3 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<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       //Unexpand the previous partially expanded row
00161       if( !m_partiallyExpanded.isEmpty() )
00162       { 
00163         while( !m_partiallyExpanded.isEmpty() )
00164             m_partiallyExpanded.erase(m_partiallyExpanded.begin());
00165             //partiallyUnExpand( m_partiallyExpanded.begin().key() );
00166       }
00167       //Notify the underlying models that the item was selected, and eventually get back the text for the expanding widget.
00168       if( !idx.isValid() ) {
00169         //All items have been unselected
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           //Either expand upwards or downwards, choose in a way that 
00178           //the visible fields of the new selected entry are not moved.
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           //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)
00185           if( oldIndex.isValid() && oldIndex < idx ) {
00186             emit dataChanged(oldIndex, idx);
00187 
00188             if( treeView()->verticalScrollMode() == QAbstractItemView::ScrollPerItem )
00189             {
00190               //Qt fails to correctly scroll in ScrollPerItem mode, so the selected index is completely visible,
00191               //so we do the scrolling by hand.
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                 //We need to scroll down
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             //This is needed to keep the item we are expanding completely visible. Qt does not scroll the view to keep the item visible.
00213             //But we must make sure that it isn't too expensive.
00214             //We need to make sure that scrolling is efficient, and the whole content is not repainted.
00215             //Since we are scrolling anyway, we can keep the next line visible, which might be a cool feature.
00216             
00217             //Since this also doesn't work smoothly, leave it for now
00218             //treeView()->scrollTo( nextLine, QAbstractItemView::EnsureVisible ); 
00219           } else if( oldIndex.isValid() &&  idx < oldIndex ) {
00220             emit dataChanged(idx, oldIndex);
00221             
00222             //For consistency with the down-scrolling, we keep one additional line visible above the current visible.
00223             
00224             //Since this also doesn't work smoothly, leave it for now
00225 /*            QModelIndex prevLine = idx.sibling(idx.row()-1, idx.column());
00226             if( prevLine.isValid() )
00227                 treeView()->scrollTo( prevLine );*/
00228           } else
00229             emit dataChanged(idx, idx);
00230         } else if( oldIndex.isValid() ) {
00231           //We are not partially expanding a new row, but we previously had a partially expanded row. So signalize that it has been unexpanded.
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     //Get the whole rectangle of the row:
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     //These offsets must match exactly those used in ExpandingDelegate::sizeHint()
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   //kDebug( 13035 ) << "Setting expand-state of row " << idx.row() << " to " << expanded;
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         //Create a html widget that shows the given string
00334         KTextEdit* edit = new KTextEdit( v.value<QString>() );
00335         edit->setReadOnly(true);
00336         edit->resize(200, 50); //Make the widget small so it embeds nicely.
00337         m_expandingWidgets[idx] = edit;
00338       } else {
00339         m_expandingWidgets[idx] = 0;
00340       }
00341     }
00342 
00343     //Eventually partially expand the row
00344     if( !expanded && firstColumn(treeView()->currentIndex()) == idx && !isPartiallyExpanded(idx) )
00345       rowSelected(idx); //Partially expand the row.
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           //The item is currently not visible
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       //Find out the basic height of the row
00395       rect.setLeft( rect.left() + 20 );
00396       rect.setRight( rightMostRect.right() - 5 );
00397 
00398       //These offsets must match exactly those used in KateCompletionDeleage::sizeHint()
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 //It is assumed that between each two strings, one space is inserted
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     //Merge them together
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     //Combine the custom-highlightings
00523     return totalHighlighting;
00524 }
00525 #include "expandingwidgetmodel.moc"
00526 

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
  • KPty
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.6.1
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