DecodeTrace: Add FlowLayout and integrate it
authorSoeren Apel <soeren@apelpie.net>
Sun, 5 Jan 2020 19:44:37 +0000 (20:44 +0100)
committerUwe Hermann <uwe@hermann-uwe.de>
Sun, 5 Jan 2020 21:46:36 +0000 (22:46 +0100)
CMakeLists.txt
pv/views/trace/decodetrace.cpp
pv/views/trace/decodetrace.hpp
pv/widgets/flowlayout.cpp [new file with mode: 0644]
pv/widgets/flowlayout.hpp [new file with mode: 0644]
test/CMakeLists.txt

index fbfb5eb1113213f0a2655b1b1c80a3faf291f434..3770f1e4d6eafc994930ab358fd58ce7ee46660c 100644 (file)
@@ -299,6 +299,7 @@ set(pulseview_SOURCES
        pv/widgets/colorpopup.cpp
        pv/widgets/devicetoolbutton.cpp
        pv/widgets/exportmenu.cpp
+       pv/widgets/flowlayout.cpp
        pv/widgets/importmenu.cpp
        pv/widgets/popup.cpp
        pv/widgets/popuptoolbutton.cpp
@@ -358,6 +359,7 @@ set(pulseview_HEADERS
        pv/widgets/colorpopup.hpp
        pv/widgets/devicetoolbutton.hpp
        pv/widgets/exportmenu.hpp
+       pv/widgets/flowlayout.hpp
        pv/widgets/importmenu.hpp
        pv/widgets/popup.hpp
        pv/widgets/popuptoolbutton.hpp
index b1084f84e9c8228e427475577c32c0d956aee719..0715981c5c7366369b2a5e7a9743381f14639743 100644 (file)
@@ -58,6 +58,7 @@ extern "C" {
 #include <pv/data/logicsegment.hpp>
 #include <pv/widgets/decodergroupbox.hpp>
 #include <pv/widgets/decodermenu.hpp>
+#include <pv/widgets/flowlayout.hpp>
 
 using std::abs;
 using std::find_if;
@@ -99,6 +100,52 @@ const int DecodeTrace::DrawPadding = 100;
 const int DecodeTrace::MaxTraceUpdateRate = 1; // No more than 1 Hz
 const unsigned int DecodeTrace::AnimationDurationInTicks = 7;
 
+
+/**
+ * Helper function for forceUpdate()
+ */
+void invalidateLayout(QLayout* layout)
+{
+       // Recompute the given layout and all its child layouts recursively
+       for (int i = 0; i < layout->count(); i++) {
+               QLayoutItem *item = layout->itemAt(i);
+
+               if (item->layout())
+                       invalidateLayout(item->layout());
+               else
+                       item->invalidate();
+       }
+
+       layout->invalidate();
+       layout->activate();
+}
+
+void forceUpdate(QWidget* widget)
+{
+       // Update all child widgets recursively
+       for (QObject* child : widget->children())
+               if (child->isWidgetType())
+                       forceUpdate((QWidget*)child);
+
+       // Invalidate the layout of the widget itself
+       if (widget->layout())
+               invalidateLayout(widget->layout());
+}
+
+
+ContainerWidget::ContainerWidget(QWidget *parent) :
+       QWidget(parent)
+{
+}
+
+void ContainerWidget::resizeEvent(QResizeEvent* event)
+{
+       QWidget::resizeEvent(event);
+
+       widgetResized(this);
+}
+
+
 DecodeTrace::DecodeTrace(pv::Session &session,
        shared_ptr<data::SignalBase> signalbase, int index) :
        Trace(signalbase),
@@ -582,6 +629,9 @@ void DecodeTrace::hover_point_changed(const QPoint &hp)
 
 void DecodeTrace::mouse_left_press_event(const QMouseEvent* event)
 {
+       // Update container widths which depend on the scrollarea's current width
+       update_expanded_rows();
+
        // Handle row expansion marker
        for (DecodeTraceRow& r : rows_) {
                if (!r.expand_marker_highlighted)
@@ -599,15 +649,18 @@ void DecodeTrace::mouse_left_press_event(const QMouseEvent* event)
                        } else {
                                r.expanding = true;
                                r.anim_shape = 0;
+
+                               // Force geometry update of the widget container to get
+                               // an up-to-date height (which also depends on the width)
+                               forceUpdate(r.container);
+
                                r.container->setVisible(true);
-                               QApplication::processEvents();
-                               r.expanded_height = 5 * default_row_height_ + r.container->size().height();
+                               r.expanded_height = 2 * default_row_height_ + r.container->sizeHint().height();
                        }
 
                        r.animation_step = 0;
                        r.anim_height = r.height;
 
-                       update_expanded_rows();
                        animation_timer_.start();
                }
        }
@@ -1181,6 +1234,7 @@ void DecodeTrace::export_annotations(vector<const Annotation*> *annotations) con
 
 void DecodeTrace::initialize_row_widgets(DecodeTraceRow* r, unsigned int row_id)
 {
+       // Set colors and fixed widths
        QFontMetrics m(QApplication::font());
 
        QPalette header_palette = owner_->view()->palette();
@@ -1201,9 +1255,9 @@ void DecodeTrace::initialize_row_widgets(DecodeTraceRow* r, unsigned int row_id)
        const int w = m.boundingRect(r->decode_row->title()).width() + RowTitleMargin;
        r->title_width = w;
 
-       r->container->resize(owner_->view()->viewport()->width() - r->container->pos().x(),
-               r->expanded_height - 2 * default_row_height_);
-       r->container->setVisible(false);
+       // Set up top-level container
+       connect(r->container, SIGNAL(widgetResized(QWidget*)),
+               this, SLOT(on_row_container_resized(QWidget*)));
 
        QVBoxLayout* vlayout = new QVBoxLayout();
        r->container->setLayout(vlayout);
@@ -1231,9 +1285,8 @@ void DecodeTrace::initialize_row_widgets(DecodeTraceRow* r, unsigned int row_id)
 
        // Add selector container
        vlayout->addWidget(r->selector_container);
-       r->selector_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
-       r->selector_container->setMinimumSize(0, 3 * default_row_height_);                            // FIXME
-       r->selector_container->setLayout(new QHBoxLayout());
+       r->selector_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+       r->selector_container->setLayout(new FlowLayout(r->selector_container));
 
        r->selector_container->setAutoFillBackground(true);
        r->selector_container->setPalette(selector_palette);
@@ -1289,7 +1342,7 @@ void DecodeTrace::update_rows()
                        nr.expanded = false;
                        nr.collapsing = false;
                        nr.expand_marker_shape = default_marker_shape_;
-                       nr.container = new QWidget(owner_->view()->scrollarea());
+                       nr.container = new ContainerWidget(owner_->view()->scrollarea());
                        nr.header_container = new QWidget(nr.container);
                        nr.selector_container = new QWidget(nr.container);
 
@@ -1365,11 +1418,23 @@ void DecodeTrace::set_row_collapsed(DecodeTraceRow* r)
 void DecodeTrace::update_expanded_rows()
 {
        for (DecodeTraceRow& r : rows_) {
-               r.container->move(2 * ArrowSize,
-                       get_row_y(&r) + default_row_height_);
-
-               r.container->resize(owner_->view()->viewport()->width() - r.container->pos().x(),
-                       r.height - 2 * default_row_height_);
+               if (r.expanding || r.expanded)
+                       r.expanded_height = 2 * default_row_height_ + r.container->sizeHint().height();
+
+               if (r.expanded)
+                       r.height = r.expanded_height;
+
+               int x = 2 * ArrowSize;
+               int y = get_row_y(&r) + default_row_height_;
+               // Only update the position if it actually changes
+               if ((x != r.container->pos().x()) || (y != r.container->pos().y()))
+                       r.container->move(x, y);
+
+               int w = owner_->view()->viewport()->width() - x;
+               int h = r.height - 2 * default_row_height_;
+               // Only update the dimension if they actually change
+               if ((w != r.container->sizeHint().width()) || (h != r.container->sizeHint().height()))
+                       r.container->resize(w, h);
        }
 }
 
@@ -1523,6 +1588,14 @@ void DecodeTrace::on_show_hide_class(QWidget* sender)
        owner_->row_item_appearance_changed(false, true);
 }
 
+void DecodeTrace::on_row_container_resized(QWidget* sender)
+{
+       sender->update();
+
+       owner_->extents_changed(false, true);
+       owner_->row_item_appearance_changed(false, true);
+}
+
 void DecodeTrace::on_copy_annotation_to_clipboard()
 {
        if (!selected_row_)
index 5c756b2645743738ee26682ea2772c755d0da018..9877dc11ff63c7ece0c0cdc945ab5b3c31cb6335 100644 (file)
@@ -81,6 +81,8 @@ class DecoderGroupBox;
 namespace views {
 namespace trace {
 
+class ContainerWidget;
+
 struct DecodeTraceRow {
        // When adding a field, make sure it's initialized properly in
        // DecodeTrace::update_rows()
@@ -92,7 +94,7 @@ struct DecodeTraceRow {
        QPolygon expand_marker_shape;
        float anim_height, anim_shape;
 
-       QWidget* container;
+       ContainerWidget* container;
        QWidget* header_container;
        QWidget* selector_container;
        vector<QCheckBox*> selectors;
@@ -101,6 +103,19 @@ struct DecodeTraceRow {
        map<uint32_t, QColor> ann_class_color;
 };
 
+class ContainerWidget : public QWidget
+{
+       Q_OBJECT
+
+public:
+       ContainerWidget(QWidget *parent = nullptr);
+
+       virtual void resizeEvent(QResizeEvent* event);
+
+Q_SIGNALS:
+       void widgetResized(QWidget* sender);
+};
+
 class DecodeTrace : public Trace
 {
        Q_OBJECT
@@ -259,6 +274,7 @@ private Q_SLOTS:
        void on_show_hide_decoder(int index);
        void on_show_hide_row(int row_id);
        void on_show_hide_class(QWidget* sender);
+       void on_row_container_resized(QWidget* sender);
 
        void on_copy_annotation_to_clipboard();
 
diff --git a/pv/widgets/flowlayout.cpp b/pv/widgets/flowlayout.cpp
new file mode 100644 (file)
index 0000000..31ba7fe
--- /dev/null
@@ -0,0 +1,212 @@
+/****************************************************************************
+ **
+ ** Copyright (C) 2015 The Qt Company Ltd.
+ ** Contact: http://www.qt.io/licensing/
+ **
+ ** This file is part of the examples of the Qt Toolkit.
+ **
+ ** $QT_BEGIN_LICENSE:BSD$
+ ** You may use this file under the terms of the BSD license as follows:
+ **
+ ** "Redistribution and use in source and binary forms, with or without
+ ** modification, are permitted provided that the following conditions are
+ ** met:
+ **   * Redistributions of source code must retain the above copyright
+ **     notice, this list of conditions and the following disclaimer.
+ **   * Redistributions in binary form must reproduce the above copyright
+ **     notice, this list of conditions and the following disclaimer in
+ **     the documentation and/or other materials provided with the
+ **     distribution.
+ **   * Neither the name of The Qt Company Ltd nor the names of its
+ **     contributors may be used to endorse or promote products derived
+ **     from this software without specific prior written permission.
+ **
+ **
+ ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+ **
+ ** $QT_END_LICENSE$
+ **
+ ****************************************************************************/
+
+#include <QWidget>
+
+#include "flowlayout.hpp"
+
+FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing) :
+       QLayout(parent),
+       m_parent(parent),
+       m_hSpace(hSpacing),
+       m_vSpace(vSpacing)
+{
+       setContentsMargins(margin, margin, margin, margin);
+}
+
+FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing) :
+       m_parent(nullptr),
+       m_hSpace(hSpacing),
+       m_vSpace(vSpacing)
+{
+       setContentsMargins(margin, margin, margin, margin);
+}
+
+FlowLayout::~FlowLayout()
+{
+       QLayoutItem *item;
+       while ((item = takeAt(0)))
+               delete item;
+}
+
+void FlowLayout::addItem(QLayoutItem *item)
+{
+       itemList.append(item);
+}
+
+int FlowLayout::horizontalSpacing() const
+{
+       if (m_hSpace >= 0)
+               return m_hSpace;
+       else
+               return smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
+}
+
+int FlowLayout::verticalSpacing() const
+{
+       if (m_vSpace >= 0)
+               return m_vSpace;
+       else
+               return smartSpacing(QStyle::PM_LayoutVerticalSpacing);
+}
+
+int FlowLayout::count() const
+{
+       return itemList.size();
+}
+
+QLayoutItem *FlowLayout::itemAt(int index) const
+{
+       return itemList.value(index);
+}
+
+QLayoutItem *FlowLayout::takeAt(int index)
+{
+       if ((index >= 0) && (index < itemList.size()))
+               return itemList.takeAt(index);
+       else
+               return 0;
+}
+
+Qt::Orientations FlowLayout::expandingDirections() const
+{
+       return Qt::Horizontal | Qt::Vertical;
+}
+
+bool FlowLayout::hasHeightForWidth() const
+{
+       return true;
+}
+
+int FlowLayout::heightForWidth(int width) const
+{
+       int height = doLayout(QRect(0, 0, width, 0), true);
+       return height;
+}
+
+void FlowLayout::setGeometry(const QRect &rect)
+{
+       QLayout::setGeometry(rect);
+       doLayout(rect, false);
+}
+
+QSize FlowLayout::sizeHint() const
+{
+       return minimumSize();
+}
+
+QSize FlowLayout::minimumSize() const
+{
+       QSize size(0, 0);
+
+       for (QLayoutItem* item : itemList) {
+               int w = item->geometry().x() + item->geometry().width();
+               if (w > size.width())
+                       size.setWidth(w);
+
+               int h = item->geometry().y() + item->geometry().width();
+               if (h > size.height())
+                       size.setHeight(h);
+       }
+
+       size += QSize(2 * margin(), 2 * margin());
+
+       return size;
+}
+
+int FlowLayout::doLayout(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;
+       for (QLayoutItem* item : itemList) {
+               QWidget* w = item->widget();
+
+               int spaceX = horizontalSpacing();
+               if (spaceX == -1)
+                       spaceX = w->style()->layoutSpacing(
+                               QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
+
+               int spaceY = verticalSpacing();
+               if (spaceY == -1)
+                       spaceY = w->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());
+       }
+
+       int height = y + lineHeight - rect.y() + bottom;
+
+       if (m_parent)
+               m_parent->setMinimumHeight(height);
+
+       return height;
+}
+
+int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const
+{
+       QObject *parent = this->parent();
+
+       if (!parent)
+               return -1;
+
+       if (parent->isWidgetType()) {
+               QWidget *pw = qobject_cast<QWidget*>(parent);
+               return pw->style()->pixelMetric(pm, 0, pw);
+       } else
+               return static_cast<QLayout*>(parent)->spacing();
+}
diff --git a/pv/widgets/flowlayout.hpp b/pv/widgets/flowlayout.hpp
new file mode 100644 (file)
index 0000000..81407d1
--- /dev/null
@@ -0,0 +1,78 @@
+/****************************************************************************
+ **
+ ** Copyright (C) 2015 The Qt Company Ltd.
+ ** Contact: http://www.qt.io/licensing/
+ **
+ ** This file is part of the examples of the Qt Toolkit.
+ **
+ ** $QT_BEGIN_LICENSE:BSD$
+ ** You may use this file under the terms of the BSD license as follows:
+ **
+ ** "Redistribution and use in source and binary forms, with or without
+ ** modification, are permitted provided that the following conditions are
+ ** met:
+ **   * Redistributions of source code must retain the above copyright
+ **     notice, this list of conditions and the following disclaimer.
+ **   * Redistributions in binary form must reproduce the above copyright
+ **     notice, this list of conditions and the following disclaimer in
+ **     the documentation and/or other materials provided with the
+ **     distribution.
+ **   * Neither the name of The Qt Company Ltd nor the names of its
+ **     contributors may be used to endorse or promote products derived
+ **     from this software without specific prior written permission.
+ **
+ **
+ ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+ **
+ ** $QT_END_LICENSE$
+ **
+ ****************************************************************************/
+
+#ifndef FLOWLAYOUT_H
+#define FLOWLAYOUT_H
+
+#include <QLayout>
+#include <QRect>
+#include <QStyle>
+#include <QWidgetItem>
+
+class FlowLayout : public QLayout
+{
+public:
+       FlowLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1);
+       FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1);
+       ~FlowLayout();
+
+       void addItem(QLayoutItem *item);
+       int horizontalSpacing() const;
+       int verticalSpacing() const;
+       Qt::Orientations expandingDirections() const;
+       bool hasHeightForWidth() const;
+       int heightForWidth(int) const;
+       int count() const;
+       QLayoutItem *itemAt(int index) const;
+       QSize minimumSize() const;
+       void setGeometry(const QRect &rect);
+       QSize sizeHint() const;
+       QLayoutItem *takeAt(int index);
+
+private:
+       int doLayout(const QRect &rect, bool testOnly) const;
+       int smartSpacing(QStyle::PixelMetric pm) const;
+
+       QWidget* m_parent;
+       QList<QLayoutItem*> itemList;
+       int m_hSpace, m_vSpace;
+};
+
+#endif
index 99c490f882ec0dd78190ea2a75e20358226c7bb7..dc874d59cc5d88498641c7ee0e655fa2d1b1b268 100644 (file)
@@ -85,6 +85,7 @@ set(pulseview_TEST_SOURCES
        ${PROJECT_SOURCE_DIR}/pv/widgets/colorpopup.cpp
        ${PROJECT_SOURCE_DIR}/pv/widgets/devicetoolbutton.cpp
        ${PROJECT_SOURCE_DIR}/pv/widgets/exportmenu.cpp
+       ${PROJECT_SOURCE_DIR}/pv/widgets/flowlayout.cpp
        ${PROJECT_SOURCE_DIR}/pv/widgets/importmenu.cpp
        ${PROJECT_SOURCE_DIR}/pv/widgets/popup.cpp
        ${PROJECT_SOURCE_DIR}/pv/widgets/popuptoolbutton.cpp
@@ -153,6 +154,7 @@ set(pulseview_TEST_HEADERS
        ${PROJECT_SOURCE_DIR}/pv/widgets/colorpopup.hpp
        ${PROJECT_SOURCE_DIR}/pv/widgets/devicetoolbutton.hpp
        ${PROJECT_SOURCE_DIR}/pv/widgets/exportmenu.hpp
+       ${PROJECT_SOURCE_DIR}/pv/widgets/flowlayout.hpp
        ${PROJECT_SOURCE_DIR}/pv/widgets/importmenu.hpp
        ${PROJECT_SOURCE_DIR}/pv/widgets/popup.hpp
        ${PROJECT_SOURCE_DIR}/pv/widgets/popuptoolbutton.hpp