Init commit for GitHub.
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
#include "ads.h"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QSplitter>
|
||||
|
||||
#include "container_widget.h"
|
||||
|
||||
const QString DragData::MIMETYPE = QString("qt/ads-dragdata");
|
||||
|
||||
ADS_NAMESPACE_BEGIN
|
||||
|
||||
ContainerWidget* findParentContainerWidget(QWidget* w)
|
||||
{
|
||||
ContainerWidget* cw = 0;
|
||||
QWidget* next = w;
|
||||
do
|
||||
{
|
||||
if ((cw = dynamic_cast<ContainerWidget*>(next)) != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
next = next->parentWidget();
|
||||
}
|
||||
while (next);
|
||||
return cw;
|
||||
}
|
||||
|
||||
SectionWidget* findParentSectionWidget(class QWidget* w)
|
||||
{
|
||||
SectionWidget* cw = 0;
|
||||
QWidget* next = w;
|
||||
do
|
||||
{
|
||||
if ((cw = dynamic_cast<SectionWidget*>(next)) != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
next = next->parentWidget();
|
||||
}
|
||||
while (next);
|
||||
return cw;
|
||||
}
|
||||
|
||||
QSplitter* findParentSplitter(class QWidget* w)
|
||||
{
|
||||
QSplitter* cw = 0;
|
||||
QWidget* next = w;
|
||||
do
|
||||
{
|
||||
if ((cw = dynamic_cast<QSplitter*>(next)) != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
next = next->parentWidget();
|
||||
}
|
||||
while (next);
|
||||
return cw;
|
||||
}
|
||||
|
||||
ADS_NAMESPACE_END
|
||||
@@ -0,0 +1,85 @@
|
||||
#ifndef ADS_H
|
||||
#define ADS_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDataStream>
|
||||
#include <QIODevice>
|
||||
#include <QSharedPointer>
|
||||
class QSplitter;
|
||||
|
||||
/*
|
||||
* ADS - Advanced Docking System for Qt
|
||||
* Developed by Manuel Freiholz
|
||||
*/
|
||||
|
||||
#define ADS_NAMESPACE_BEGIN namespace ads {
|
||||
#define ADS_NAMESPACE_END }
|
||||
|
||||
// Width of the native window frame border (based on OS).
|
||||
#define ADS_WINDOW_FRAME_BORDER_WIDTH 7
|
||||
|
||||
// Indicates whether ADS should include animations.
|
||||
//#define ADS_ANIMATIONS_ENABLED 1
|
||||
//#define ADS_ANIMATION_DURATION 150
|
||||
|
||||
// DragData contains information about dragged contents (SectionContent).
|
||||
// e.g. from where is has been dragged.
|
||||
class DragData
|
||||
{
|
||||
public:
|
||||
static const QString MIMETYPE;
|
||||
|
||||
QByteArray serialize() const
|
||||
{
|
||||
QByteArray ba;
|
||||
QDataStream out(&ba, QIODevice::WriteOnly);
|
||||
out << contentUid;
|
||||
out << sectionUid;
|
||||
return ba;
|
||||
}
|
||||
|
||||
void deserialize(const QByteArray& bytes)
|
||||
{
|
||||
QDataStream in(bytes);
|
||||
in >> contentUid;
|
||||
in >> sectionUid;
|
||||
}
|
||||
|
||||
QString toString() const
|
||||
{
|
||||
return QString("content-uid=%1; section-uid=%2").arg(contentUid).arg(sectionUid);
|
||||
}
|
||||
|
||||
public:
|
||||
int contentUid;
|
||||
int sectionUid;
|
||||
};
|
||||
|
||||
ADS_NAMESPACE_BEGIN
|
||||
|
||||
enum DropArea
|
||||
{
|
||||
TopDropArea,
|
||||
RightDropArea,
|
||||
BottomDropArea,
|
||||
LeftDropArea,
|
||||
CenterDropArea,
|
||||
InvalidDropArea
|
||||
};
|
||||
|
||||
class InternalContentData
|
||||
{
|
||||
public:
|
||||
InternalContentData() : titleWidget(0), contentWidget(0) {}
|
||||
QSharedPointer<class SectionContent> content;
|
||||
class SectionTitleWidget* titleWidget;
|
||||
class SectionContentWidget* contentWidget;
|
||||
};
|
||||
|
||||
class ContainerWidget* findParentContainerWidget(class QWidget* w);
|
||||
class SectionWidget* findParentSectionWidget(class QWidget* w);
|
||||
QSplitter* findParentSplitter(class QWidget* w);
|
||||
|
||||
ADS_NAMESPACE_END
|
||||
|
||||
#endif // ADC_H
|
||||
@@ -0,0 +1,272 @@
|
||||
#include "container_widget.h"
|
||||
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
#include <QContextMenuEvent>
|
||||
#include <QMenu>
|
||||
#include <QSplitter>
|
||||
|
||||
ADS_NAMESPACE_BEGIN
|
||||
|
||||
// Static Helper //////////////////////////////////////////////////////
|
||||
|
||||
static QSplitter* newSplitter(Qt::Orientation orientation = Qt::Horizontal)
|
||||
{
|
||||
QSplitter* s = new QSplitter();
|
||||
s->setChildrenCollapsible(false);
|
||||
s->setOpaqueResize(false);
|
||||
s->setOrientation(orientation);
|
||||
return s;
|
||||
}
|
||||
|
||||
//static void deleteEmptySplitter(ContainerWidget* container)
|
||||
//{
|
||||
// auto splitters = container->findChildren<QSplitter*>();
|
||||
// for (auto i = 0; i < splitters.count(); ++i)
|
||||
// {
|
||||
// if (splitters[i]->count() == 0 && container->_splitter != splitters[i])
|
||||
// {
|
||||
// qDebug() << "Delete empty QSplitter";
|
||||
// splitters[i]->deleteLater();
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
|
||||
ContainerWidget::ContainerWidget(QWidget *parent) :
|
||||
QFrame(parent),
|
||||
_mainLayout(NULL),
|
||||
_orientation(Qt::Horizontal),
|
||||
_splitter(NULL)
|
||||
{
|
||||
_mainLayout = new QGridLayout();
|
||||
_mainLayout->setContentsMargins(9, 9, 9, 9);
|
||||
_mainLayout->setSpacing(9);
|
||||
setLayout(_mainLayout);
|
||||
}
|
||||
|
||||
Qt::Orientation ContainerWidget::orientation() const
|
||||
{
|
||||
return _orientation;
|
||||
}
|
||||
|
||||
void ContainerWidget::setOrientation(Qt::Orientation orientation)
|
||||
{
|
||||
if (_orientation != orientation)
|
||||
{
|
||||
_orientation = orientation;
|
||||
emit orientationChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void ContainerWidget::dropContent(const InternalContentData& data, SectionWidget* targetSection, DropArea area)
|
||||
{
|
||||
if (!targetSection)
|
||||
{
|
||||
qWarning() << "Drop on invalid targetSection";
|
||||
return;
|
||||
}
|
||||
|
||||
auto targetSectionSplitter = findParentSplitter(targetSection);
|
||||
|
||||
// Drop logic based on area.
|
||||
switch (area)
|
||||
{
|
||||
case TopDropArea:
|
||||
{
|
||||
auto sw = new SectionWidget(this);
|
||||
sw->addContent(data, true);
|
||||
if (targetSectionSplitter->orientation() == Qt::Vertical)
|
||||
{
|
||||
auto index = targetSectionSplitter->indexOf(targetSection);
|
||||
targetSectionSplitter->insertWidget(index, sw);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto index = targetSectionSplitter->indexOf(targetSection);
|
||||
auto s = newSplitter(Qt::Vertical);
|
||||
s->addWidget(sw);
|
||||
s->addWidget(targetSection);
|
||||
targetSectionSplitter->insertWidget(index, s);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case RightDropArea:
|
||||
{
|
||||
auto sw = new SectionWidget(this);
|
||||
sw->addContent(data, true);
|
||||
if (targetSectionSplitter->orientation() == Qt::Horizontal)
|
||||
{
|
||||
auto index = targetSectionSplitter->indexOf(targetSection);
|
||||
targetSectionSplitter->insertWidget(index + 1, sw);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto index = targetSectionSplitter->indexOf(targetSection);
|
||||
auto s = newSplitter(Qt::Horizontal);
|
||||
s->addWidget(targetSection);
|
||||
s->addWidget(sw);
|
||||
targetSectionSplitter->insertWidget(index, s);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case BottomDropArea:
|
||||
{
|
||||
auto sw = new SectionWidget(this);
|
||||
sw->addContent(data, true);
|
||||
if (targetSectionSplitter->orientation() == Qt::Vertical)
|
||||
{
|
||||
auto index = targetSectionSplitter->indexOf(targetSection);
|
||||
targetSectionSplitter->insertWidget(index + 1, sw);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto index = targetSectionSplitter->indexOf(targetSection);
|
||||
auto s = newSplitter(Qt::Vertical);
|
||||
s->addWidget(targetSection);
|
||||
s->addWidget(sw);
|
||||
targetSectionSplitter->insertWidget(index, s);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LeftDropArea:
|
||||
{
|
||||
auto sw = new SectionWidget(this);
|
||||
sw->addContent(data, true);
|
||||
if (targetSectionSplitter->orientation() == Qt::Horizontal)
|
||||
{
|
||||
auto index = targetSectionSplitter->indexOf(targetSection);
|
||||
targetSectionSplitter->insertWidget(index, sw);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto s = newSplitter(Qt::Horizontal);
|
||||
s->addWidget(sw);
|
||||
auto index = targetSectionSplitter->indexOf(targetSection);
|
||||
targetSectionSplitter->insertWidget(index, s);
|
||||
s->addWidget(targetSection);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CenterDropArea:
|
||||
{
|
||||
targetSection->addContent(data, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ContainerWidget::addSection(SectionWidget* section)
|
||||
{
|
||||
// Create default splitter.
|
||||
if (!_splitter)
|
||||
{
|
||||
_splitter = new QSplitter(_orientation);
|
||||
_splitter->setChildrenCollapsible(false);
|
||||
_splitter->setOpaqueResize(false);
|
||||
_mainLayout->addWidget(_splitter, 0, 0);
|
||||
}
|
||||
if (_splitter->indexOf(section) != -1)
|
||||
{
|
||||
qWarning() << Q_FUNC_INFO << QString("Section has already been added");
|
||||
return;
|
||||
}
|
||||
_splitter->addWidget(section);
|
||||
}
|
||||
|
||||
void ContainerWidget::splitSections(SectionWidget* s1, SectionWidget* s2, Qt::Orientation orientation)
|
||||
{
|
||||
addSection(s1);
|
||||
addSection(s2);
|
||||
//_sections.append(s2);
|
||||
|
||||
auto currentSplitter = findParentSplitter(s1);
|
||||
if (currentSplitter)
|
||||
{
|
||||
const int index = currentSplitter->indexOf(s1);
|
||||
auto splitter = new QSplitter(orientation, this);
|
||||
splitter->setChildrenCollapsible(false);
|
||||
splitter->setOpaqueResize(false);
|
||||
splitter->addWidget(s1);
|
||||
splitter->addWidget(s2);
|
||||
currentSplitter->insertWidget(index, splitter);
|
||||
}
|
||||
// _mainLayout->replaceWidget(section1, splitter);
|
||||
// splitter->addWidget(section1);
|
||||
// splitter->addWidget(section2);
|
||||
}
|
||||
|
||||
SectionWidget* ContainerWidget::sectionAt(const QPoint& pos) const
|
||||
{
|
||||
auto gpos = mapToGlobal(pos);
|
||||
for (int i = 0; i < _sections.size(); ++i)
|
||||
{
|
||||
auto sw = _sections[i];
|
||||
if (sw->rect().contains(sw->mapFromGlobal(gpos)))
|
||||
{
|
||||
return sw;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
QRect ContainerWidget::outerTopDropRect() const
|
||||
{
|
||||
auto r = rect();
|
||||
auto h = r.height() / 100 * 5;
|
||||
return QRect(r.left(), r.top(), r.width(), h);
|
||||
}
|
||||
|
||||
QRect ContainerWidget::outerRightDropRect() const
|
||||
{
|
||||
auto r = rect();
|
||||
auto w = r.width() / 100 * 5;
|
||||
return QRect(r.right() - w, r.top(), w, r.height());
|
||||
}
|
||||
|
||||
QRect ContainerWidget::outerBottomDropRect() const
|
||||
{
|
||||
auto r = rect();
|
||||
auto h = r.height() / 100 * 5;
|
||||
return QRect(r.left(), r.bottom() - h, r.width(), h);
|
||||
}
|
||||
|
||||
QRect ContainerWidget::outerLeftDropRect() const
|
||||
{
|
||||
auto r = rect();
|
||||
auto w = r.width() / 100 * 5;
|
||||
return QRect(r.left(), r.top(), w, r.height());
|
||||
}
|
||||
|
||||
void ContainerWidget::paintEvent(QPaintEvent* e)
|
||||
{
|
||||
QFrame::paintEvent(e);
|
||||
|
||||
// QPainter p(this);
|
||||
// p.fillRect(outerTopDropRect(), QBrush(QColor(Qt::red)));
|
||||
// p.fillRect(outerRightDropRect(), QBrush(QColor(Qt::green)));
|
||||
// p.fillRect(outerBottomDropRect(), QBrush(QColor(Qt::blue)));
|
||||
// p.fillRect(outerLeftDropRect(), QBrush(QColor(Qt::yellow)));
|
||||
}
|
||||
|
||||
void ContainerWidget::contextMenuEvent(QContextMenuEvent*)
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
// if (_contents.size() <= 0)
|
||||
// {
|
||||
// QFrame::contextMenuEvent(e);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // Menu with all available contents.
|
||||
// QMenu m;
|
||||
// for (int i = 0; i < _contents.size(); ++i)
|
||||
// {
|
||||
// auto c = _contents[i];
|
||||
// m.addAction(QIcon(), QString("Content %1").arg(c->uid()));
|
||||
// }
|
||||
// m.exec();
|
||||
}
|
||||
|
||||
ADS_NAMESPACE_END
|
||||
@@ -0,0 +1,69 @@
|
||||
#ifndef ADS_CONTAINERWIDGET_H
|
||||
#define ADS_CONTAINERWIDGET_H
|
||||
|
||||
#include <QFrame>
|
||||
#include <QGridLayout>
|
||||
#include <QPoint>
|
||||
#include <QList>
|
||||
class QSplitter;
|
||||
|
||||
#include "ads.h"
|
||||
#include "section_content.h"
|
||||
#include "section_widget.h"
|
||||
#include "floating_widget.h"
|
||||
|
||||
ADS_NAMESPACE_BEGIN
|
||||
|
||||
// ContainerWidget is the main container to provide the docking
|
||||
// functionality. It manages mulitple Sections and all possible areas.
|
||||
class ContainerWidget : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged)
|
||||
|
||||
friend class SectionWidget;
|
||||
|
||||
public:
|
||||
explicit ContainerWidget(QWidget *parent = NULL);
|
||||
|
||||
Qt::Orientation orientation() const;
|
||||
void setOrientation(Qt::Orientation orientation);
|
||||
|
||||
void dropContent(const InternalContentData& data, SectionWidget* targetSection, DropArea area);
|
||||
|
||||
void addSection(SectionWidget* section);
|
||||
void splitSections(SectionWidget* section1, SectionWidget* section2, Qt::Orientation orientation = Qt::Horizontal);
|
||||
SectionWidget* sectionAt(const QPoint& pos) const;
|
||||
|
||||
// Drop areas for the ContainerWidget
|
||||
QRect outerTopDropRect() const;
|
||||
QRect outerRightDropRect() const;
|
||||
QRect outerBottomDropRect() const;
|
||||
QRect outerLeftDropRect() const;
|
||||
|
||||
protected:
|
||||
virtual void paintEvent(QPaintEvent*);
|
||||
virtual void contextMenuEvent(QContextMenuEvent*);
|
||||
|
||||
private: // Make private!
|
||||
|
||||
|
||||
signals:
|
||||
void orientationChanged();
|
||||
|
||||
public:
|
||||
// Existing sections.
|
||||
// SectionWidgets are always visible.
|
||||
QList<SectionWidget*> _sections;
|
||||
|
||||
// All currently active Floatingwidgets.
|
||||
QList<FloatingWidget*> _floatingWidgets;
|
||||
|
||||
// Layout stuff
|
||||
QGridLayout* _mainLayout;
|
||||
Qt::Orientation _orientation; ///< Default orientation of new sections.
|
||||
QSplitter* _splitter; ///< Default/main splitter.
|
||||
};
|
||||
|
||||
ADS_NAMESPACE_END
|
||||
#endif
|
||||
@@ -0,0 +1,203 @@
|
||||
#include "drop_overlay.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QPointer>
|
||||
#include <QPaintEvent>
|
||||
#include <QResizeEvent>
|
||||
#include <QMoveEvent>
|
||||
#include <QPainter>
|
||||
#include <QGridLayout>
|
||||
#include <QCursor>
|
||||
#include <QtGui/QIcon>
|
||||
#include <QtWidgets/QLabel>
|
||||
|
||||
ADS_NAMESPACE_BEGIN
|
||||
|
||||
// Helper /////////////////////////////////////////////////////////////
|
||||
|
||||
static QWidget* createDropWidget(const QString& img)
|
||||
{
|
||||
auto label = new QLabel();
|
||||
label->setObjectName("DropAreaLabel");
|
||||
label->setPixmap(QPixmap(img));
|
||||
return label;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
|
||||
DropOverlay::DropOverlay(QWidget *parent) :
|
||||
QFrame(parent),
|
||||
_splitAreas(NULL)
|
||||
{
|
||||
setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
setWindowFlags(windowFlags() | Qt::Tool);
|
||||
setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
|
||||
setWindowOpacity(0.2);
|
||||
|
||||
auto l = new QBoxLayout(QBoxLayout::TopToBottom);
|
||||
l->setContentsMargins(0, 0, 0, 0);
|
||||
l->setSpacing(0);
|
||||
setLayout(l);
|
||||
|
||||
_splitAreas = new DropSplitAreas(parent);
|
||||
_splitAreas->move(pos());
|
||||
_splitAreas->resize(size());
|
||||
_splitAreas->setVisible(true);
|
||||
}
|
||||
|
||||
DropOverlay::~DropOverlay()
|
||||
{
|
||||
if (_splitAreas)
|
||||
{
|
||||
delete _splitAreas;
|
||||
_splitAreas = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
DropArea DropOverlay::cursorLocation() const
|
||||
{
|
||||
auto loc = InvalidDropArea;
|
||||
if (_splitAreas)
|
||||
{
|
||||
loc = _splitAreas->cursorLocation();
|
||||
}
|
||||
return loc;
|
||||
}
|
||||
|
||||
void DropOverlay::paintEvent(QPaintEvent*)
|
||||
{
|
||||
QPainter p(this);
|
||||
|
||||
// Draw rect over the entire size + border.
|
||||
auto r = rect();
|
||||
r.setWidth(r.width() - 1);
|
||||
r.setHeight(r.height() - 1);
|
||||
|
||||
p.fillRect(r, QBrush(QColor(0, 100, 255), Qt::Dense4Pattern));
|
||||
p.setBrush(QBrush(QColor(0, 100, 255)));
|
||||
p.drawRect(r);
|
||||
}
|
||||
|
||||
void DropOverlay::resizeEvent(QResizeEvent* e)
|
||||
{
|
||||
// Keep it in center of DropOverlay
|
||||
if (_splitAreas)
|
||||
{
|
||||
_splitAreas->resize(e->size());
|
||||
}
|
||||
}
|
||||
|
||||
void DropOverlay::moveEvent(QMoveEvent* e)
|
||||
{
|
||||
// Keep it in center of DropOverlay
|
||||
if (_splitAreas)
|
||||
{
|
||||
_splitAreas->move(e->pos());
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
|
||||
DropSplitAreas::DropSplitAreas(QWidget* parent) : AbstractDropAreas(parent)
|
||||
{
|
||||
//setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
setAttribute(Qt::WA_TranslucentBackground);
|
||||
setWindowFlags(windowFlags() | Qt::Tool);
|
||||
setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
|
||||
|
||||
_top = createDropWidget(":/img/split-top.png");
|
||||
_right = createDropWidget(":/img/split-right.png");
|
||||
_bottom = createDropWidget(":/img/split-bottom.png");
|
||||
_left = createDropWidget(":/img/split-left.png");
|
||||
_center = createDropWidget(":/img/dock-center.png");
|
||||
|
||||
QGridLayout* grid = new QGridLayout();
|
||||
grid->setContentsMargins(0, 0, 0, 0);
|
||||
grid->setSpacing(6);
|
||||
grid->addWidget(_top, 0, 1, Qt::AlignHCenter | Qt::AlignBottom);
|
||||
grid->addWidget(_right, 1, 2, Qt::AlignLeft | Qt::AlignVCenter);
|
||||
grid->addWidget(_bottom, 2, 1, Qt::AlignHCenter | Qt::AlignTop);
|
||||
grid->addWidget(_left, 1, 0, Qt::AlignRight | Qt::AlignVCenter);
|
||||
grid->addWidget(_center, 1, 1, Qt::AlignCenter);
|
||||
|
||||
QBoxLayout* bl1 = new QBoxLayout(QBoxLayout::TopToBottom);
|
||||
bl1->setContentsMargins(0, 0, 0, 0);
|
||||
bl1->setSpacing(0);
|
||||
setLayout(bl1);
|
||||
|
||||
QBoxLayout* bl2 = new QBoxLayout(QBoxLayout::LeftToRight);
|
||||
bl2->setContentsMargins(0, 0, 0, 0);
|
||||
bl2->setSpacing(0);
|
||||
|
||||
bl1->addStretch(1);
|
||||
bl1->addLayout(bl2);
|
||||
bl2->addStretch(1);
|
||||
bl2->addLayout(grid, 0);
|
||||
bl2->addStretch(1);
|
||||
bl1->addStretch(1);
|
||||
}
|
||||
|
||||
DropArea DropSplitAreas::cursorLocation() const
|
||||
{
|
||||
auto loc = InvalidDropArea;
|
||||
auto pos = mapFromGlobal(QCursor::pos());
|
||||
if (_top->geometry().contains(pos))
|
||||
{
|
||||
loc = TopDropArea;
|
||||
}
|
||||
else if (_right->geometry().contains(pos))
|
||||
{
|
||||
loc = RightDropArea;
|
||||
}
|
||||
else if (_bottom->geometry().contains(pos))
|
||||
{
|
||||
loc = BottomDropArea;
|
||||
}
|
||||
else if (_left->geometry().contains(pos))
|
||||
{
|
||||
loc = LeftDropArea;
|
||||
}
|
||||
else if (_center->geometry().contains(pos))
|
||||
{
|
||||
loc = CenterDropArea;
|
||||
}
|
||||
return loc;
|
||||
}
|
||||
|
||||
// Globals ////////////////////////////////////////////////////////////
|
||||
|
||||
static QPointer<DropOverlay> MyOverlay;
|
||||
static QPointer<QWidget> MyOverlayParent;
|
||||
|
||||
DropArea showDropOverlay(QWidget* parent)
|
||||
{
|
||||
if (MyOverlay)
|
||||
{
|
||||
if (MyOverlayParent == parent)
|
||||
{
|
||||
// Hint: We could update geometry of overlay here.
|
||||
return MyOverlay->cursorLocation();
|
||||
}
|
||||
hideDropOverlay();
|
||||
}
|
||||
|
||||
// Create new overlay and move it directly over the "parent" widget.
|
||||
MyOverlay = new DropOverlay(parent);
|
||||
MyOverlay->resize(parent->size());
|
||||
MyOverlay->move(parent->mapToGlobal(parent->rect().topLeft()));
|
||||
MyOverlay->show();
|
||||
MyOverlayParent = parent;
|
||||
return MyOverlay->cursorLocation();
|
||||
}
|
||||
|
||||
void hideDropOverlay()
|
||||
{
|
||||
if (MyOverlay)
|
||||
{
|
||||
MyOverlay->hide();
|
||||
delete MyOverlay;
|
||||
MyOverlayParent.clear();
|
||||
}
|
||||
}
|
||||
|
||||
ADS_NAMESPACE_END
|
||||
@@ -0,0 +1,61 @@
|
||||
#ifndef DROP_OVERLAY_H
|
||||
#define DROP_OVERLAY_H
|
||||
|
||||
#include <QFrame>
|
||||
|
||||
#include "ads.h"
|
||||
|
||||
ADS_NAMESPACE_BEGIN
|
||||
|
||||
// DropOverlay paints a translucent rectangle over another widget.
|
||||
// It can also show different types of drop area indicators.
|
||||
class DropOverlay : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DropOverlay(QWidget* parent);
|
||||
virtual ~DropOverlay();
|
||||
DropArea cursorLocation() const;
|
||||
|
||||
protected:
|
||||
virtual void paintEvent(QPaintEvent *e);
|
||||
virtual void resizeEvent(QResizeEvent* e);
|
||||
virtual void moveEvent(QMoveEvent* e);
|
||||
|
||||
private:
|
||||
class DropSplitAreas* _splitAreas;
|
||||
};
|
||||
|
||||
// AbstractDropAreas is used as base for drop area indicator widgets.
|
||||
class AbstractDropAreas : public QWidget
|
||||
{
|
||||
public:
|
||||
AbstractDropAreas(QWidget* parent) : QWidget(parent) {}
|
||||
virtual DropArea cursorLocation() const = 0;
|
||||
};
|
||||
|
||||
// DropSplitAreas shows a cross with 5 different drop area possibilities.
|
||||
class DropSplitAreas : public AbstractDropAreas
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DropSplitAreas(QWidget* parent);
|
||||
virtual DropArea cursorLocation() const;
|
||||
|
||||
private:
|
||||
QWidget* _top;
|
||||
QWidget* _right;
|
||||
QWidget* _bottom;
|
||||
QWidget* _left;
|
||||
QWidget* _center;
|
||||
};
|
||||
|
||||
|
||||
DropArea showDropOverlay(QWidget* parent);
|
||||
void hideDropOverlay();
|
||||
|
||||
ADS_NAMESPACE_END
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
#include "floating_widget.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include <QSizePolicy>
|
||||
#include <QMouseEvent>
|
||||
#include <QStyle>
|
||||
|
||||
#include "section_title_widget.h"
|
||||
#include "section_content_widget.h"
|
||||
|
||||
ADS_NAMESPACE_BEGIN
|
||||
|
||||
FloatingWidget::FloatingWidget(SectionContent::RefPtr sc, SectionTitleWidget* titleWidget, SectionContentWidget* contentWidget, QWidget* parent) :
|
||||
QWidget(parent, Qt::CustomizeWindowHint | Qt::Tool),
|
||||
_content(sc),
|
||||
_titleWidget(titleWidget),
|
||||
_contentWidget(contentWidget)
|
||||
{
|
||||
auto l = new QBoxLayout(QBoxLayout::TopToBottom);
|
||||
l->setContentsMargins(0, 0, 0, 0);
|
||||
l->setSpacing(0);
|
||||
setLayout(l);
|
||||
|
||||
// Title + Controls
|
||||
_titleLayout = new QBoxLayout(QBoxLayout::LeftToRight);
|
||||
_titleLayout->addWidget(titleWidget, 1);
|
||||
|
||||
auto maximizeButton = new QPushButton();
|
||||
maximizeButton->setObjectName("maximizeButton");
|
||||
maximizeButton->setIcon(style()->standardIcon(QStyle::SP_TitleBarMaxButton));
|
||||
maximizeButton->setToolTip(tr("Maximize"));
|
||||
maximizeButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
_titleLayout->addWidget(maximizeButton);
|
||||
|
||||
auto closeButton = new QPushButton();
|
||||
closeButton->setObjectName("closeButton");
|
||||
closeButton->setIcon(style()->standardIcon(QStyle::SP_TitleBarCloseButton));
|
||||
closeButton->setToolTip(tr("Close"));
|
||||
closeButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
_titleLayout->addWidget(closeButton);
|
||||
QObject::connect(closeButton, &QPushButton::clicked, this, &FloatingWidget::close);
|
||||
|
||||
l->addLayout(_titleLayout, 0);
|
||||
|
||||
// Content
|
||||
l->addWidget(contentWidget, 1);
|
||||
contentWidget->show();
|
||||
}
|
||||
|
||||
InternalContentData FloatingWidget::takeContent()
|
||||
{
|
||||
InternalContentData data;
|
||||
data.content = _content;
|
||||
data.titleWidget = _titleWidget;
|
||||
data.contentWidget = _contentWidget;
|
||||
|
||||
_titleLayout->removeWidget(_titleWidget);
|
||||
_titleWidget = NULL;
|
||||
|
||||
layout()->removeWidget(_contentWidget);
|
||||
_contentWidget = NULL;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void FloatingWidget::closeEvent(QCloseEvent*)
|
||||
{
|
||||
}
|
||||
|
||||
ADS_NAMESPACE_END
|
||||
@@ -0,0 +1,39 @@
|
||||
#ifndef FLOATINGWIDGET_H
|
||||
#define FLOATINGWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QFrame>
|
||||
|
||||
#include "ads.h"
|
||||
#include "section_content.h"
|
||||
|
||||
class QBoxLayout;
|
||||
|
||||
ADS_NAMESPACE_BEGIN
|
||||
|
||||
class SectionTitleWidget;
|
||||
class SectionContentWidget;
|
||||
|
||||
// FloatingWidget holds and displays SectionContent as a floating window.
|
||||
// It can be resized, moved and dropped back into a SectionWidget.
|
||||
class FloatingWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FloatingWidget(SectionContent::RefPtr sc, SectionTitleWidget* titleWidget, SectionContentWidget* contentWidget, QWidget* parent = nullptr);
|
||||
InternalContentData takeContent();
|
||||
|
||||
protected:
|
||||
virtual void closeEvent(QCloseEvent* e);
|
||||
|
||||
private:
|
||||
SectionContent::RefPtr _content;
|
||||
SectionTitleWidget* _titleWidget;
|
||||
SectionContentWidget* _contentWidget;
|
||||
|
||||
QBoxLayout* _titleLayout;
|
||||
};
|
||||
|
||||
ADS_NAMESPACE_END
|
||||
#endif
|
||||
@@ -0,0 +1,56 @@
|
||||
#include "section_content.h"
|
||||
|
||||
#include <QLabel>
|
||||
|
||||
ADS_NAMESPACE_BEGIN
|
||||
|
||||
int SectionContent::NextUid = 1;
|
||||
QHash<int, SectionContent*> SectionContent::LookupMap;
|
||||
|
||||
SectionContent::SectionContent(QWidget* title, QWidget* content, const QString& uniqueName) :
|
||||
_uid(NextUid++),
|
||||
_uniqueName(uniqueName)
|
||||
{
|
||||
_title = title;
|
||||
_content = content;
|
||||
LookupMap.insert(_uid, this);
|
||||
}
|
||||
|
||||
SectionContent::~SectionContent()
|
||||
{
|
||||
LookupMap.remove(_uid);
|
||||
delete _title;
|
||||
delete _content;
|
||||
}
|
||||
|
||||
int SectionContent::uid() const
|
||||
{
|
||||
return _uid;
|
||||
}
|
||||
|
||||
QString SectionContent::uniqueName() const
|
||||
{
|
||||
return _uniqueName;
|
||||
}
|
||||
|
||||
QWidget* SectionContent::titleWidget() const
|
||||
{
|
||||
return _title;
|
||||
}
|
||||
|
||||
QWidget* SectionContent::contentWidget() const
|
||||
{
|
||||
return _content;
|
||||
}
|
||||
|
||||
SectionContent::RefPtr SectionContent::newSectionContent(QWidget* title, QWidget* content)
|
||||
{
|
||||
return QSharedPointer<SectionContent>(new SectionContent(title, content));
|
||||
}
|
||||
|
||||
SectionContent::RefPtr SectionContent::newSectionContent(const QString& title, QWidget* content)
|
||||
{
|
||||
return QSharedPointer<SectionContent>(new SectionContent(new QLabel(title), content));
|
||||
}
|
||||
|
||||
ADS_NAMESPACE_END
|
||||
@@ -0,0 +1,40 @@
|
||||
#ifndef SECTIONCONTENT_H
|
||||
#define SECTIONCONTENT_H
|
||||
|
||||
#include <QPointer>
|
||||
#include <QWidget>
|
||||
#include <QHash>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include "ads.h"
|
||||
|
||||
ADS_NAMESPACE_BEGIN
|
||||
|
||||
class SectionContent
|
||||
{
|
||||
public:
|
||||
typedef QSharedPointer<SectionContent> RefPtr;
|
||||
|
||||
SectionContent(QWidget* title, QWidget* content, const QString& uniqueName = QString()); ///< Do not use!
|
||||
virtual ~SectionContent();
|
||||
|
||||
int uid() const;
|
||||
QString uniqueName() const;
|
||||
QWidget* titleWidget() const;
|
||||
QWidget* contentWidget() const;
|
||||
|
||||
static RefPtr newSectionContent(QWidget* title, QWidget* content);
|
||||
static RefPtr newSectionContent(const QString& title, QWidget* content);
|
||||
|
||||
public:
|
||||
const int _uid;
|
||||
const QString _uniqueName;
|
||||
QPointer<QWidget> _title;
|
||||
QPointer<QWidget> _content;
|
||||
|
||||
static int NextUid;
|
||||
static QHash<int, SectionContent*> LookupMap;
|
||||
};
|
||||
|
||||
ADS_NAMESPACE_END
|
||||
#endif
|
||||
@@ -0,0 +1,18 @@
|
||||
#include "section_content_widget.h"
|
||||
|
||||
#include <QBoxLayout>
|
||||
|
||||
ADS_NAMESPACE_BEGIN
|
||||
|
||||
SectionContentWidget::SectionContentWidget(SectionContent::RefPtr c, QWidget* parent) :
|
||||
QFrame(parent),
|
||||
_content(c)
|
||||
{
|
||||
auto l = new QBoxLayout(QBoxLayout::TopToBottom);
|
||||
l->setContentsMargins(0, 0, 0, 0);
|
||||
l->setSpacing(0);
|
||||
l->addWidget(_content->contentWidget());
|
||||
setLayout(l);
|
||||
}
|
||||
|
||||
ADS_NAMESPACE_END
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef SECTION_CONTENT_WIDGET_H
|
||||
#define SECTION_CONTENT_WIDGET_H
|
||||
|
||||
#include <QFrame>
|
||||
|
||||
#include "ads.h"
|
||||
#include "section_content.h"
|
||||
|
||||
ADS_NAMESPACE_BEGIN
|
||||
|
||||
class SectionWidget;
|
||||
|
||||
class SectionContentWidget : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SectionContentWidget(SectionContent::RefPtr c, QWidget* parent = 0);
|
||||
|
||||
private:
|
||||
SectionContent::RefPtr _content;
|
||||
};
|
||||
|
||||
ADS_NAMESPACE_END
|
||||
#endif
|
||||
@@ -0,0 +1,212 @@
|
||||
#include "section_title_widget.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QString>
|
||||
#include <QApplication>
|
||||
#include <QBoxLayout>
|
||||
#include <QMouseEvent>
|
||||
#include <QMimeData>
|
||||
#include <QDrag>
|
||||
#include <QCursor>
|
||||
#include <QStyle>
|
||||
|
||||
#ifdef ADS_ANIMATIONS_ENABLED
|
||||
#include <QPropertyAnimation>
|
||||
#include <QParallelAnimationGroup>
|
||||
#endif
|
||||
|
||||
#include "drop_overlay.h"
|
||||
#include "section_content.h"
|
||||
#include "section_widget.h"
|
||||
#include "floating_widget.h"
|
||||
#include "container_widget.h"
|
||||
|
||||
ADS_NAMESPACE_BEGIN
|
||||
|
||||
SectionTitleWidget::SectionTitleWidget(SectionContent::RefPtr content, QWidget* parent) :
|
||||
QFrame(parent),
|
||||
_content(content),
|
||||
_activeTab(false)
|
||||
{
|
||||
auto l = new QBoxLayout(QBoxLayout::LeftToRight);
|
||||
l->addWidget(content->titleWidget());
|
||||
setLayout(l);
|
||||
}
|
||||
|
||||
bool SectionTitleWidget::isActiveTab() const
|
||||
{
|
||||
return _activeTab;
|
||||
}
|
||||
|
||||
void SectionTitleWidget::setActiveTab(bool active)
|
||||
{
|
||||
if (active != _activeTab)
|
||||
{
|
||||
_activeTab = active;
|
||||
|
||||
style()->unpolish(this);
|
||||
style()->polish(this);
|
||||
update();
|
||||
|
||||
emit activeTabChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void SectionTitleWidget::mousePressEvent(QMouseEvent* ev)
|
||||
{
|
||||
// qDebug() << Q_FUNC_INFO << ev->pos();
|
||||
if (ev->button() == Qt::LeftButton)
|
||||
{
|
||||
_dragStartPos = ev->pos();
|
||||
ev->accept();
|
||||
return;
|
||||
}
|
||||
QFrame::mousePressEvent(ev);
|
||||
}
|
||||
|
||||
void SectionTitleWidget::mouseReleaseEvent(QMouseEvent* ev)
|
||||
{
|
||||
// qDebug() << Q_FUNC_INFO << ev->pos();
|
||||
if (!_dragStartPos.isNull())
|
||||
{
|
||||
emit clicked();
|
||||
}
|
||||
|
||||
// Drop contents of FloatingWidget into SectionWidget.
|
||||
if (_fw)
|
||||
{
|
||||
auto cw = findParentContainerWidget(this);
|
||||
auto sw = cw->sectionAt(cw->mapFromGlobal(ev->globalPos()));
|
||||
if (sw)
|
||||
{
|
||||
auto loc = showDropOverlay(sw);
|
||||
if (loc == InvalidDropArea)
|
||||
{
|
||||
qDebug() << "Invalid drop area";
|
||||
}
|
||||
else
|
||||
{
|
||||
#if !defined(ADS_ANIMATIONS_ENABLED)
|
||||
auto data = _fw->takeContent();
|
||||
_fw->deleteLater();
|
||||
_fw.clear();
|
||||
cw->dropContent(data, sw, loc);
|
||||
#else
|
||||
auto moveAnim = new QPropertyAnimation(_fw, "pos", this);
|
||||
moveAnim->setStartValue(_fw->pos());
|
||||
moveAnim->setEndValue(sw->mapToGlobal(sw->rect().topLeft()));
|
||||
moveAnim->setDuration(ADS_ANIMATION_DURATION);
|
||||
|
||||
auto resizeAnim = new QPropertyAnimation(_fw, "size", this);
|
||||
resizeAnim->setStartValue(_fw->size());
|
||||
resizeAnim->setEndValue(sw->size());
|
||||
resizeAnim->setDuration(ADS_ANIMATION_DURATION);
|
||||
|
||||
auto animGroup = new QParallelAnimationGroup(this);
|
||||
QObject::connect(animGroup, &QPropertyAnimation::finished, [this, sw]()
|
||||
{
|
||||
auto data = _fw->takeContent();
|
||||
_fw->deleteLater();
|
||||
_fw.clear();
|
||||
sw->addContent(data, true);
|
||||
});
|
||||
animGroup->addAnimation(moveAnim);
|
||||
animGroup->addAnimation(resizeAnim);
|
||||
animGroup->start(QAbstractAnimation::DeleteWhenStopped);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
// Mouse is over a outer-edge drop area
|
||||
else if (false)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Reset
|
||||
_dragStartPos = QPoint();
|
||||
hideDropOverlay();
|
||||
QFrame::mouseReleaseEvent(ev);
|
||||
}
|
||||
|
||||
void SectionTitleWidget::mouseMoveEvent(QMouseEvent* ev)
|
||||
{
|
||||
ContainerWidget* cw = findParentContainerWidget(this);
|
||||
SectionWidget* section = NULL;
|
||||
|
||||
// Move already existing FloatingWidget
|
||||
if (_fw)
|
||||
{
|
||||
const QPoint moveToPos = ev->globalPos() - (_dragStartPos + QPoint(ADS_WINDOW_FRAME_BORDER_WIDTH, ADS_WINDOW_FRAME_BORDER_WIDTH));
|
||||
_fw->move(moveToPos);
|
||||
|
||||
// Show drop indicator
|
||||
if (true)
|
||||
{
|
||||
// Mouse is over a SectionWidget
|
||||
section = cw->sectionAt(cw->mapFromGlobal(QCursor::pos()));
|
||||
if (section)
|
||||
{
|
||||
showDropOverlay(section);
|
||||
}
|
||||
// Mouse is at the edge of the ContainerWidget
|
||||
// Top, Right, Bottom, Left
|
||||
else if (cw->outerTopDropRect().contains(cw->mapFromGlobal(QCursor::pos())))
|
||||
{
|
||||
qDebug() << "Top area...";
|
||||
}
|
||||
else if (cw->outerRightDropRect().contains(cw->mapFromGlobal(QCursor::pos())))
|
||||
{
|
||||
qDebug() << "Right area...";
|
||||
}
|
||||
else if (cw->outerBottomDropRect().contains(cw->mapFromGlobal(QCursor::pos())))
|
||||
{
|
||||
qDebug() << "Bottom area...";
|
||||
}
|
||||
else if (cw->outerLeftDropRect().contains(cw->mapFromGlobal(QCursor::pos())))
|
||||
{
|
||||
qDebug() << "Left area...";
|
||||
}
|
||||
else
|
||||
{
|
||||
hideDropOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
ev->accept();
|
||||
return;
|
||||
}
|
||||
// Begin to drag title inside the title area to switch its position inside the SectionWidget.
|
||||
else if (!_dragStartPos.isNull() && (ev->buttons() & Qt::LeftButton)
|
||||
&& (section = findParentSectionWidget(this)) != NULL && section->titleAreaGeometry().contains(section->mapFromGlobal(ev->globalPos())))
|
||||
{
|
||||
// qDebug() << "Move inside title area" << ev->pos();
|
||||
}
|
||||
// Begin to drag/float the SectionContent.
|
||||
else if (!_dragStartPos.isNull() && (ev->buttons() & Qt::LeftButton) && (ev->globalPos() - _dragStartPos).manhattanLength() >= QApplication::startDragDistance()
|
||||
&& (section = findParentSectionWidget(this)) != NULL)
|
||||
{
|
||||
// Create floating widget.
|
||||
auto data = section->take(_content->uid(), false);
|
||||
|
||||
_fw = new FloatingWidget(data.content, data.titleWidget, data.contentWidget, cw);
|
||||
_fw->resize(section->size());
|
||||
setActiveTab(false);
|
||||
|
||||
const QPoint moveToPos = ev->globalPos() - (_dragStartPos + QPoint(ADS_WINDOW_FRAME_BORDER_WIDTH, ADS_WINDOW_FRAME_BORDER_WIDTH));
|
||||
_fw->move(moveToPos);
|
||||
_fw->show();
|
||||
|
||||
// Delete old section, if it is empty now.
|
||||
if (section->contents().isEmpty())
|
||||
{
|
||||
delete section;
|
||||
section = NULL;
|
||||
}
|
||||
ev->accept();
|
||||
return;
|
||||
}
|
||||
QFrame::mouseMoveEvent(ev);
|
||||
}
|
||||
|
||||
ADS_NAMESPACE_END
|
||||
@@ -0,0 +1,44 @@
|
||||
#ifndef SECTION_TITLE_WIDGET_H
|
||||
#define SECTION_TITLE_WIDGET_H
|
||||
|
||||
#include <QFrame>
|
||||
#include <QPoint>
|
||||
#include "ads.h"
|
||||
#include "section_content.h"
|
||||
|
||||
ADS_NAMESPACE_BEGIN
|
||||
|
||||
class SectionWidget;
|
||||
class FloatingWidget;
|
||||
|
||||
class SectionTitleWidget : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool activeTab MEMBER _activeTab NOTIFY activeTabChanged)
|
||||
|
||||
friend class SectionWidget;
|
||||
|
||||
SectionContent::RefPtr _content;
|
||||
QPointer<FloatingWidget> _fw;
|
||||
QPoint _dragStartPos;
|
||||
|
||||
bool _activeTab;
|
||||
|
||||
public:
|
||||
SectionTitleWidget(SectionContent::RefPtr content, QWidget* parent);
|
||||
|
||||
bool isActiveTab() const;
|
||||
void setActiveTab(bool active);
|
||||
|
||||
protected:
|
||||
virtual void mousePressEvent(QMouseEvent* ev);
|
||||
virtual void mouseReleaseEvent(QMouseEvent* ev);
|
||||
virtual void mouseMoveEvent(QMouseEvent* ev);
|
||||
|
||||
signals:
|
||||
void activeTabChanged();
|
||||
void clicked();
|
||||
};
|
||||
|
||||
ADS_NAMESPACE_END
|
||||
#endif
|
||||
@@ -0,0 +1,231 @@
|
||||
#include "section_widget.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QBoxLayout>
|
||||
#include <QStackedLayout>
|
||||
#include <QMouseEvent>
|
||||
#include <QDragEnterEvent>
|
||||
#include <QMimeData>
|
||||
#include <QPainter>
|
||||
#include <QStyle>
|
||||
#include <QtWidgets/QSplitter>
|
||||
|
||||
#if defined(ADS_ANIMATIONS_ENABLED)
|
||||
#include <QGraphicsDropShadowEffect>
|
||||
#endif
|
||||
|
||||
#include "drop_overlay.h"
|
||||
#include "section_content.h"
|
||||
#include "section_title_widget.h"
|
||||
#include "section_content_widget.h"
|
||||
#include "floating_widget.h"
|
||||
#include "container_widget.h"
|
||||
|
||||
ADS_NAMESPACE_BEGIN
|
||||
|
||||
int SectionWidget::NextUid = 1;
|
||||
QHash<int, SectionWidget*> SectionWidget::LookupMap;
|
||||
|
||||
SectionWidget::SectionWidget(ContainerWidget* parent) :
|
||||
QFrame(parent),
|
||||
_uid(NextUid++),
|
||||
_container(parent)
|
||||
{
|
||||
//setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
|
||||
auto l = new QBoxLayout(QBoxLayout::TopToBottom);
|
||||
l->setContentsMargins(0, 0, 0, 0);
|
||||
l->setSpacing(0);
|
||||
setLayout(l);
|
||||
|
||||
_tabsLayout = new QBoxLayout(QBoxLayout::LeftToRight);
|
||||
_tabsLayout->setContentsMargins(0, 0, 0, 0);
|
||||
_tabsLayout->setSpacing(0);
|
||||
_tabsLayout->addStretch(1);
|
||||
l->addLayout(_tabsLayout);
|
||||
|
||||
_contentsLayout = new QStackedLayout();
|
||||
_contentsLayout->setContentsMargins(0, 0, 0, 0);
|
||||
_contentsLayout->setSpacing(0);
|
||||
l->addLayout(_contentsLayout, 1);
|
||||
|
||||
#if defined(ADS_ANIMATIONS_ENABLED)
|
||||
auto shadow = new QGraphicsDropShadowEffect(this);
|
||||
shadow->setOffset(0, 0);
|
||||
shadow->setBlurRadius(8);
|
||||
setGraphicsEffect(shadow);
|
||||
#endif
|
||||
|
||||
LookupMap.insert(_uid, this);
|
||||
_container->_sections.append(this);
|
||||
}
|
||||
|
||||
SectionWidget::~SectionWidget()
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
LookupMap.remove(_uid);
|
||||
_container->_sections.removeAll(this);
|
||||
|
||||
// Delete empty QSplitter.
|
||||
auto splitter = findParentSplitter(this);
|
||||
if (splitter && splitter->count() == 0)
|
||||
{
|
||||
splitter->deleteLater();
|
||||
qDebug() << "Delete empty splitter";
|
||||
}
|
||||
}
|
||||
|
||||
int SectionWidget::uid() const
|
||||
{
|
||||
return _uid;
|
||||
}
|
||||
|
||||
ContainerWidget* SectionWidget::containerWidget() const
|
||||
{
|
||||
return _container;
|
||||
}
|
||||
|
||||
QRect SectionWidget::titleAreaGeometry() const
|
||||
{
|
||||
return _tabsLayout->geometry();
|
||||
}
|
||||
|
||||
QRect SectionWidget::contentAreaGeometry() const
|
||||
{
|
||||
return _contentsLayout->geometry();
|
||||
}
|
||||
|
||||
void SectionWidget::addContent(SectionContent::RefPtr c)
|
||||
{
|
||||
_contents.append(c);
|
||||
|
||||
auto title = new SectionTitleWidget(c, nullptr);
|
||||
_sectionTitles.append(title);
|
||||
_tabsLayout->insertWidget(_tabsLayout->count() - 1, title);
|
||||
QObject::connect(title, &SectionTitleWidget::clicked, this, &SectionWidget::onSectionTitleClicked);
|
||||
|
||||
auto content = new SectionContentWidget(c, nullptr);
|
||||
_sectionContents.append(content);
|
||||
_contentsLayout->addWidget(content);
|
||||
|
||||
// Active first TAB.
|
||||
if (_contents.size() == 1)
|
||||
setCurrentIndex(0);
|
||||
// Switch to newest.
|
||||
// else
|
||||
// setCurrentIndex(_contentsLayout->count() - 1);
|
||||
}
|
||||
|
||||
void SectionWidget::addContent(const InternalContentData& data, bool autoActivate)
|
||||
{
|
||||
_contents.append(data.content);
|
||||
|
||||
_sectionTitles.append(data.titleWidget);
|
||||
_tabsLayout->insertWidget(_tabsLayout->count() - 1, data.titleWidget);
|
||||
QObject::connect(data.titleWidget, &SectionTitleWidget::clicked, this, &SectionWidget::onSectionTitleClicked);
|
||||
|
||||
_sectionContents.append(data.contentWidget);
|
||||
_contentsLayout->addWidget(data.contentWidget);
|
||||
|
||||
// Active first TAB.
|
||||
if (_contents.size() == 1)
|
||||
setCurrentIndex(0);
|
||||
// Switch to newest.
|
||||
else if (autoActivate)
|
||||
setCurrentIndex(_contentsLayout->count() - 1);
|
||||
}
|
||||
|
||||
// take removes a widget from the SectionWidget but does not delete
|
||||
// the used SectionTitle- and SectionContent-Widget. Instead it returns
|
||||
// these objects.
|
||||
InternalContentData SectionWidget::take(int uid, bool del)
|
||||
{
|
||||
InternalContentData data;
|
||||
|
||||
// Find SectionContent.
|
||||
SectionContent::RefPtr sc;
|
||||
int index = -1;
|
||||
for (int i = 0; i < _contents.count(); i++)
|
||||
{
|
||||
if (_contents[i]->uid() != uid)
|
||||
continue;
|
||||
index = i;
|
||||
sc = _contents.takeAt(i);
|
||||
break;
|
||||
}
|
||||
|
||||
// Title wrapper widget (TAB)
|
||||
auto title = _sectionTitles.takeAt(index);
|
||||
if (title)
|
||||
{
|
||||
_tabsLayout->removeWidget(title);
|
||||
title->disconnect(this);
|
||||
if (del)
|
||||
title->deleteLater();
|
||||
}
|
||||
|
||||
// Content wrapper widget (CONTENT)
|
||||
auto content = _sectionContents.takeAt(index);
|
||||
if (content)
|
||||
{
|
||||
_contentsLayout->removeWidget(content);
|
||||
content->disconnect(this);
|
||||
if (del)
|
||||
content->deleteLater();
|
||||
}
|
||||
|
||||
// Select the previous tab as activeTab.
|
||||
if (_contents.size() > 0 && title->isActiveTab())
|
||||
{
|
||||
if (index > 0)
|
||||
setCurrentIndex(index - 1);
|
||||
else
|
||||
setCurrentIndex(0);
|
||||
}
|
||||
|
||||
data.content = sc;
|
||||
data.titleWidget = title;
|
||||
data.contentWidget = content;
|
||||
return data;
|
||||
}
|
||||
|
||||
void SectionWidget::setCurrentIndex(int index)
|
||||
{
|
||||
// Set active TAB.
|
||||
for (int i = 0; i < _tabsLayout->count(); ++i)
|
||||
{
|
||||
auto item = _tabsLayout->itemAt(i);
|
||||
if (item->widget())
|
||||
{
|
||||
auto stw = dynamic_cast<SectionTitleWidget*>(item->widget());
|
||||
if (i == index)
|
||||
stw->setActiveTab(true);
|
||||
else
|
||||
stw->setActiveTab(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Set active CONTENT.
|
||||
_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()
|
||||
{
|
||||
auto stw = qobject_cast<SectionTitleWidget*>(sender());
|
||||
if (stw)
|
||||
{
|
||||
auto index = _tabsLayout->indexOf(stw);
|
||||
setCurrentIndex(index);
|
||||
}
|
||||
}
|
||||
|
||||
ADS_NAMESPACE_END
|
||||
@@ -0,0 +1,72 @@
|
||||
#ifndef SECTION_WIDGET_H
|
||||
#define SECTION_WIDGET_H
|
||||
|
||||
#include <QDebug>
|
||||
#include <QList>
|
||||
#include <QHash>
|
||||
#include <QFrame>
|
||||
|
||||
#include "ads.h"
|
||||
#include "section_content.h"
|
||||
|
||||
class QBoxLayout;
|
||||
class QStackedLayout;
|
||||
|
||||
ADS_NAMESPACE_BEGIN
|
||||
|
||||
class ContainerWidget;
|
||||
class SectionTitleWidget;
|
||||
class SectionContentWidget;
|
||||
|
||||
// SectionWidget manages multiple instances of SectionContent.
|
||||
// It displays a title TAB, which is clickable and will switch to
|
||||
// the contents associated to the title when clicked.
|
||||
class SectionWidget : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SectionWidget(ContainerWidget* parent);
|
||||
virtual ~SectionWidget();
|
||||
|
||||
int uid() const;
|
||||
ContainerWidget* containerWidget() const;
|
||||
|
||||
QRect titleAreaGeometry() const;
|
||||
QRect contentAreaGeometry() const;
|
||||
|
||||
QList<SectionContent::RefPtr> contents() const { return _contents; }
|
||||
void addContent(SectionContent::RefPtr c);
|
||||
void addContent(const InternalContentData& data, bool autoActivate);
|
||||
InternalContentData take(int uid, bool del = true);
|
||||
|
||||
public slots:
|
||||
void setCurrentIndex(int index);
|
||||
|
||||
protected:
|
||||
virtual void paintEvent(QPaintEvent*);
|
||||
|
||||
private slots:
|
||||
void onSectionTitleClicked();
|
||||
|
||||
private:
|
||||
const int _uid;
|
||||
|
||||
ContainerWidget* _container;
|
||||
QList<SectionContent::RefPtr> _contents;
|
||||
QList<SectionTitleWidget*> _sectionTitles;
|
||||
QList<SectionContentWidget*> _sectionContents;
|
||||
|
||||
QBoxLayout *_tabsLayout;
|
||||
QStackedLayout *_contentsLayout;
|
||||
|
||||
QPoint _mousePressPoint;
|
||||
SectionContent::RefPtr _mousePressContent;
|
||||
SectionTitleWidget* _mousePressTitleWidget;
|
||||
|
||||
static int NextUid;
|
||||
static QHash<int, SectionWidget*> LookupMap;
|
||||
};
|
||||
|
||||
ADS_NAMESPACE_END
|
||||
#endif
|
||||
Reference in New Issue
Block a user