Files
cantata/widgets/autohidingsplitter.cpp

511 lines
15 KiB
C++

/*
* Cantata
*
* Copyright (c) 2011-2014 Craig Drummond <craig.p.drummond@gmail.com>
* This file (c) 2012 Piotr Wicijowski <piotr.wicijowski@gmail.com>
*
* ----
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "autohidingsplitter.h"
#include "support/gtkstyle.h"
#include "support/utils.h"
#include <QSplitterHandle>
#include <QTimer>
#include <QChildEvent>
#include <QResizeEvent>
#include <QComboBox>
#include <QMenu>
#include <QApplication>
#include <QStyle>
static int splitterSize(const QWidget *w)
{
static int size=-1;
if (-1==size || !w || !w->isVisible()) {
if (Utils::touchFriendly()) {
size=4;
} else if (qApp->style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) {
int spacing=qApp->style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarSpacing);
int splitterSize=qApp->style()->pixelMetric(QStyle::PM_SplitterWidth);
size=qMin(spacing+2, splitterSize);
} else {
size=GtkStyle::isActive() ? 0 : 1;
}
}
return size;
}
QSize AutohidingSplitterHandle::sizeHint() const
{
int sz=splitterSize(this);
return QSize(sz, sz);
}
class SplitterSizeAnimation:public QVariantAnimation
{
public:
SplitterSizeAnimation(QObject *parent)
: QVariantAnimation(parent)
, splitter(0)
{
}
void setSplitter(QSplitter *splitter) {
this->splitter = splitter;
}
protected:
virtual QVariant interpolated(const QVariant &from, const QVariant &to, qreal progress) const {
QList<int> fromInt = from.value<QList<int> >();
QList<int> toInt = to.value<QList<int> >();
QList<int> returnValue;
for (int i = 0; i < fromInt.count() ; ++i) {
returnValue.append((int)((progress)*toInt.at(i) + (1-progress)*fromInt.at(i)));
}
return QVariant::fromValue(returnValue);
}
virtual void updateCurrentValue(const QVariant &value) {
if (splitter) {
splitter->setSizes(value.value<QList<int> >());
}
}
private:
QSplitter *splitter;
};
AutohidingSplitter::AutohidingSplitter(Qt::Orientation orientation, QWidget *parent)
: QSplitter(orientation, parent)
, autoHideEnabled(false)
{
haltModifications = false;
autohideAnimation = new SplitterSizeAnimation(this);
autohideAnimation->setSplitter(this);
autohideAnimation->setDuration(100);
autohideAnimation->setEasingCurve(QEasingCurve::Linear);
//connect(this, SIGNAL(splitterMoved(int, int)), this, SLOT(updateAfterSplitterMoved(int, int)));
setMinimumWidth(32);
setHandleWidth(splitterSize(this));
}
AutohidingSplitter::AutohidingSplitter(QWidget *parent)
: QSplitter(parent)
, autoHideEnabled(false)
{
haltModifications = false;
autohideAnimation = new SplitterSizeAnimation(this);
autohideAnimation->setSplitter(this);
autohideAnimation->setDuration(100);
autohideAnimation->setEasingCurve(QEasingCurve::Linear);
//connect(this, SIGNAL(splitterMoved(int, int)), this, SLOT(updateAfterSplitterMoved(int, int)));
setMinimumWidth(32);
setHandleWidth(Utils::touchFriendly() ? 4 : 1);
}
AutohidingSplitter::~AutohidingSplitter()
{
foreach(QTimer *sat, animationDelayTimer) {
sat->stop();
delete sat;
}
animationDelayTimer.clear();
autohideAnimation->stop();
delete autohideAnimation;
// setSizes(expandedSizes);
}
QSplitterHandle * AutohidingSplitter::createHandle()
{
AutohidingSplitterHandle *sh = new AutohidingSplitterHandle(orientation(), this);
connect(sh, SIGNAL(hoverStarted()), this, SLOT(handleHoverStarted()));
connect(sh, SIGNAL(hoverFinished()), this, SLOT(handleHoverFinished()));
return sh;
}
void AutohidingSplitter::addChild(QObject *pObject)
{
if (pObject && pObject->isWidgetType()) {
pObject->installEventFilter(this);
const QObjectList &childList = pObject->children();
foreach (QObject *obj, childList) {
addChild(obj);
}
}
QComboBox * combo = qobject_cast<QComboBox *>(pObject);
if(combo){
popupsBlockingAutohiding.insert(combo->view());
// addChild(combo->view());
}
}
void AutohidingSplitter::removeChild(QObject *pObject)
{
if (pObject && (pObject->isWidgetType())) {
pObject->removeEventFilter(this);
const QObjectList& childList = pObject->children();
foreach (QObject *obj, childList) {
removeChild(obj);
}
}
}
void AutohidingSplitter::childEvent(QChildEvent *e)
{
// if (!autoHideEnabled) {
// QSplitter::childEvent(e);
// return;
// }
if (e->child()->isWidgetType()) {
if (QEvent::ChildAdded==e->type()) {
addChild(e->child());
} else if (QEvent::ChildRemoved==e->type()) {
removeChild(e->child());
}
}
QSplitter::childEvent(e);
}
bool AutohidingSplitter::eventFilter(QObject *target, QEvent *e)
{
if (!autoHideEnabled) {
return QSplitter::eventFilter(target, e);
}
switch (e->type()) {
case QEvent::ChildAdded: {
QChildEvent *ce = (QChildEvent*)e;
addChild(ce->child());
break;
}
case QEvent::ChildRemoved: {
QChildEvent *ce = (QChildEvent*)e;
removeChild(ce->child());
break;
}
default:
break;
}
if (!autoHideEnabled) {
return QSplitter::eventFilter(target, e);
}
switch (e->type()) {
case QEvent::Enter:
widgetHoverStarted(indexOf(qobject_cast<QWidget *>(target)));
if(popupsBlockingAutohiding.contains(qobject_cast<QWidget *>(target))) {
haltModifications = true;
}
break;
case QEvent::Leave:
widgetHoverFinished(indexOf(qobject_cast<QWidget *>(target)));
if(popupsBlockingAutohiding.contains(qobject_cast<QWidget *>(target))) {
haltModifications = false;
}
break;
case QEvent::MouseButtonPress:
if(qobject_cast<AutohidingSplitterHandle *>(target)){
foreach(QTimer * timer, animationDelayTimer) {
timer->stop();
}
haltModifications = true;
}
break;
case QEvent::MouseButtonRelease:{
if(qobject_cast<AutohidingSplitterHandle *>(target)){
haltModifications = false;
targetSizes.clear();
}
break;
}
case QEvent::FocusIn:{
// QFocusEvent *ce = (QFocusEvent *)e;
haltModifications = false;
break;
}
case QEvent::FocusOut:{
// QFocusEvent *ce = (QFocusEvent *)e;
haltModifications = true;
break;
}
default:
break;
}
return QSplitter::eventFilter(target, e);
}
void AutohidingSplitter::setAutoHideEnabled(bool ah)
{
if (ah==autoHideEnabled) {
return;
}
autoHideEnabled=ah;
if (autoHideEnabled) {
expandedSizes = sizes();
connect(this, SIGNAL(splitterMoved(int, int)), this, SLOT(updateAfterSplitterMoved(int, int)));
} else {
for(int i = 0; i < widgetAutohidden.count() ; ++i) {
widgetAutohidden[i]=false;
}
disconnect(this, SIGNAL(splitterMoved(int, int)), this, SLOT(updateAfterSplitterMoved(int, int)));
}
}
void AutohidingSplitter::setVisible(bool visible)
{
haltModifications=!visible;
QSplitter::setVisible(visible);
}
void AutohidingSplitter::resizeEvent(QResizeEvent *event)
{
if (autoHideEnabled && !haltModifications && event->oldSize().width() > 0) {
// int oldUsableSize = event->oldSize().width()/*-(count()-1)*handleWidth()*/;
int oldUsableSize = 0;
foreach(int size, expandedSizes) {
oldUsableSize+=size;
}
int newUsableSize = event->size().width()/*-(count()-1)*handleWidth()*/;
int leftToDistribute = newUsableSize-oldUsableSize;
for (int i = 0; i < count()-1; ++ i) {
expandedSizes[i]+=int(qreal(leftToDistribute)/(count()-i));
leftToDistribute-=int(qreal(leftToDistribute)/(count()-i));
}
if (count()>0) {
expandedSizes[count()-1]+=leftToDistribute;
}
}
QSplitter::resizeEvent(event);
}
void AutohidingSplitter::addWidget(QWidget *widget)
{
QSplitter::addWidget(widget);
expandedSizes.append(widget->size().width());
if (count()+1!=widgetAutohidable.count()) {
QTimer *sat = new QTimer(this);
sat->setSingleShot(true);
sat->setInterval(500);
connect(sat, SIGNAL(timeout()), this, SLOT(setWidgetForHiding()));
animationDelayTimer.append(sat);
widgetAutohidden.append(false);
widgetAutohiddenPrev.append(false);
widgetAutohidable.append(false);
}
}
void AutohidingSplitter::setAutohidable(int index, bool autohidable)
{
widgetAutohidable[index]=autohidable;
widgetAutohidden[index]=autohidable;
// updateResizeQueue();
if (autoHideEnabled) {
setSizes(getSizesAfterHiding());
}
}
bool AutohidingSplitter::restoreState(const QByteArray &state)
{
bool result = QSplitter::restoreState(state);
expandedSizes = sizes();
return result;
}
QByteArray AutohidingSplitter::saveState() const
{
if (!autoHideEnabled) {
return QSplitter::saveState();
}
AutohidingSplitter *tmpSplitter = new AutohidingSplitter(/*qobject_cast<QWidget *>(parent())*/);
for (int i = 0; i < count(); ++ i) {
QWidget *widget = new QWidget(/*tmpSplitter*/);
tmpSplitter->addWidget(widget);
}
tmpSplitter->setSizes(expandedSizes);
QByteArray result = tmpSplitter->QSplitter::saveState();
delete tmpSplitter;
return result;
}
void AutohidingSplitter::widgetHoverStarted(int index)
{
if (!autoHideEnabled || index<0 || index > count()) {
return;
}
if(!haltModifications){
if (animationDelayTimer.at(index)->isActive()) {
animationDelayTimer.at(index)->stop();
}
if (widgetAutohidden.at(index) && widgetAutohidable.at(index)) {
widgetAutohiddenPrev[index] = widgetAutohidden[index];
widgetAutohidden[index] = false;
updateResizeQueue();
}
}
}
void AutohidingSplitter::widgetHoverFinished(int index)
{
if (!autoHideEnabled || index<0 || index > count()) {
return;
}
if (!widgetAutohidden.at(index) && widgetAutohidable.at(index)) {
animationDelayTimer.at(index)->start();
}
}
void AutohidingSplitter::handleHoverStarted()
{
if (!autoHideEnabled) {
return;
}
if(!haltModifications){
int index = indexOf(qobject_cast<QWidget *>(QObject::sender()));
if (animationDelayTimer.at(index)->isActive()) {
animationDelayTimer.at(index)->stop();
}
if (widgetAutohidden.at(index) && widgetAutohidable.at(index)) {
widgetAutohiddenPrev[index] = widgetAutohidden[index];
widgetAutohidden[index] = false;
updateResizeQueue();
}
if (index > 0 && animationDelayTimer.at(index-1)->isActive()) {
animationDelayTimer.at(index-1)->stop();
}
if (index > 0 && widgetAutohidden.at(index-1) && widgetAutohidable.at(index-1)) {
widgetAutohiddenPrev[index-1] = widgetAutohidden[index-1];
widgetAutohidden[index-1] = false;
updateResizeQueue();
}
}
}
void AutohidingSplitter::handleHoverFinished()
{
if (!autoHideEnabled) {
return;
}
if(!haltModifications){
int index = indexOf(qobject_cast<QWidget *>(QObject::sender()));
if (!widgetAutohidden.at(index) && widgetAutohidable.at(index)) {
animationDelayTimer.at(index)->start();
}
if (index>0 && !widgetAutohidden.at(index-1) && widgetAutohidable.at(index-1)) {
animationDelayTimer.at(index-1)->start();
}
}
}
void AutohidingSplitter::updateResizeQueue()
{
if (!autoHideEnabled) {
return;
}
targetSizes.enqueue(getSizesAfterHiding());
if (autohideAnimation->state()==QAbstractAnimation::Stopped) {
startAnimation();
}
}
QList<int> AutohidingSplitter::getSizesAfterHiding() const
{
int toDistribute = 0;
int numberOfExpanded = 0;
QList<int> result;
result = sizes();
for (int i = 0 ; i<widgetAutohidden.count(); ++i) {
if (widgetAutohidable.at(i)) {
if (widgetAutohidden.at(i)) {
toDistribute+=result.at(i);
result[i]=0;
} else {
toDistribute+=result.at(i)-expandedSizes.at(i);
result[i]=expandedSizes.at(i);
if(widgetAutohidden.at(i)==widgetAutohiddenPrev.at(i))
numberOfExpanded++;
}
} else {
toDistribute+=result.at(i)-expandedSizes.at(i);
result[i]=expandedSizes.at(i);
numberOfExpanded++;
}
}
for (int i = 0 ; i<widgetAutohidden.count(); ++i) {
if( (widgetAutohidable.at(i)&&!widgetAutohidden.at(i)&&widgetAutohidden.at(i)==widgetAutohiddenPrev.at(i)) || !widgetAutohidable.at(i)) {
numberOfExpanded--;
if (numberOfExpanded) {
result[i]+=int(qreal(toDistribute)/(numberOfExpanded+1));
toDistribute-=int(qreal(toDistribute)/(numberOfExpanded+1));
} else {
result[i]+=toDistribute;
}
}
}
return result;
}
void AutohidingSplitter::startAnimation()
{
if (!autoHideEnabled) {
return;
}
disconnect(this,SLOT(startAnimation()));
if (!targetSizes.isEmpty()) {
QList<int> nextSizes = targetSizes.dequeue();
autohideAnimation->setStartValue(QVariant::fromValue(sizes()));
autohideAnimation->setCurrentTime(0);
autohideAnimation->setEndValue(QVariant::fromValue(nextSizes));
connect(autohideAnimation,SIGNAL(finished()),this,SLOT(startAnimation()));
autohideAnimation->start();
}
}
void AutohidingSplitter::setWidgetForHiding()
{
int index = animationDelayTimer.indexOf(qobject_cast<QTimer *>(QObject::sender()));
if(!haltModifications){
if (!widgetAutohidden.at(index)) {
widgetAutohiddenPrev[index] = widgetAutohidden[index];
widgetAutohidden[index] = true;
updateResizeQueue();
}
}
}
void AutohidingSplitter::updateAfterSplitterMoved(int pos, int index)
{
Q_UNUSED(pos);
if (!autoHideEnabled || index<=0 || index>count()) {
return;
}
QList<int> currentTemporarySizes = sizes();
QList<int> previousTemporarySizes = getSizesAfterHiding();
expandedSizes[index-1]+=currentTemporarySizes.at(index-1)-previousTemporarySizes.at(index-1);
expandedSizes[index]+=currentTemporarySizes.at(index)-previousTemporarySizes.at(index);
}