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

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
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.4
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