1 /*
2     This file is part of the KDE frameworks
3     SPDX-FileCopyrightText: 2014 Aurélien Gâteau <agateau@kde.org>
4 
5     SPDX-License-Identifier: LGPL-2.1-or-later
6 */
7 #include <kcolumnresizer.h>
8 
9 #include "loggingcategory.h"
10 
11 #include <QEvent>
12 #include <QGridLayout>
13 #include <QSet>
14 #include <QTimer>
15 #include <QWidget>
16 
17 class FormLayoutWidgetItem : public QWidgetItem
18 {
19 public:
FormLayoutWidgetItem(QWidget * widget,QFormLayout * formLayout,QFormLayout::ItemRole itemRole)20     FormLayoutWidgetItem(QWidget *widget, QFormLayout *formLayout, QFormLayout::ItemRole itemRole)
21         : QWidgetItem(widget)
22         , m_formLayout(formLayout)
23         , m_itemRole(itemRole)
24     {
25     }
26 
setWidth(int width)27     void setWidth(int width)
28     {
29         if (width != m_width) {
30             m_width = width;
31             invalidate();
32         }
33     }
34 
formLayout() const35     QFormLayout *formLayout() const
36     {
37         return m_formLayout;
38     }
39 
sizeHint() const40     QSize sizeHint() const override
41     {
42         QSize size = QWidgetItem::sizeHint();
43         if (m_width != -1) {
44             size.setWidth(m_width);
45         }
46         return size;
47     }
48 
minimumSize() const49     QSize minimumSize() const override
50     {
51         QSize size = QWidgetItem::minimumSize();
52         if (m_width != -1) {
53             size.setWidth(m_width);
54         }
55         return size;
56     }
57 
maximumSize() const58     QSize maximumSize() const override
59     {
60         QSize size = QWidgetItem::maximumSize();
61         if (m_width != -1) {
62             size.setWidth(m_width);
63         }
64         return size;
65     }
66 
setGeometry(const QRect & _rect)67     void setGeometry(const QRect &_rect) override
68     {
69         QRect rect = _rect;
70         int width = widget()->sizeHint().width();
71         if (m_itemRole == QFormLayout::LabelRole && m_formLayout->labelAlignment() & Qt::AlignRight) {
72             rect.setLeft(rect.right() - width);
73         }
74         QWidgetItem::setGeometry(rect);
75     }
76 
77 private:
78     QFormLayout *const m_formLayout;
79     int m_width = -1;
80     const QFormLayout::ItemRole m_itemRole;
81 };
82 
83 struct GridColumnInfo {
GridColumnInfoGridColumnInfo84     GridColumnInfo(QGridLayout *layout_, int column_)
85         : layout(layout_)
86         , column(column_)
87     {
88     }
89     QGridLayout *layout;
90     int column;
91 };
92 
93 class KColumnResizerPrivate
94 {
95 public:
KColumnResizerPrivate(KColumnResizer * q_ptr)96     KColumnResizerPrivate(KColumnResizer *q_ptr)
97         : q(q_ptr)
98         , m_updateTimer(new QTimer(q))
99     {
100         m_updateTimer->setSingleShot(true);
101         m_updateTimer->setInterval(0);
102         QObject::connect(m_updateTimer, &QTimer::timeout, q, [this]() {
103             updateWidth();
104         });
105     }
106 
scheduleWidthUpdate()107     void scheduleWidthUpdate()
108     {
109         m_updateTimer->start();
110     }
111 
updateWidth()112     void updateWidth()
113     {
114         int width = 0;
115         for (QWidget *widget : std::as_const(m_widgets)) {
116             width = qMax(widget->sizeHint().width(), width);
117         }
118         for (FormLayoutWidgetItem *item : std::as_const(m_formWidgetItemList)) {
119             item->setWidth(width);
120             item->formLayout()->update();
121         }
122         for (const GridColumnInfo &info : std::as_const(m_gridColumnInfoList)) {
123             info.layout->setColumnMinimumWidth(info.column, width);
124         }
125     }
126 
addWidgetsFromGridLayout(QGridLayout * layout,int column)127     void addWidgetsFromGridLayout(QGridLayout *layout, int column)
128     {
129         for (int row = 0; row < layout->rowCount(); ++row) {
130             QLayoutItem *item = layout->itemAtPosition(row, column);
131             if (!item) {
132                 continue;
133             }
134             QWidget *widget = item->widget();
135             if (!widget) {
136                 continue;
137             }
138             q->addWidget(widget);
139         }
140         m_gridColumnInfoList << GridColumnInfo(layout, column);
141     }
142 
addWidgetsFromFormLayout(QFormLayout * layout,QFormLayout::ItemRole role)143     void addWidgetsFromFormLayout(QFormLayout *layout, QFormLayout::ItemRole role)
144     {
145         for (int row = 0; row < layout->rowCount(); ++row) {
146             QLayoutItem *item = layout->itemAt(row, role);
147             if (!item) {
148                 continue;
149             }
150             QWidget *widget = item->widget();
151             if (!widget) {
152                 continue;
153             }
154             // Replace the QWidgetItem with our own
155             layout->removeItem(item);
156             delete item;
157             FormLayoutWidgetItem *newItem = new FormLayoutWidgetItem(widget, layout, role);
158             layout->setItem(row, role, newItem);
159             m_formWidgetItemList << newItem;
160 
161             q->addWidget(widget);
162         }
163     }
164 
165     KColumnResizer *q;
166     QTimer *m_updateTimer;
167     QSet<QWidget *> m_widgets;
168     QList<FormLayoutWidgetItem *> m_formWidgetItemList;
169     QList<GridColumnInfo> m_gridColumnInfoList;
170 };
171 
KColumnResizer(QObject * parent)172 KColumnResizer::KColumnResizer(QObject *parent)
173     : QObject(parent)
174     , d(new KColumnResizerPrivate(this))
175 {
176 }
177 
178 KColumnResizer::~KColumnResizer() = default;
179 
addWidget(QWidget * widget)180 void KColumnResizer::addWidget(QWidget *widget)
181 {
182     if (d->m_widgets.contains(widget)) {
183         return;
184     }
185     d->m_widgets.insert(widget);
186     widget->installEventFilter(this);
187     d->scheduleWidthUpdate();
188 }
189 
removeWidget(QWidget * widget)190 void KColumnResizer::removeWidget(QWidget *widget)
191 {
192     if (!d->m_widgets.remove(widget)) {
193         return;
194     }
195     widget->removeEventFilter(this);
196     d->scheduleWidthUpdate();
197 }
198 
eventFilter(QObject *,QEvent * event)199 bool KColumnResizer::eventFilter(QObject *, QEvent *event)
200 {
201     if (event->type() == QEvent::Resize) {
202         d->scheduleWidthUpdate();
203     }
204     return false;
205 }
206 
addWidgetsFromLayout(QLayout * layout,int column)207 void KColumnResizer::addWidgetsFromLayout(QLayout *layout, int column)
208 {
209     Q_ASSERT(column >= 0);
210     if (column < 0) {
211         qCWarning(KWidgetsAddonsLog) << "column must be >= 0";
212         return;
213     }
214     QGridLayout *gridLayout = qobject_cast<QGridLayout *>(layout);
215     if (gridLayout) {
216         d->addWidgetsFromGridLayout(gridLayout, column);
217         return;
218     }
219     QFormLayout *formLayout = qobject_cast<QFormLayout *>(layout);
220     if (formLayout) {
221         Q_ASSERT(column <= QFormLayout::SpanningRole);
222         if (column > QFormLayout::SpanningRole) {
223             qCWarning(KWidgetsAddonsLog) << "column should not be more than" << QFormLayout::SpanningRole << "for QFormLayout";
224             return;
225         }
226         QFormLayout::ItemRole role = static_cast<QFormLayout::ItemRole>(column);
227         d->addWidgetsFromFormLayout(formLayout, role);
228     } else {
229         qCWarning(KWidgetsAddonsLog) << "Don't know how to handle layout" << layout;
230         Q_ASSERT(0);
231     }
232 }
233 
234 #include <moc_kcolumnresizer.cpp>
235 // vi: ts=4 sw=4 et
236