diff --git a/plugins/feature/aprs/aprsgui.ui b/plugins/feature/aprs/aprsgui.ui index db0e01608..569bc4e96 100644 --- a/plugins/feature/aprs/aprsgui.ui +++ b/plugins/feature/aprs/aprsgui.ui @@ -11,7 +11,7 @@ - + 0 0 diff --git a/plugins/feature/gs232controller/gs232controllergui.ui b/plugins/feature/gs232controller/gs232controllergui.ui index 603068634..656be9249 100644 --- a/plugins/feature/gs232controller/gs232controllergui.ui +++ b/plugins/feature/gs232controller/gs232controllergui.ui @@ -11,7 +11,7 @@ - + 0 0 diff --git a/plugins/feature/vorlocalizer/vorlocalizergui.ui b/plugins/feature/vorlocalizer/vorlocalizergui.ui index a1e90d9ad..61ba9fdf9 100644 --- a/plugins/feature/vorlocalizer/vorlocalizergui.ui +++ b/plugins/feature/vorlocalizer/vorlocalizergui.ui @@ -11,7 +11,7 @@ - + 0 0 diff --git a/sdrgui/CMakeLists.txt b/sdrgui/CMakeLists.txt index 261c7ec99..93aceaa29 100644 --- a/sdrgui/CMakeLists.txt +++ b/sdrgui/CMakeLists.txt @@ -35,6 +35,7 @@ set(sdrgui_SOURCES gui/externalclockdialog.cpp gui/fmpreemphasisdialog.cpp gui/featureadddialog.cpp + gui/featurelayout.cpp gui/featuresdock.cpp gui/featurepresetsdialog.cpp gui/featurewindow.cpp @@ -127,6 +128,7 @@ set(sdrgui_HEADERS gui/externalclockdialog.h gui/fmpreemphasisdialog.h gui/featureadddialog.h + gui/featurelayout.h gui/featuresdock.h gui/featurepresetsdialog.h gui/featurewindow.h diff --git a/sdrgui/gui/featurelayout.cpp b/sdrgui/gui/featurelayout.cpp new file mode 100644 index 000000000..24fc47a48 --- /dev/null +++ b/sdrgui/gui/featurelayout.cpp @@ -0,0 +1,316 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "featurelayout.h" + +FeatureLayout::FeatureLayout(QWidget *parent, int margin, int hSpacing, int vSpacing) + : QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing) +{ + setContentsMargins(margin, margin, margin, margin); +} + +FeatureLayout::FeatureLayout(int margin, int hSpacing, int vSpacing) + : m_hSpace(hSpacing), m_vSpace(vSpacing) +{ + setContentsMargins(margin, margin, margin, margin); +} + +FeatureLayout::~FeatureLayout() +{ + QLayoutItem *item; + while ((item = takeAt(0))) { + delete item; + } +} + +void FeatureLayout::addItem(QLayoutItem *item) +{ + itemList.append(item); +} + +int FeatureLayout::horizontalSpacing() const +{ + if (m_hSpace >= 0) { + return m_hSpace; + } else { + return smartSpacing(QStyle::PM_LayoutHorizontalSpacing); + } +} + +int FeatureLayout::verticalSpacing() const +{ + if (m_vSpace >= 0) { + return m_vSpace; + } else { + return smartSpacing(QStyle::PM_LayoutVerticalSpacing); + } +} + +bool FeatureLayout::hasHeightForWidth() const +{ + return true; +} + +int FeatureLayout::heightForWidth(int width) const +{ + QSize size; + if (m_orientation == Qt::Horizontal) { + size = doLayoutHorizontally(QRect(0, 0, width, 0), true); + } else { + size = doLayoutVertically(QRect(0, 0, width, 0), true); + } + return size.height(); +} + +int FeatureLayout::count() const +{ + return itemList.size(); +} + +QLayoutItem *FeatureLayout::itemAt(int index) const +{ + return itemList.value(index); +} + +QLayoutItem *FeatureLayout::takeAt(int index) +{ + if (index >= 0 && index < itemList.size()) { + return itemList.takeAt(index); + } + return nullptr; +} + +void FeatureLayout::setOrientation(Qt::Orientation orientation) +{ + m_orientation = orientation; +} + +Qt::Orientations FeatureLayout::expandingDirections() const +{ + return Qt::Horizontal | Qt::Vertical; +} + +void FeatureLayout::setGeometry(const QRect &rect) +{ + m_prevGeometry = rect; + QLayout::setGeometry(rect); + if (m_orientation == Qt::Horizontal) { + doLayoutHorizontally(rect, false); + } else { + doLayoutVertically(rect, false); + } +} + +// Calculate preferred size +QSize FeatureLayout::sizeHint() const +{ + QSize size; + if (m_orientation == Qt::Horizontal) { + size = doLayoutHorizontally(m_prevGeometry, true); + } else { + size = doLayoutVertically(m_prevGeometry, true); + } + return size; +} + +QSize FeatureLayout::minimumSize() const +{ + QSize size; + if (m_orientation == Qt::Horizontal) { + size = doLayoutHorizontally(m_prevGeometry, true); + } else { + size = doLayoutVertically(m_prevGeometry, true); + } + return size; +} + +QSize FeatureLayout::doLayoutHorizontally(const QRect &rect, bool testOnly) const +{ + int left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom); + int x = effectiveRect.x(); + int y = effectiveRect.y(); + int lineWidth = 0; + int spaceX = 0; + int spaceY = 0; + + // Calculate space available for columns of widgets + int maxWidthForColums = effectiveRect.width(); + if (itemList.size() > 0) { + maxWidthForColums -= itemList[0]->minimumSize().width(); + } + int minHeight = 0; + + int i = 0; + for (QLayoutItem *item : qAsConst(itemList)) + { + // Splitter is item 0, so skip + if (i != 0) + { + const QWidget *wid = item->widget(); + spaceX = horizontalSpacing(); + if (spaceX == -1) { + spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal); + } + spaceY = verticalSpacing(); + if (spaceY == -1) { + spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical); + } + + // Layout in vertical columns + int nextY = y + item->sizeHint().height() + spaceY; + int nextX = x + lineWidth + spaceX + item->sizeHint().width(); + if (nextY - spaceY > effectiveRect.bottom() && lineWidth > 0 && nextX < maxWidthForColums) + { + minHeight = qMax(minHeight, y); + y = effectiveRect.y(); + x = x + lineWidth + spaceX; + nextY = y + item->sizeHint().height() + spaceY; + lineWidth = 0; + } + + if (!testOnly) { + item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); + } + + y = nextY; + lineWidth = qMax(lineWidth, item->sizeHint().width()); + minHeight = qMax(minHeight, y); + } + i++; + } + + if (itemList.size() > 0) + { + // Now layout splitter + QLayoutItem *item = itemList[0]; + y = effectiveRect.y(); + x = x + lineWidth + spaceX; + + if (!testOnly) + { + // Use all available space + int splitterWidth = rect.width() - right - x; + int splitterHeight = rect.height() - bottom - y; + splitterWidth = qMax(splitterWidth, item->minimumSize().width()); + splitterHeight = qMax(splitterHeight, item->minimumSize().height()); + item->setGeometry(QRect(QPoint(x, y), QSize(splitterWidth, splitterHeight))); + } + lineWidth = item->minimumSize().width(); + y = y + item->minimumSize().height() + spaceY; + } + minHeight = qMax(minHeight, y); + + QSize size(x + lineWidth + right, minHeight - spaceY + bottom); + return size; +} + +QSize FeatureLayout::doLayoutVertically(const QRect &rect, bool testOnly) const +{ + int left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom); + int x = effectiveRect.x(); + int y = effectiveRect.y(); + int lineHeight = 0; + int spaceX = 0; + int spaceY = 0; + + // Calculate space available for rows of widgets + int maxHeightForRows = effectiveRect.height(); + if (itemList.size() > 0) { + maxHeightForRows -= itemList[0]->minimumSize().height(); + } + int minWidth = 0; + + int i = 0; + for (QLayoutItem *item : qAsConst(itemList)) + { + // Splitter is item 0, so skip + if (i != 0) + { + const QWidget *wid = item->widget(); + spaceX = horizontalSpacing(); + if (spaceX == -1) { + spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal); + } + spaceY = verticalSpacing(); + if (spaceY == -1) { + spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical); + } + + int nextX = x + item->sizeHint().width() + spaceX; + if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) + { + x = effectiveRect.x(); + y = y + lineHeight + spaceY; + nextX = x + item->sizeHint().width() + spaceX; + lineHeight = 0; + } + + if (!testOnly) { + item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); + } + + x = nextX; + lineHeight = qMax(lineHeight, item->sizeHint().height()); + minWidth = qMax(minWidth, x); + } + i++; + } + + if (itemList.size() > 0) + { + // Now layout splitter + QLayoutItem *item = itemList[0]; + x = effectiveRect.x(); + y = y + lineHeight + spaceY; + + if (!testOnly) + { + // Use all available space + int splitterWidth = rect.width() - right - x; + int splitterHeight = rect.height() - bottom - y; + splitterWidth = qMax(splitterWidth, item->minimumSize().width()); + splitterHeight = qMax(splitterHeight, item->minimumSize().height()); + item->setGeometry(QRect(QPoint(x, y), QSize(splitterWidth, splitterHeight))); + } + lineHeight = item->minimumSize().height(); + x = x + item->minimumSize().width() + spaceX; + } + minWidth = qMax(minWidth, x); + + QSize size(minWidth - spaceX + right, y + lineHeight - rect.y() + bottom); + return size; +} + +int FeatureLayout::smartSpacing(QStyle::PixelMetric pm) const +{ + QObject *parent = this->parent(); + if (!parent) { + return -1; + } else if (parent->isWidgetType()) { + QWidget *pw = static_cast(parent); + return pw->style()->pixelMetric(pm, nullptr, pw); + } else { + return static_cast(parent)->spacing(); + } +} diff --git a/sdrgui/gui/featurelayout.h b/sdrgui/gui/featurelayout.h new file mode 100644 index 000000000..abe084d33 --- /dev/null +++ b/sdrgui/gui/featurelayout.h @@ -0,0 +1,72 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 The Qt Company Ltd. // +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRGUI_GUI_FEATURELAYOUT_H +#define SDRGUI_GUI_FEATURELAYOUT_H + +#include +#include +#include + +// A QLayout specifically for the Features window, that tries to make the most +// of available space, while allowing the user to resize some elements, on the +// assumption that there are two types of Feature UI +// - Fixed size widgets like Rotator Controller +// - Expanding widgets like Map +// The Feature window is split in to two parts (when horizontal orientation): +// - Left hand side for fixed widgets which are stacked in vertical columns +// to fit available height +// - Right hand side is for expanding widgets inside a Splitter which allows +// a user to manually set how much space is used for each Feature +// When vertical orientation, the fixed widgets are in columns at the top, with +// the expanding widgets underneath (this isn't quite as nice, as the widget +// heights vary more than the widths) +class FeatureLayout : public QLayout +{ +public: + explicit FeatureLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1); + explicit FeatureLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1); + ~FeatureLayout(); + + void addItem(QLayoutItem *item) override; + int horizontalSpacing() const; + int verticalSpacing() const; + Qt::Orientations expandingDirections() const override; + bool hasHeightForWidth() const override; + int heightForWidth(int) const override; + int count() const override; + QLayoutItem *itemAt(int index) const override; + QSize minimumSize() const override; + void setGeometry(const QRect &rect) override; + QSize sizeHint() const override; + QLayoutItem *takeAt(int index) override; + void setOrientation(Qt::Orientation); + +private: + QSize doLayoutVertically(const QRect &rect, bool testOnly) const; + QSize doLayoutHorizontally(const QRect &rect, bool testOnly) const; + int smartSpacing(QStyle::PixelMetric pm) const; + + QList itemList; + int m_hSpace; + int m_vSpace; + Qt::Orientation m_orientation; + QRect m_prevGeometry; +}; + +#endif // SDRGUI_GUI_FEATURELAYOUT_H diff --git a/sdrgui/gui/featurewindow.cpp b/sdrgui/gui/featurewindow.cpp index 787b15118..af9230b1e 100644 --- a/sdrgui/gui/featurewindow.cpp +++ b/sdrgui/gui/featurewindow.cpp @@ -15,8 +15,6 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include -#include #include #include @@ -24,34 +22,43 @@ #include "rollupwidget.h" FeatureWindow::FeatureWindow(QWidget* parent) : - QScrollArea(parent) + QScrollArea(parent) { - m_container = new QWidget(this); - m_layout = new QBoxLayout(QBoxLayout::TopToBottom, m_container); - setWidget(m_container); - setWidgetResizable(true); - setBackgroundRole(QPalette::Base); - m_layout->setMargin(3); - m_layout->setSpacing(3); + m_container = new QWidget(this); + m_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + m_splitter = new QSplitter(); + m_layout = new FeatureLayout(m_container, 3, 3, 3); + setWidget(m_container); + setWidgetResizable(true); + setBackgroundRole(QPalette::Base); + m_layout->addWidget(m_splitter); // Splitter must be added first } void FeatureWindow::addRollupWidget(QWidget* rollupWidget) { - rollupWidget->setParent(m_container); - m_container->layout()->addWidget(rollupWidget); + if (rollupWidget->sizePolicy().verticalPolicy() == QSizePolicy::Expanding) + { + rollupWidget->setParent(m_splitter); + m_splitter->addWidget(rollupWidget); + } + else + { + rollupWidget->setParent(m_container); + m_layout->addWidget(rollupWidget); + } } void FeatureWindow::resizeEvent(QResizeEvent* event) { - if (event->size().height() > event->size().width()) - { - m_layout->setDirection(QBoxLayout::TopToBottom); - m_layout->setAlignment(Qt::AlignTop); - } - else - { - m_layout->setDirection(QBoxLayout::LeftToRight); - m_layout->setAlignment(Qt::AlignLeft); - } - QScrollArea::resizeEvent(event); + if (event->size().height() > event->size().width()) + { + m_layout->setOrientation(Qt::Vertical); + m_splitter->setOrientation(Qt::Vertical); + } + else + { + m_layout->setOrientation(Qt::Horizontal); + m_splitter->setOrientation(Qt::Horizontal); + } + QScrollArea::resizeEvent(event); } diff --git a/sdrgui/gui/featurewindow.h b/sdrgui/gui/featurewindow.h index 7e423ae21..09889d5e9 100644 --- a/sdrgui/gui/featurewindow.h +++ b/sdrgui/gui/featurewindow.h @@ -19,26 +19,28 @@ #define INCLUDE_FEATUREWINDOW_H #include +#include #include "export.h" +#include "featurelayout.h" class QBoxLayout; class QSpacerItem; class RollupWidget; class SDRGUI_API FeatureWindow : public QScrollArea { - Q_OBJECT + Q_OBJECT public: - FeatureWindow(QWidget* parent = nullptr); - - void addRollupWidget(QWidget* rollupWidget); + FeatureWindow(QWidget* parent = nullptr); + void addRollupWidget(QWidget* rollupWidget); protected: - QWidget* m_container; - QBoxLayout* m_layout; + QWidget* m_container; + FeatureLayout* m_layout; + QSplitter* m_splitter; - void resizeEvent(QResizeEvent* event); + void resizeEvent(QResizeEvent* event); }; #endif // INCLUDE_FEATUREWINDOW_H diff --git a/sdrgui/gui/rollupwidget.cpp b/sdrgui/gui/rollupwidget.cpp index 2c093031c..5b89eb355 100644 --- a/sdrgui/gui/rollupwidget.cpp +++ b/sdrgui/gui/rollupwidget.cpp @@ -290,7 +290,7 @@ int RollupWidget::arrangeRollups() } else { setMaximumHeight(16777215); } - + updateGeometry(); return pos; }