Init commit for GitHub.

This commit is contained in:
mfreiholz
2015-12-09 12:21:38 +01:00
commit 343c5cc727
40 changed files with 1961 additions and 0 deletions
+60
View File
@@ -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
+85
View File
@@ -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