diff --git a/AdvancedDockingSystem/AdvancedDockingSystem.pro b/AdvancedDockingSystem/AdvancedDockingSystem.pro index e84b2fa..f89c77b 100644 --- a/AdvancedDockingSystem/AdvancedDockingSystem.pro +++ b/AdvancedDockingSystem/AdvancedDockingSystem.pro @@ -7,6 +7,8 @@ TEMPLATE = lib CONFIG += staticlib +VERSION = 0.1.0 + INCLUDEPATH += $$PWD/src INCLUDEPATH += $$PWD/include diff --git a/AdvancedDockingSystem/include/ads/ContainerWidget.h b/AdvancedDockingSystem/include/ads/ContainerWidget.h index 1017b9d..82118bf 100644 --- a/AdvancedDockingSystem/include/ads/ContainerWidget.h +++ b/AdvancedDockingSystem/include/ads/ContainerWidget.h @@ -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 _sections; - - //Floatings of this container QList _floatings; + QHash _hiddenSectionContents; + + // Layout stuff QGridLayout* _mainLayout; Qt::Orientation _orientation; - QPointer _splitter; // $mfreiholz: I'd like to remove this variable entirely. + QPointer _splitter; // $mfreiholz: I'd like to remove this variable entirely, + // because it changes during user interaction anyway. }; ADS_NAMESPACE_END diff --git a/AdvancedDockingSystem/include/ads/Internal.h b/AdvancedDockingSystem/include/ads/Internal.h index bf37299..062b833 100644 --- a/AdvancedDockingSystem/include/ads/Internal.h +++ b/AdvancedDockingSystem/include/ads/Internal.h @@ -28,5 +28,19 @@ public: }; +class HiddenSectionItem +{ +public: + HiddenSectionItem() : + preferredSectionId(-1), + preferredSectionIndex(-1) + {} + + int preferredSectionId; + int preferredSectionIndex; + InternalContentData data; +}; + + ADS_NAMESPACE_END #endif diff --git a/AdvancedDockingSystem/include/ads/SectionContentWidget.h b/AdvancedDockingSystem/include/ads/SectionContentWidget.h index 1529fd1..fa59b8b 100644 --- a/AdvancedDockingSystem/include/ads/SectionContentWidget.h +++ b/AdvancedDockingSystem/include/ads/SectionContentWidget.h @@ -18,6 +18,7 @@ class SectionContentWidget : public QFrame public: SectionContentWidget(SectionContent::RefPtr c, QWidget* parent = 0); + virtual ~SectionContentWidget(); private: SectionContent::RefPtr _content; diff --git a/AdvancedDockingSystem/include/ads/SectionTitleWidget.h b/AdvancedDockingSystem/include/ads/SectionTitleWidget.h index a7cd84e..faa7ce5 100644 --- a/AdvancedDockingSystem/include/ads/SectionTitleWidget.h +++ b/AdvancedDockingSystem/include/ads/SectionTitleWidget.h @@ -34,6 +34,7 @@ class SectionTitleWidget : public QFrame public: SectionTitleWidget(SectionContent::RefPtr content, QWidget* parent); + virtual ~SectionTitleWidget(); bool isActiveTab() const; void setActiveTab(bool active); diff --git a/AdvancedDockingSystem/include/ads/SectionWidget.h b/AdvancedDockingSystem/include/ads/SectionWidget.h index c248de4..d75e502 100644 --- a/AdvancedDockingSystem/include/ads/SectionWidget.h +++ b/AdvancedDockingSystem/include/ads/SectionWidget.h @@ -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& 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; diff --git a/AdvancedDockingSystem/src/ContainerWidget.cpp b/AdvancedDockingSystem/src/ContainerWidget.cpp index 0bb390a..d6ee708 100644 --- a/AdvancedDockingSystem/src/ContainerWidget.cpp +++ b/AdvancedDockingSystem/src/ContainerWidget.cpp @@ -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 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 actions; + + // Visible contents of sections + for (int i = 0; i < _sections.size(); ++i) + { + const SectionWidget* sw = _sections.at(i); + const QList& 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 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(widget)) != NULL) { + const QList& contents = sw->contents(); + QList hiddenContents; + + QHashIterator 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& 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 diff --git a/AdvancedDockingSystem/src/SectionContentWidget.cpp b/AdvancedDockingSystem/src/SectionContentWidget.cpp index 456020e..60db301 100644 --- a/AdvancedDockingSystem/src/SectionContentWidget.cpp +++ b/AdvancedDockingSystem/src/SectionContentWidget.cpp @@ -1,5 +1,6 @@ #include "ads/SectionContentWidget.h" +#include #include 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 diff --git a/AdvancedDockingSystem/src/SectionTitleWidget.cpp b/AdvancedDockingSystem/src/SectionTitleWidget.cpp index be81ba4..28434a0 100644 --- a/AdvancedDockingSystem/src/SectionTitleWidget.cpp +++ b/AdvancedDockingSystem/src/SectionTitleWidget.cpp @@ -38,6 +38,11 @@ SectionTitleWidget::SectionTitleWidget(SectionContent::RefPtr content, QWidget* setLayout(l); } +SectionTitleWidget::~SectionTitleWidget() +{ + qDebug() << Q_FUNC_INFO; +} + bool SectionTitleWidget::isActiveTab() const { return _activeTab; diff --git a/AdvancedDockingSystem/src/SectionWidget.cpp b/AdvancedDockingSystem/src/SectionWidget.cpp index c6461f4..7be3931 100644 --- a/AdvancedDockingSystem/src/SectionWidget.cpp +++ b/AdvancedDockingSystem/src/SectionWidget.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #if defined(ADS_ANIMATIONS_ENABLED) #include @@ -31,7 +32,10 @@ QHash > 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(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(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 diff --git a/README.md b/README.md index 32e17ef..8edcfe4 100644 --- a/README.md +++ b/README.md @@ -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...