[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-core-devel
Subject:    Re: QHeaderView
From:       "Robert Knight" <robertknight () gmail ! com>
Date:       2009-01-18 2:51:59
Message-ID: 13ed09c00901171851y64ba2be5l1f7b8edef1bf6dc7 () mail ! gmail ! com
[Download RAW message or body]

>  Any suggestions on how to proceed?  If other people agree with me,
> perhaps we can get a patch into Qt to allow for this behaviour.

The goal is to keep the content in each column readable when resizing
the sections.  This requires some input to the header view about the
minimum width for each column and also an 'aesthetically pleasant'
distribution of widths between them.  For a commercial project* I
implemented a 'stretchy' header view widget which sets the initial
sizes based on ratios between the various sections and then adjusts
the proportions, rather than the actual widths, when the user resizes
columns.  This is pretty much the same as the 'stretch factor' concept
which Qt layouts have.  This means that:

- Most importantly, the default column widths can be set for each
column in a way which
works with different screen and font sizes
- It keeps a decent amount of the content visible when resizing but
prevents columns being undesirably squished

Implementation attached.  To use it, replace the existing QHeaderView
widgets in the tableview with a StretchHeaderView instance and then
call header->setStretchFactor() for each column to set ratios between
QHeaderView columns.  Works the same way as setStretchFactor() in Qt
layouts.

Regards,
Robert.

* see www.mendeley.com

2009/1/18 John Tapsell <johnflux@gmail.com>: Any suggestions on how to
proceed?  If other people agree with me,
perhaps we can get a patch into Qt to allow for this behaviour.

> Hi all,
>
>  It's very difficult for a user to use a QHeaderView.  Press ctrl+esc
> to bring up the task manager thing, then try to enlarge, say, the CPU
> column.  Doing so shrinks the column next to, so the user then has to
> enlarge that column to fix it.  Then the user has to fix the column
> next to _that_, and so on.  Trying to englarge a single column
> requires about 5 operations!
>
>  Shrinking a column has a different behaviour though - it causes the
> last column (set to stretch) to grow.  So if you just enlarge the CPU
> column then shrink it again back to its original size, you then have
> to do about 5 operations to fix the size of all the other columns
> again.
>
>  I think the ideal behaviour is that both grow and shrink should
> shrink/grow the last stretched column only.
>
>  Dolphin implements this behaviour by reimplementing QHeaderView, but
> Peter Penz says that this took a lot of work to do.
>
>  Any suggestions on how to proceed?  If other people agree with me,
> perhaps we can get a patch into Qt to allow for this behaviour.
>
>
> JohnFlux
>

["StretchHeaderView.h" (text/x-chdr)]

#ifndef HEADERVIEW_H
#define HEADERVIEW_H

#include <QtCore/QVarLengthArray>
#include <QtGui/QHeaderView>

/**
 * A header-view which provides automatic proportional resizing of
 * sections when the containing view is resized.
 *
 * @author Robert Knight <robert.knight@mendeley.com>
 */
class StretchHeaderView : public QHeaderView
{
	Q_OBJECT

	public:
		StretchHeaderView(Qt::Orientation orientation, QWidget* parent = 0);
		
		/// Set the minimum size which section @p logicalIndex will
		/// be automatically resized to when the view size changes
		void setMinimumSectionSize(int logicalindex, int minSize);
		/// See setMinimumSectionSize()
		int minimumSectionSize(int logicalIndex) const;

		/** 
		 * Set the proportional amount of space which section @p logicalIndex
		 * of the header will be given when the whole header is automatically
		 * resized (usually as a result of resizing the view)
		 *
		 * @p logicalIndex The index of the view to set the spacing for
		 * @p stretchFactor The amount of space to allocate the section.
		 * The final size given to each section is:
		 *
		 * availableSpace * (totalStretchFactor / stretchFactorOfSection)
		 *
		 * Where 'totalStretchFactor' is the sum of stretch factors for
		 * all sections.  If the stretch factor for a section is not 
		 * explicitly set, it defaults to 1.0
		 */
		void setStretchFactor(int logicalIndex, qreal stretchFactor);
		/// See setStretchFactor()
		qreal stretchFactor(int logicalIndex) const;

		/// Returns true if a saved header layout exists with
		/// @p name
		bool hasSavedState(const QString& name) const;
		/// Saves the current layout of the header under the name
		/// @p name
		void saveState(const QString& name);
		/// Restores a header layout previously saved with saveState()
		void restoreState(const QString& name);

		/**
		 * Re-implemented from QHeaderView to support retrieving
		 * the header section size allowing enough room for the
		 * sort indicator even though the section doesn't show
		 * the sort indicator at the moment.
		 *
		 * @param logicalIndex The section to retrieve the size of.
		 * @param assumeSortIndicator Include the space needed by the
		 * sort indicator in the returned size even if the section doesn't
		 * have such an indicator yet.
		 */
		QSize sectionSizeFromContents(int logicalIndex, 
				bool assumeSortIndicator = false) const;

	protected Q_SLOTS:
		virtual void updateGeometries();
	
	private Q_SLOTS:
		void sectionSizeChanged(int logicalIndex, int oldSize, int newSize);

	private:
		class LayoutConstraint
		{
		public:
			LayoutConstraint()
				: minimumSize(0),
				  stretchFactor(1),
				  userResized(false)
			{
			}
			int minimumSize;
			qreal stretchFactor;
			// set to true if the user manually resizes a section,
			// in which case the 'minimumSize' constraint is ignored
			bool userResized;
		}; 

		struct SavedLayout
		{
			QByteArray headerViewState;
			QVector<LayoutConstraint> constraints;
		};

		void ensureSectionCount(int logicalIndex);
		void updateStretchFactors();

		bool m_ignoreSectionResizedSignal;
		QVector<LayoutConstraint> m_layoutConstraints;
		QHash<QString,SavedLayout> m_savedStates;

		qreal m_totalStretchFactor;
		QVarLengthArray<int,16> m_visibleResizableSections;
		int m_availableSize;
		bool m_userIsResizing;
};

#endif // HEADERVIEW_H


["StretchHeaderView.cpp" (text/x-c++src)]


#include "StretchHeaderView.h"

#include <QtDebug>

StretchHeaderView::StretchHeaderView(Qt::Orientation orientation, QWidget* parent)
: QHeaderView(orientation,parent)
, m_ignoreSectionResizedSignal(false)
, m_availableSize(0)
, m_userIsResizing(false)
{
	connect(this,SIGNAL(sectionResized(int,int,int)),
			this,SLOT(sectionSizeChanged(int,int,int)));
}

void StretchHeaderView::ensureSectionCount(int index)
{
	if (m_layoutConstraints.count() <= index)
	{
		m_layoutConstraints.resize(index + 1);
	}
}

void StretchHeaderView::setStretchFactor(int logicalIndex, qreal stretchFactor)
{
	ensureSectionCount(logicalIndex);
	stretchFactor = qMax(1.0,stretchFactor);
	m_layoutConstraints[logicalIndex].stretchFactor = stretchFactor;

	if (!m_userIsResizing)
	{
		scheduleDelayedItemsLayout();
	}
}

qreal StretchHeaderView::stretchFactor(int logicalIndex) const
{
	if (m_layoutConstraints.count() >= logicalIndex)
	{
		return 1;
	}
	return m_layoutConstraints[logicalIndex].stretchFactor;
}

void StretchHeaderView::setMinimumSectionSize(int logicalIndex, int size)
{
	ensureSectionCount(logicalIndex);
	m_layoutConstraints[logicalIndex].minimumSize = size;
}

int StretchHeaderView::minimumSectionSize(int logicalIndex) const
{
	if (m_layoutConstraints.count() >= logicalIndex)
	{
		return 0;
	}
	return m_layoutConstraints[logicalIndex].minimumSize;
}

void StretchHeaderView::updateGeometries()
{
	m_ignoreSectionResizedSignal = true;

	QHeaderView::updateGeometries();
	if (count() > 0)
	{
		updateStretchFactors();

		for (int i=0; i < m_visibleResizableSections.count(); i++)
		{
			int logicalIndex = m_visibleResizableSections[i];
			const LayoutConstraint& constraint = m_layoutConstraints[logicalIndex];
			qreal proportion = constraint.stretchFactor / (qreal)m_totalStretchFactor;

			int preferredNewWidth = static_cast<int>(proportion * m_availableSize);
			int newWidth = constraint.userResized ? preferredNewWidth :
						   qMax(preferredNewWidth,constraint.minimumSize);

			if (newWidth > preferredNewWidth)
			{
				m_availableSize += newWidth - preferredNewWidth;
			}
			resizeSection(logicalIndex,newWidth);
		}
	}

	m_ignoreSectionResizedSignal = false;
}

void StretchHeaderView::updateStretchFactors()
{
	m_visibleResizableSections.clear();

	// Get visible, user-resizeable header sections
	int fixedSectionWidth = 0;
	for (int logicalIndex=0; logicalIndex < count(); logicalIndex++)
	{
		bool isFixed = resizeMode(logicalIndex) == QHeaderView::Fixed;
		if (!isSectionHidden(logicalIndex))
		{
			if (isFixed)
			{
				fixedSectionWidth += sectionSize(logicalIndex);
			}
			else
			{
				m_visibleResizableSections.append(logicalIndex);
			}
		}
	}

	ensureSectionCount(count()-1);

	// Calculate total available space for resizable
	// sections and distribute amongst the sections according
	// to their stretch factors
	m_availableSize = width() - fixedSectionWidth;
	m_totalStretchFactor = 0;
	for (int i=0; i < m_visibleResizableSections.count(); i++)
	{
		int logicalIndex = m_visibleResizableSections[i];
		LayoutConstraint& constraint = m_layoutConstraints[logicalIndex];
		constraint.stretchFactor = qMax(1.0,constraint.stretchFactor);
		m_totalStretchFactor += constraint.stretchFactor;
	}
}

bool StretchHeaderView::hasSavedState(const QString& name) const
{
	return m_savedStates.contains(name);
}

void StretchHeaderView::saveState(const QString& name)
{
	SavedLayout layout;
	layout.constraints = m_layoutConstraints;
	layout.headerViewState = QHeaderView::saveState();
	m_savedStates.insert(name,layout);
}

void StretchHeaderView::restoreState(const QString& name)
{
	if (m_savedStates.contains(name))
	{
		const SavedLayout& layout = m_savedStates[name];
		QHeaderView::restoreState(layout.headerViewState);
		m_layoutConstraints = layout.constraints;
	}
}

void StretchHeaderView::sectionSizeChanged(int logicalIndex, int oldSize, int newSize)
{
	// Ignore size change signals coming from updateGeometries()
	if (m_ignoreSectionResizedSignal)
	{
		return;
	}

	m_userIsResizing = true;

	ensureSectionCount(logicalIndex);
	updateStretchFactors();

	m_layoutConstraints[logicalIndex].userResized = true;

	// When user manually resizes a section, re-calculate the stretch
	// factor for the section so that the section adjusts in size
	// with the proportion given by the user
	qreal proportionalSize = (qreal)newSize / m_availableSize;

	if (proportionalSize > minimumSectionSize(logicalIndex))
	{
		qreal newStretchFactor = proportionalSize * m_totalStretchFactor;
		setStretchFactor(logicalIndex,newStretchFactor);
	}

	m_userIsResizing = false;
}

QSize StretchHeaderView::sectionSizeFromContents(int logicalIndex, bool assumeSortIndicator) const
{
	// copied and adapted from QHeaderView::sectionSizeFromContents()
	QStyleOptionHeader opt;
    initStyleOption(&opt);
    opt.section = logicalIndex;
    QVariant var = model()->headerData(logicalIndex, orientation(),
                                            Qt::FontRole);
    QFont fnt;
    if (var.isValid() && qVariantCanConvert<QFont>(var))
        fnt = qvariant_cast<QFont>(var);
    else
        fnt = font();
    fnt.setBold(true);
    opt.fontMetrics = QFontMetrics(fnt);
    opt.text = model()->headerData(logicalIndex, orientation(),
                                    Qt::DisplayRole).toString();
    QVariant variant = model()->headerData(logicalIndex, orientation(), Qt::DecorationRole);
    opt.icon = qvariant_cast<QIcon>(variant);
    if (opt.icon.isNull())
        opt.icon = qvariant_cast<QPixmap>(variant);
    QSize size = style()->sizeFromContents(QStyle::CT_HeaderSection, &opt, QSize(), this);

	bool hasSortIndicator = isSortIndicatorShown() && sortIndicatorSection() == logicalIndex;
	if (hasSortIndicator || assumeSortIndicator)
	{
        int margin = style()->pixelMetric(QStyle::PM_HeaderMargin, &opt, this);
        if (orientation() == Qt::Horizontal)
            size.rwidth() += size.height() + margin;
        else
            size.rheight() += size.width() + margin;
    }
    return size;

}


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic