• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

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 

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
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
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