Implements show/hide of SectionContent + serialization of state (not yet

completed/tested).
Context menu also works with new impl.
Shows close button on top-right corner of SectionWidget.
This commit is contained in:
mfreiholz 2016-02-18 15:06:00 +01:00
parent d5eaabd2a7
commit 6a1b6307c9
11 changed files with 300 additions and 72 deletions

View File

@ -7,6 +7,8 @@ TEMPLATE = lib
CONFIG += staticlib
VERSION = 0.1.0
INCLUDEPATH += $$PWD/src
INCLUDEPATH += $$PWD/include

View File

@ -10,6 +10,7 @@ class QSplitter;
class QMenu;
#include "ads/API.h"
#include "ads/Internal.h"
#include "ads/SectionContent.h"
#include "ads/SectionWidget.h"
#include "ads/FloatingWidget.h"
@ -50,6 +51,18 @@ public:
*/
SectionWidget* addSectionContent(const SectionContent::RefPtr& sc, SectionWidget* sw = NULL, DropArea area = CenterDropArea);
/*!
* Shows the specific SectionContent in UI.
* Independed of the current state, whether it is used inside a section or is floating.
*/
bool showSectionContent(const SectionContent::RefPtr& sc);
/*!
* Closes the specified SectionContent from UI.
* Independed of the current state, whether it is used inside a section or is floating.
*/
bool hideSectionContent(const SectionContent::RefPtr& sc);
/*!
* Creates a QMenu based on available SectionContents.
* The caller is responsible to delete the menu.
@ -102,16 +115,17 @@ signals:
void orientationChanged();
private:
// Sections of this container
QList<SectionWidget*> _sections;
//Floatings of this container
QList<FloatingWidget*> _floatings;
QHash<int, HiddenSectionItem> _hiddenSectionContents;
// Layout stuff
QGridLayout* _mainLayout;
Qt::Orientation _orientation;
QPointer<QSplitter> _splitter; // $mfreiholz: I'd like to remove this variable entirely.
QPointer<QSplitter> _splitter; // $mfreiholz: I'd like to remove this variable entirely,
// because it changes during user interaction anyway.
};
ADS_NAMESPACE_END

View File

@ -28,5 +28,19 @@ public:
};
class HiddenSectionItem
{
public:
HiddenSectionItem() :
preferredSectionId(-1),
preferredSectionIndex(-1)
{}
int preferredSectionId;
int preferredSectionIndex;
InternalContentData data;
};
ADS_NAMESPACE_END
#endif

View File

@ -18,6 +18,7 @@ class SectionContentWidget : public QFrame
public:
SectionContentWidget(SectionContent::RefPtr c, QWidget* parent = 0);
virtual ~SectionContentWidget();
private:
SectionContent::RefPtr _content;

View File

@ -34,6 +34,7 @@ class SectionTitleWidget : public QFrame
public:
SectionTitleWidget(SectionContent::RefPtr content, QWidget* parent);
virtual ~SectionTitleWidget();
bool isActiveTab() const;
void setActiveTab(bool active);

View File

@ -9,13 +9,13 @@ class QBoxLayout;
class QStackedLayout;
#include "ads/API.h"
#include "ads/Internal.h"
#include "ads/SectionContent.h"
ADS_NAMESPACE_BEGIN
class ContainerWidget;
class SectionTitleWidget;
class SectionContentWidget;
class InternalContentData;
// SectionWidget manages multiple instances of SectionContent.
// It displays a title TAB, which is clickable and will switch to
@ -37,10 +37,10 @@ public:
QRect contentAreaGeometry() const;
const QList<SectionContent::RefPtr>& contents() const { return _contents; }
void addContent(SectionContent::RefPtr c);
void addContent(const SectionContent::RefPtr& c);
void addContent(const InternalContentData& data, bool autoActivate);
bool takeContent(int uid, InternalContentData& data);
int indexOfContent(SectionContent::RefPtr c) const;
int indexOfContent(const SectionContent::RefPtr& c) const;
int indexOfContentByTitlePos(const QPoint& pos, QWidget* exclude = NULL) const;
int currentIndex() const;
@ -49,11 +49,9 @@ public:
public slots:
void setCurrentIndex(int index);
protected:
virtual void paintEvent(QPaintEvent*);
private slots:
void onSectionTitleClicked();
void onCloseButtonClicked();
private:
const int _uid;

View File

@ -73,52 +73,164 @@ SectionWidget* ContainerWidget::addSectionContent(const SectionContent::RefPtr&
return dropContent(data, sw, area, false);
}
QMenu* ContainerWidget::createContextMenu() const
bool ContainerWidget::showSectionContent(const SectionContent::RefPtr& sc)
{
QMenu* m = new QMenu(NULL);
// Search SC in floatings
for (int i = 0; i < _floatings.count(); ++i)
{
const bool found = _floatings.at(i)->content()->uid() == sc->uid();
if (!found)
continue;
_floatings.at(i)->setVisible(true);
return true;
}
// Contents of SectionWidgets
for (int i = 0; i < _sections.size(); ++i)
// Search SC in hidden sections
// Try to show them in the last position, otherwise simply append
// it to the first section (or create a new section?)
if (_hiddenSectionContents.contains(sc->uid()))
{
const HiddenSectionItem hsi = _hiddenSectionContents.take(sc->uid());
hsi.data.titleWidget->setVisible(true);
hsi.data.contentWidget->setVisible(true);
SectionWidget* sw = NULL;
if (hsi.preferredSectionId > 0 && (sw = SectionWidget::LookupMap.value(hsi.preferredSectionId)) != NULL)
{
sw->addContent(hsi.data, true);
return true;
}
else if (_sections.size() > 0 && (sw = _sections.first()) != NULL)
{
sw->addContent(hsi.data, true);
return true;
}
else
{
qDebug() << "TODO Create new SW here and add SC";
return true;
}
}
qFatal("Unable to show SectionContent, don't know where 8-/");
return false;
}
bool ContainerWidget::hideSectionContent(const SectionContent::RefPtr& sc)
{
// Search SC in floatings
// We can simply hide floatings, nothing else required.
for (int i = 0; i < _floatings.count(); ++i)
{
const bool found = _floatings.at(i)->content()->uid() == sc->uid();
if (!found)
continue;
_floatings.at(i)->setVisible(false);
return true;
}
// Search SC in sections
// It's required to remove the SC from SW completely and hold it in a
// separate list as long as a "showSectionContent" gets called for the SC again.
// In case that the SW does not have any other SCs, we need to delete it.
for (int i = 0; i < _sections.count(); ++i)
{
SectionWidget* sw = _sections.at(i);
QList<SectionContent::RefPtr> contents = sw->contents();
foreach (const SectionContent::RefPtr& c, contents)
const bool found = sw->indexOfContent(sc) >= 0;
if (!found)
continue;
HiddenSectionItem hsi;
hsi.preferredSectionId = sw->uid();
hsi.preferredSectionIndex = sw->indexOfContent(sc);
if (!sw->takeContent(sc->uid(), hsi.data))
return false;
hsi.data.titleWidget->setVisible(false);
hsi.data.contentWidget->setVisible(false);
_hiddenSectionContents.insert(sc->uid(), hsi);
if (sw->contents().isEmpty())
{
QAction* a = m->addAction(QIcon(), c->uniqueName());
a->setProperty("uid", c->uid());
delete sw;
sw = NULL;
deleteEmptySplitter(this);
}
return true;
}
qFatal("Unable to hide SectionContent, don't know this one 8-/");
return false;
}
QMenu* ContainerWidget::createContextMenu() const
{
// Fill map with actions (sorted by key!)
QMap<QString, QAction*> actions;
// Visible contents of sections
for (int i = 0; i < _sections.size(); ++i)
{
const SectionWidget* sw = _sections.at(i);
const QList<SectionContent::RefPtr>& contents = sw->contents();
foreach (const SectionContent::RefPtr& sc, contents)
{
QAction* a = new QAction(QIcon(), sc->uniqueName(), NULL);
a->setObjectName(QString("ads-action-sc-%1").arg(QString::number(sc->uid())));
a->setProperty("uid", sc->uid());
a->setProperty("type", "section");
a->setCheckable(true);
a->setChecked(c->titleWidget()->isVisible());
a->setChecked(true);
#if QT_VERSION >= 0x050000
QObject::connect(a, &QAction::toggled, this, &ContainerWidget::onActionToggleSectionContentVisibility);
#else
QObject::connect(a, SIGNAL(toggled(bool)), this, SLOT(onActionToggleSectionContentVisibility(bool)));
#endif
actions.insert(a->text(), a);
}
}
// Contents of FloatingWidgets
if (_floatings.size())
// Hidden contents of sections
QHashIterator<int, HiddenSectionItem> hiddenIter(_hiddenSectionContents);
while (hiddenIter.hasNext())
{
if (m->actions().size())
m->addSeparator();
for (int i = 0; i < _floatings.size(); ++i)
{
FloatingWidget* fw = _floatings.at(i);
SectionContent::RefPtr c = fw->content();
QAction* a = m->addAction(QIcon(), c->uniqueName());
a->setProperty("uid", c->uid());
a->setProperty("type", "floating");
a->setCheckable(true);
a->setChecked(fw->isVisible());
hiddenIter.next();
const SectionContent::RefPtr sc = hiddenIter.value().data.content;
QAction* a = new QAction(QIcon(), sc->uniqueName(), NULL);
a->setObjectName(QString("ads-action-sc-%1").arg(QString::number(sc->uid())));
a->setProperty("uid", sc->uid());
a->setProperty("type", "section");
a->setCheckable(true);
a->setChecked(false);
#if QT_VERSION >= 0x050000
QObject::connect(a, &QAction::toggled, fw, &FloatingWidget::setVisible);
QObject::connect(a, &QAction::toggled, this, &ContainerWidget::onActionToggleSectionContentVisibility);
#else
QObject::connect(a, SIGNAL(toggled(bool)), fw, SLOT(setVisible(bool)));
QObject::connect(a, SIGNAL(toggled(bool)), this, SLOT(onActionToggleSectionContentVisibility(bool)));
#endif
}
actions.insert(a->text(), a);
}
// Floating contents
for (int i = 0; i < _floatings.size(); ++i)
{
const FloatingWidget* fw = _floatings.at(i);
const SectionContent::RefPtr sc = fw->content();
QAction* a = new QAction(QIcon(), sc->uniqueName(), NULL);
a->setObjectName(QString("ads-action-sc-%1").arg(QString::number(sc->uid())));
a->setProperty("uid", sc->uid());
a->setProperty("type", "floating");
a->setCheckable(true);
a->setChecked(fw->isVisible());
#if QT_VERSION >= 0x050000
QObject::connect(a, &QAction::toggled, fw, &FloatingWidget::setVisible);
#else
QObject::connect(a, SIGNAL(toggled(bool)), fw, SLOT(setVisible(bool)));
#endif
actions.insert(a->text(), a);
}
// Create menu from "actions"
QMenu* m = new QMenu(NULL);
m->addActions(actions.values());
return m;
}
@ -208,15 +320,21 @@ bool ContainerWidget::restoreState(const QByteArray& data)
// What should we do with a drunken sailor.. what should.. erm..
// .. we do with the left-contents?
if (!_sections.isEmpty())
// We might need to add them into the hidden-list, if no other sections are available
for (int i = 0; i < leftContents.count(); ++i)
{
for (int i = 0; i < leftContents.count(); ++i)
const SectionContent::RefPtr sc = leftContents.at(i);
InternalContentData data;
this->takeContent(sc, data);
if (sections.isEmpty())
{
const SectionContent::RefPtr sc = leftContents.at(i);
InternalContentData data;
this->takeContent(sc, data);
sections.first()->addContent(sc);
HiddenSectionItem hsi;
hsi.data = data;
_hiddenSectionContents.insert(sc->uid(), hsi);
}
else
sections.first()->addContent(sc);
}
}
@ -514,13 +632,34 @@ void ContainerWidget::saveSectionWidgets(QDataStream& out, QWidget* widget) cons
}
else if ((sw = dynamic_cast<SectionWidget*>(widget)) != NULL)
{
const QList<SectionContent::RefPtr>& contents = sw->contents();
QList<HiddenSectionItem> hiddenContents;
QHashIterator<int, HiddenSectionItem> iter(_hiddenSectionContents);
while (iter.hasNext())
{
iter.next();
const HiddenSectionItem& hsi = iter.value();
if (hsi.preferredSectionId != sw->uid())
continue;
hiddenContents.append(hsi);
}
out << 2; // Type = SectionWidget
out << sw->currentIndex();
out << sw->contents().count();
const QList<SectionContent::RefPtr>& contents = sw->contents();
out << contents.count() + hiddenContents.count();
for (int i = 0; i < contents.count(); ++i)
{
out << contents[i]->uniqueName();
out << contents[i]->uniqueName(); // Unique name
out << (bool) true; // Visiblity
out << i; // Index
}
for (int i = 0; i < hiddenContents.count(); ++i)
{
out << hiddenContents.at(i).data.content->uniqueName();
out << (bool) false;
out << hiddenContents.at(i).preferredSectionIndex;
}
}
}
@ -612,18 +751,30 @@ bool ContainerWidget::restoreSectionWidgets(QDataStream& in, QSplitter* currentS
{
QString uname;
in >> uname;
bool visible = false;
in >> visible;
int preferredIndex = -1;
in >> preferredIndex;
const SectionContent::RefPtr sc = SectionContent::LookupMapByName.value(uname).toStrongRef();
if (!sc)
{
qWarning() << "Can not find SectionContent:" << uname;
continue;
}
InternalContentData data;
if (!this->takeContent(sc, data))
continue;
this->takeContent(sc, data);
sw->addContent(sc);
if (visible)
sw->addContent(sc);
else
{
HiddenSectionItem hsi;
hsi.preferredSectionId = sw->uid();
hsi.preferredSectionIndex = preferredIndex;
hsi.data = data;
_hiddenSectionContents.insert(sc->uid(), hsi);
}
}
if (sw->contents().isEmpty())
{
@ -672,7 +823,16 @@ void ContainerWidget::onActionToggleSectionContentVisibility(bool visible)
if (!a)
return;
const int uid = a->property("uid").toInt();
qDebug() << "Change visibility of" << uid << visible;
const SectionContent::RefPtr sc = SectionContent::LookupMap.value(uid).toStrongRef();
if (sc.isNull())
{
qCritical() << "Can not find content by ID" << uid;
return;
}
if (visible)
showSectionContent(sc);
else
hideSectionContent(sc);
}
ADS_NAMESPACE_END

View File

@ -1,5 +1,6 @@
#include "ads/SectionContentWidget.h"
#include <QDebug>
#include <QBoxLayout>
ADS_NAMESPACE_BEGIN
@ -15,4 +16,9 @@ SectionContentWidget::SectionContentWidget(SectionContent::RefPtr c, QWidget* pa
setLayout(l);
}
SectionContentWidget::~SectionContentWidget()
{
qDebug() << Q_FUNC_INFO;
}
ADS_NAMESPACE_END

View File

@ -38,6 +38,11 @@ SectionTitleWidget::SectionTitleWidget(SectionContent::RefPtr content, QWidget*
setLayout(l);
}
SectionTitleWidget::~SectionTitleWidget()
{
qDebug() << Q_FUNC_INFO;
}
bool SectionTitleWidget::isActiveTab() const
{
return _activeTab;

View File

@ -9,6 +9,7 @@
#include <QPainter>
#include <QStyle>
#include <QSplitter>
#include <QPushButton>
#if defined(ADS_ANIMATIONS_ENABLED)
#include <QGraphicsDropShadowEffect>
@ -31,7 +32,10 @@ QHash<ContainerWidget*, QHash<int, SectionWidget*> > SectionWidget::LookupMapByC
SectionWidget::SectionWidget(ContainerWidget* parent) :
QFrame(parent),
_uid(NextUid++),
_container(parent)
_container(parent),
_tabsLayout(NULL),
_contentsLayout(NULL),
_mousePressTitleWidget(NULL)
{
QBoxLayout* l = new QBoxLayout(QBoxLayout::TopToBottom);
l->setContentsMargins(0, 0, 0, 0);
@ -44,6 +48,19 @@ SectionWidget::SectionWidget(ContainerWidget* parent) :
_tabsLayout->addStretch(1);
l->addLayout(_tabsLayout);
QPushButton* closeButton = new QPushButton();
closeButton->setObjectName("closeButton");
closeButton->setFlat(true);
closeButton->setIcon(style()->standardIcon(QStyle::SP_TitleBarCloseButton));
closeButton->setToolTip(tr("Close"));
closeButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
_tabsLayout->addWidget(closeButton);
#if QT_VERSION >= 0x050000
QObject::connect(closeButton, &QPushButton::clicked, this, &SectionWidget::onCloseButtonClicked);
#else
QObject::connect(closeButton, SIGNAL(clicked(bool)), this, SLOT(onCloseButtonClicked()));
#endif
_contentsLayout = new QStackedLayout();
_contentsLayout->setContentsMargins(0, 0, 0, 0);
_contentsLayout->setSpacing(0);
@ -58,7 +75,6 @@ SectionWidget::SectionWidget(ContainerWidget* parent) :
LookupMap.insert(_uid, this);
LookupMapByContainer[_container].insert(_uid, this);
// _container->_sections.append(this);
}
SectionWidget::~SectionWidget()
@ -97,13 +113,13 @@ QRect SectionWidget::contentAreaGeometry() const
return _contentsLayout->geometry();
}
void SectionWidget::addContent(SectionContent::RefPtr c)
void SectionWidget::addContent(const SectionContent::RefPtr& c)
{
_contents.append(c);
SectionTitleWidget* title = new SectionTitleWidget(c, NULL);
_sectionTitles.append(title);
_tabsLayout->insertWidget(_tabsLayout->count() - 1, title);
_tabsLayout->insertWidget(_tabsLayout->count() - 2, title);
#if QT_VERSION >= 0x050000
QObject::connect(title, &SectionTitleWidget::clicked, this, &SectionWidget::onSectionTitleClicked);
#else
@ -127,7 +143,7 @@ void SectionWidget::addContent(const InternalContentData& data, bool autoActivat
_contents.append(data.content);
_sectionTitles.append(data.titleWidget);
_tabsLayout->insertWidget(_tabsLayout->count() - 1, data.titleWidget);
_tabsLayout->insertWidget(_tabsLayout->count() - 2, data.titleWidget);
#if QT_VERSION >= 0x050000
QObject::connect(data.titleWidget, &SectionTitleWidget::clicked, this, &SectionWidget::onSectionTitleClicked);
#else
@ -142,12 +158,9 @@ void SectionWidget::addContent(const InternalContentData& data, bool autoActivat
setCurrentIndex(0);
// Switch to newest.
else if (autoActivate)
setCurrentIndex(_contentsLayout->count() - 1);
setCurrentIndex(_contents.count() - 1);
}
// take removes a widget from the SectionWidget but does not delete
// the used SectionTitle- and SectionContent-Widget. Instead it returns
// these objects.
bool SectionWidget::takeContent(int uid, InternalContentData& data)
{
// Find SectionContent.
@ -170,6 +183,7 @@ bool SectionWidget::takeContent(int uid, InternalContentData& data)
{
_tabsLayout->removeWidget(title);
title->disconnect(this);
title->setParent(_container);
}
// Content wrapper widget (CONTENT)
@ -178,6 +192,7 @@ bool SectionWidget::takeContent(int uid, InternalContentData& data)
{
_contentsLayout->removeWidget(content);
content->disconnect(this);
content->setParent(_container);
}
// Select the previous tab as activeTab.
@ -195,7 +210,7 @@ bool SectionWidget::takeContent(int uid, InternalContentData& data)
return !data.content.isNull();
}
int SectionWidget::indexOfContent(SectionContent::RefPtr c) const
int SectionWidget::indexOfContent(const SectionContent::RefPtr& c) const
{
return _contents.indexOf(c);
}
@ -258,10 +273,13 @@ void SectionWidget::setCurrentIndex(int index)
if (item->widget())
{
SectionTitleWidget* stw = dynamic_cast<SectionTitleWidget*>(item->widget());
if (i == index)
stw->setActiveTab(true);
else
stw->setActiveTab(false);
if (stw)
{
if (i == index)
stw->setActiveTab(true);
else
stw->setActiveTab(false);
}
}
}
@ -269,15 +287,6 @@ void SectionWidget::setCurrentIndex(int index)
_contentsLayout->setCurrentIndex(index);
}
void SectionWidget::paintEvent(QPaintEvent* e)
{
QFrame::paintEvent(e);
// QPainter p(this);
// auto r = rect();
// p.drawText(rect(), Qt::AlignCenter | Qt::AlignVCenter, QString("x=%1; y=%2; w=%3; h=%4").arg(r.x()).arg(r.y()).arg(r.width()).arg(r.height()));
}
void SectionWidget::onSectionTitleClicked()
{
SectionTitleWidget* stw = qobject_cast<SectionTitleWidget*>(sender());
@ -288,4 +297,16 @@ void SectionWidget::onSectionTitleClicked()
}
}
void SectionWidget::onCloseButtonClicked()
{
qDebug() << Q_FUNC_INFO << currentIndex();
const int index = currentIndex();
if (index < 0 || index > _contents.size() - 1)
return;
SectionContent::RefPtr sc = _contents.at(index);
if (sc.isNull())
return;
_container->hideSectionContent(sc);
}
ADS_NAMESPACE_END

View File

@ -17,6 +17,10 @@ Basic usage of QWidgets an QLayouts and using basic styles as much as possible.
## Build
Open the `build.pro` with QtCreator and start the build, that's it.
## Release & Development
The master branch is not guaranteed to be stable or does even build, since it is the main working branch.
If you want a version that builds you should always use a release/beta tag.
## Notes
- *SectionContent* class may safe a "size-type" property, which defines how the size of the widget should be handled.
- PerCent: Resize in proportion to other widgets.
@ -34,6 +38,8 @@ Sorted by priority
- [x] Working with outer-edge-drops sometimes leaves empty splitters #BUG
- [x] Clean up of unused e.g. count()<=1 QSplitters doesn't work well #BUG
- [ ] Show close button on right corner of SectionWidget. How to safe last section position?
- [ ] Serialize state of `_hiddenSectionContents`
- [ ] `ContainerWidget::showSectionContent` needs to insert the SC at the correct preferred position of SW
- [ ] Empty splitters, if only 2 or 1 items are in container
### Some day...