/* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2006 Hamish Rodda * Copyright (C) 2007-2008 David Nolden * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "katecompletiontree.h" #include #include #include #include #include #include #include "kateview.h" #include "katerenderer.h" #include "kateconfig.h" #include "katecompletionwidget.h" #include "katecompletiondelegate.h" #include "katecompletionmodel.h" KateCompletionTree::KateCompletionTree(KateCompletionWidget* parent) : ExpandingTree(parent) { m_scrollingEnabled = true; header()->hide(); setRootIsDecorated(false); setIndentation(0); setFrameStyle(QFrame::NoFrame); setAllColumnsShowFocus(true); setAlternatingRowColors(true); //We need ScrollPerItem, because ScrollPerPixel is too slow with a very large competion-list(see KDevelop). setVerticalScrollMode(QAbstractItemView::ScrollPerItem); m_resizeTimer = new QTimer(this); m_resizeTimer->setSingleShot(true); connect(m_resizeTimer, SIGNAL(timeout()), this, SLOT(resizeColumnsSlot())); // Provide custom highlighting to completion entries setItemDelegate(new KateCompletionDelegate(widget()->model(), widget())); ///@todo uncomment this once we're sure it isn't called too often, or maybe use a timer. //connect(widget()->model(), SIGNAL(contentGeometryChanged()), this, SLOT(resizeColumnsSlot())); // Prevent user from expanding / collapsing with the mouse setItemsExpandable(false); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } void KateCompletionTree::currentChanged ( const QModelIndex & current, const QModelIndex & previous ) { widget()->model()->rowSelected(current); ExpandingTree::currentChanged(current, previous); } void KateCompletionTree::setScrollingEnabled(bool enabled) { m_scrollingEnabled = enabled; } void KateCompletionTree::scrollContentsBy( int dx, int dy ) { if(m_scrollingEnabled) QTreeView::scrollContentsBy(dx, dy); if (isVisible()) m_resizeTimer->start(300); } int KateCompletionTree::columnTextViewportPosition ( int column ) const { int ret = columnViewportPosition(column); QModelIndex i = model()->index(0, column, QModelIndex()); QModelIndex base = model()->index(0, 0, QModelIndex()); //If it's just a group header, use the first child if(base.isValid() && model()->rowCount(base)) i = base.child(0, column); if(i.isValid()) { QIcon icon = i.data(Qt::DecorationRole).value(); if(!icon.isNull()) ret += icon.actualSize(sizeHintForIndex(i)).width(); } return ret; } KateCompletionWidget * KateCompletionTree::widget( ) const { return static_cast(const_cast(parent())); } void KateCompletionTree::resizeColumnsSlot() { if(model()) resizeColumns(); } void KateCompletionTree::resizeColumns(bool firstShow, bool forceResize) { static bool preventRecursion = false; if (preventRecursion) return; if(firstShow) forceResize = true; preventRecursion = true; widget()->setUpdatesEnabled(false); int modelIndexOfName = kateModel()->translateColumn(KTextEditor::CodeCompletionModel::Name); int oldIndentWidth = columnViewportPosition(modelIndexOfName); ///Step 1: Compute the needed column-sizes for the visible content int numColumns = model()->columnCount(); QVector columnSize(numColumns, 5); int currentYPos = 0; QModelIndex current = indexAt(QPoint(1,1)); if( current.child(0,0).isValid() ) { //If the index has children, it is a group-label. Then we should start with it's first child. currentYPos += sizeHintForIndex(current).height(); current = current.child(0,0); } int num = 0; bool changed = false; while( current.isValid() && currentYPos < height() ) { // kDebug() << current.row() << "out of" << model()->rowCount(current.parent()) << "in" << current.parent().data(Qt::DisplayRole); currentYPos += sizeHintForIndex(current).height(); // itemDelegate()->sizeHint(QStyleOptionViewItem(), current).isValid() && itemDelegate()->sizeHint(QStyleOptionViewItem(), current).intersects(visibleViewportRect) changed = true; num++; for( int a = 0; a < numColumns; a++ ) { QSize s = sizeHintForIndex (current.sibling(current.row(), a)); // kDebug() << "size-hint for" << current.row() << a << ":" << s << current.sibling(current.row(), a).data(Qt::DisplayRole); if( s.width() > columnSize[a] && s.width() < 2000 ) columnSize[a] = s.width(); else if( s.width() > 2000 ) kDebug( 13035 ) << "got invalid size-hint of width " << s.width(); } QModelIndex oldCurrent = current; current = current.sibling(current.row()+1, 0); //Are we at the end of a group? If yes, move on into the next group if( !current.isValid() && oldCurrent.parent().isValid() ) { current = oldCurrent.parent().sibling( oldCurrent.parent().row()+1, 0 ); if( current.isValid() && current.child(0,0).isValid() ) { currentYPos += sizeHintForIndex(current).height(); current = current.child(0,0); } } } int totalColumnsWidth = 0, originalViewportWidth = viewport()->width(); int maxWidth = (QApplication::desktop()->screenGeometry(widget()->view()).width()*3) / 4; ///Step 2: Update column-sizes //This contains several hacks to reduce the amount of resizing that happens. Generally, //resizes only happen if a) More than a specific amount of space is saved by the resize, or //b) the resizing is required so the list can show all of its contents. int minimumResize = 0; int maximumResize = 0; if( changed ) { for( int n = 0; n < numColumns; n++ ) { totalColumnsWidth += columnSize[n]; int diff = columnSize[n] - columnWidth(n); if( diff < minimumResize ) minimumResize = diff; if( diff > maximumResize ) maximumResize = diff; } int noReduceTotalWidth = 0; //The total width of the widget of no columns are reduced for( int n = 0; n < numColumns; n++ ) { if(columnSize[n] < columnWidth(n)) noReduceTotalWidth += columnWidth(n); else noReduceTotalWidth += columnSize[n]; } //Check whether we can afford to reduce none of the columns //Only reduce size if we widget would else be too wide. bool noReduce = noReduceTotalWidth < maxWidth && !forceResize; if(noReduce) { totalColumnsWidth = 0; for( int n = 0; n < numColumns; n++ ) { if(columnSize[n] < columnWidth(n)) columnSize[n] = columnWidth(n); totalColumnsWidth += columnSize[n]; } } if( minimumResize > -40 && maximumResize == 0 && !forceResize ) { //No column needs to be exanded, and no column needs to be reduced by more than 40 pixels. //To prevent flashing, do not resize at all. totalColumnsWidth = 0; for( int n = 0; n < numColumns; n++ ) { columnSize[n] = columnWidth(n); totalColumnsWidth += columnSize[n]; } } else { // viewport()->resize( 5000, viewport()->height() ); for( int n = 0; n < numColumns; n++ ) { setColumnWidth(n, columnSize[n]); } // kDebug() << "resizing viewport to" << totalColumnsWidth; viewport()->resize( totalColumnsWidth, viewport()->height() ); } } ///Step 3: Update widget-size and -position int scrollBarWidth = verticalScrollBar()->width(); int newIndentWidth = columnViewportPosition(modelIndexOfName); int newWidth = qMin(maxWidth, qMax(75, totalColumnsWidth)); if(newWidth == maxWidth) setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); else setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); if(maximumResize > 0 || forceResize || oldIndentWidth != newIndentWidth) { // kDebug() << geometry() << "newWidth" << newWidth << "current width" << width() << "target width" << newWidth + scrollBarWidth; if((newWidth + scrollBarWidth) != width() && originalViewportWidth != totalColumnsWidth) { widget()->resize(newWidth + scrollBarWidth + 2, widget()->height()); resize(newWidth + scrollBarWidth, widget()->height()- (2*widget()->frameWidth())); } // kDebug() << "created geometry:" << widget()->geometry() << geometry() << "newWidth" << newWidth << "viewport" << viewport()->width(); if( viewport()->width() > totalColumnsWidth ) //Set the size of the last column to fill the whole rest of the widget setColumnWidth(numColumns-1, viewport()->width() - columnViewportPosition(numColumns-1)); /* for(int a = 0; a < numColumns; ++a) kDebug() << "column" << a << columnWidth(a) << "target:" << columnSize[a];*/ if (oldIndentWidth != newIndentWidth) if(widget()->updatePosition() && !forceResize) { preventRecursion = false; resizeColumns(true, true); } } widget()->setUpdatesEnabled(true); preventRecursion = false; } QStyleOptionViewItem KateCompletionTree::viewOptions( ) const { QStyleOptionViewItem opt = QTreeView::viewOptions(); opt.font = widget()->view()->renderer()->config()->font(); return opt; } KateCompletionModel * KateCompletionTree::kateModel( ) const { return static_cast(model()); } bool KateCompletionTree::nextCompletion() { QModelIndex current; QModelIndex firstCurrent = currentIndex(); do { QModelIndex oldCurrent = currentIndex(); current = moveCursor(MoveDown, Qt::NoModifier); if (current != oldCurrent && current.isValid()) { setCurrentIndex(current); } else { if (firstCurrent.isValid()) setCurrentIndex(firstCurrent); return false; } } while (!kateModel()->indexIsItem(current)); return true; } bool KateCompletionTree::previousCompletion() { QModelIndex current; QModelIndex firstCurrent = currentIndex(); do { QModelIndex oldCurrent = currentIndex(); current = moveCursor(MoveUp, Qt::NoModifier); if (current != oldCurrent && current.isValid()) { setCurrentIndex(current); } else { if (firstCurrent.isValid()) setCurrentIndex(firstCurrent); return false; } } while (!kateModel()->indexIsItem(current)); return true; } bool KateCompletionTree::pageDown( ) { QModelIndex old = currentIndex(); QModelIndex current = moveCursor(MovePageDown, Qt::NoModifier); if (current.isValid()) { setCurrentIndex(current); if (!kateModel()->indexIsItem(current)) if (!nextCompletion()) previousCompletion(); } return current != old; } bool KateCompletionTree::pageUp( ) { QModelIndex old = currentIndex(); QModelIndex current = moveCursor(MovePageUp, Qt::NoModifier); if (current.isValid()) { setCurrentIndex(current); if (!kateModel()->indexIsItem(current)) if (!previousCompletion()) nextCompletion(); } return current != old; } void KateCompletionTree::top( ) { QModelIndex current = moveCursor(MoveHome, Qt::NoModifier); setCurrentIndex(current); if (current.isValid()) { setCurrentIndex(current); if (!kateModel()->indexIsItem(current)) nextCompletion(); } } void KateCompletionTree::scheduleUpdate() { m_resizeTimer->start(300); } void KateCompletionTree::bottom( ) { QModelIndex current = moveCursor(MoveEnd, Qt::NoModifier); setCurrentIndex(current); if (current.isValid()) { setCurrentIndex(current); if (!kateModel()->indexIsItem(current)) previousCompletion(); } } #include "moc_katecompletiontree.cpp"