File indexing completed on 2024-05-12 16:39:54

0001 /* This file is part of the KDE project
0002    Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr>
0003    Copyright (C) 2007-2012 Jarosław Staniek <staniek@kde.org>
0004 
0005    This library is free software; you can redistribute it and/or
0006    modify it under the terms of the GNU Library General Public
0007    License as published by the Free Software Foundation; either
0008    version 2 of the License, or (at your option) any later version.
0009 
0010    This library is distributed in the hope that it will be useful,
0011    but WITHOUT ANY WARRANTY; without even the implied warranty of
0012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013    Library General Public License for more details.
0014 
0015    You should have received a copy of the GNU Library General Public License
0016    along with this library; see the file COPYING.LIB.  If not, write to
0017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018  * Boston, MA 02110-1301, USA.
0019 */
0020 
0021 #include "FlowLayout.h"
0022 #include "kexiutils_global.h"
0023 
0024 #include <QDebug>
0025 
0026 class Q_DECL_HIDDEN KexiFlowLayout::Private
0027 {
0028 public:
0029     Private();
0030     ~Private();
0031 
0032     QList<QLayoutItem*> list;
0033     int cached_width;
0034     int cached_hfw;
0035     bool justify;
0036     Qt::Orientation orientation;
0037     QSize cached_sizeHint;
0038     QSize cached_minSize;
0039 };
0040 
0041 KexiFlowLayout::Private::Private()
0042     : cached_width(0), cached_hfw(0), justify(false), orientation(Qt::Horizontal)
0043 {
0044 
0045 }
0046 
0047 KexiFlowLayout::Private::~Private()
0048 {
0049     qDeleteAll(list);
0050 }
0051 
0052 //// The layout itself
0053 
0054 KexiFlowLayout::KexiFlowLayout(QWidget *parent, int margin, int spacing)
0055     : QLayout(parent), d(new Private())
0056 {
0057     setMargin(margin);
0058     setSpacing(spacing);
0059 }
0060 
0061 KexiFlowLayout::KexiFlowLayout(QLayout* parent, int margin, int spacing)
0062     : QLayout(), d(new Private())
0063 {
0064     parent->addItem(this);
0065     setMargin(margin);
0066     setSpacing(spacing);
0067 }
0068 
0069 KexiFlowLayout::KexiFlowLayout(int margin, int spacing)
0070     : QLayout(), d(new Private())
0071 {
0072     setMargin(margin);
0073     setSpacing(spacing);
0074 }
0075 
0076 KexiFlowLayout::~KexiFlowLayout()
0077 {
0078     delete d;
0079 }
0080 
0081 void
0082 KexiFlowLayout::addItem(QLayoutItem *item)
0083 {
0084     d->list.append(item);
0085 }
0086 
0087 void KexiFlowLayout::addSpacing(int size)
0088 {
0089     if (d->orientation == Qt::Horizontal)
0090         addItem(new QSpacerItem(size, 0, QSizePolicy::Fixed, QSizePolicy::Minimum));
0091     else
0092         addItem(new QSpacerItem(0, size, QSizePolicy::Minimum, QSizePolicy::Fixed));
0093 }
0094 
0095 void KexiFlowLayout::insertWidget(int index, QWidget* widget, int stretch, Qt::Alignment alignment)
0096 {
0097     Q_UNUSED(stretch);
0098     QWidgetItem *wi = new QWidgetItem(widget);
0099     wi->setAlignment(alignment);
0100     d->list.insert(index, wi);
0101 }
0102 
0103 
0104 QList<QWidget*>* KexiFlowLayout::widgetList() const
0105 {
0106     QList<QWidget*> *list = new QList<QWidget*>();
0107     foreach(QLayoutItem* item, d->list) {
0108         if (item->widget())
0109             list->append(item->widget());
0110     }
0111     return list;
0112 }
0113 
0114 void KexiFlowLayout::invalidate()
0115 {
0116     QLayout::invalidate();
0117     d->cached_sizeHint = QSize();
0118     d->cached_minSize = QSize();
0119     d->cached_width = 0;
0120 }
0121 
0122 int KexiFlowLayout::count() const
0123 {
0124     return d->list.size();
0125 }
0126 
0127 bool KexiFlowLayout::isEmpty() const
0128 {
0129     return d->list.isEmpty();
0130 }
0131 
0132 bool KexiFlowLayout::hasHeightForWidth() const
0133 {
0134     return (d->orientation == Qt::Horizontal);
0135 }
0136 
0137 int KexiFlowLayout::heightForWidth(int w) const
0138 {
0139     if (d->cached_width != w) {
0140         // workaround to allow this method to stay 'const'
0141         KexiFlowLayout *mthis = const_cast<KexiFlowLayout*>(this);
0142         int h = mthis->simulateLayout(QRect(0, 0, w, 0));
0143         mthis->d->cached_hfw = h;
0144         mthis->d->cached_width = w;
0145         return h;
0146     }
0147     return d->cached_hfw;
0148 }
0149 
0150 QSize KexiFlowLayout::sizeHint() const
0151 {
0152     if (d->cached_sizeHint.isEmpty()) {
0153         KexiFlowLayout *mthis = const_cast<KexiFlowLayout*>(this);
0154         QRect r = QRect(0, 0, 2000, 2000);
0155         mthis->simulateLayout(r);
0156     }
0157     return d->cached_sizeHint;
0158 }
0159 
0160 QSize KexiFlowLayout::minimumSize() const
0161 {
0162 //! @todo Do we really need to simulate layout here?
0163 //!       I commented this out because it was impossible to stretch layout conveniently.
0164 //!       Now, minimum size is computed automatically based on item's minimumSize...
0165 #if 0
0166     if (d->cached_minSize.isEmpty()) {
0167         KexiFlowLayout *mthis = (KexiFlowLayout*)this;
0168         QRect r = QRect(0, 0, 2000, 2000);
0169         mthis->simulateLayout(r);
0170     }
0171 #endif
0172     return d->cached_minSize;
0173 }
0174 
0175 Qt::Orientations KexiFlowLayout::expandingDirections() const
0176 {
0177     if (d->orientation == Qt::Vertical)
0178         return Qt::Vertical;
0179     else
0180         return Qt::Horizontal;
0181 }
0182 
0183 void KexiFlowLayout::setGeometry(const QRect &r)
0184 {
0185     QLayout::setGeometry(r);
0186     if (d->orientation == Qt::Horizontal)
0187         doHorizontalLayout(r);
0188     else
0189         doVerticalLayout(r);
0190 }
0191 
0192 int KexiFlowLayout::simulateLayout(const QRect &r)
0193 {
0194     if (d->orientation == Qt::Horizontal)
0195         return doHorizontalLayout(r, true);
0196     else
0197         return doVerticalLayout(r, true);
0198 }
0199 
0200 inline void doHorizontalLayoutForLine(const QRect &r, const QList<QLayoutItem*>& currentLine,
0201                                       int spacing, bool justify, int& y, int& h, int& availableSpace, int& expandingWidgets,
0202                                       int& sizeHintWidth, int& minSizeWidth, int& lineMinHeight, bool testOnly)
0203 {
0204     QListIterator<QLayoutItem*> it2(currentLine);
0205     int wx = r.x();
0206     sizeHintWidth = 0 - spacing;
0207     minSizeWidth = 0 - spacing;
0208     lineMinHeight = 0;
0209     while (it2.hasNext()) {
0210         QLayoutItem *item = it2.next();
0211         QSize itemSizeHint = item->sizeHint(); // we cache these ones because it can take
0212         QSize itemMinSize = item->minimumSize(); // a while to get them
0213         QSize s;
0214         if (justify) {
0215             if (expandingWidgets != 0) {
0216                 if (item->expandingDirections() & Qt::Horizontal)
0217                     s = QSize(
0218                             qMin(itemSizeHint.width() + availableSpace / expandingWidgets, r.width()),
0219                             itemSizeHint.height()
0220                         );
0221                 else
0222                     s = QSize(qMin(itemSizeHint.width(), r.width()), itemSizeHint.height());
0223             } else
0224                 s = QSize(
0225                         qMin(itemSizeHint.width() + availableSpace / (int)currentLine.count(), r.width()),
0226                         itemSizeHint.height()
0227                     );
0228         } else
0229             s = QSize(qMin(itemSizeHint.width(), r.width()), itemSizeHint.height());
0230         if (!testOnly) {
0231             // adjust vertical position depending on vertical alignment
0232             int add_y;
0233             if (item->alignment() & Qt::AlignBottom)
0234                 add_y = h - s.height() - 1;
0235             else if (item->alignment() & Qt::AlignVCenter)
0236                 add_y = (h - s.height() - 1) / 2;
0237             else
0238                 add_y = 0; // Qt::AlignTop
0239             item->setGeometry(QRect(QPoint(wx, y + add_y), s));
0240         }
0241         wx = wx + s.width() + spacing;
0242         minSizeWidth = minSizeWidth + spacing + itemMinSize.width();
0243         sizeHintWidth = sizeHintWidth + spacing +  itemSizeHint.width();
0244         lineMinHeight = qMax(lineMinHeight, itemMinSize.height());
0245     }
0246 }
0247 
0248 int KexiFlowLayout::doHorizontalLayout(const QRect &r, bool testOnly)
0249 {
0250     int x = r.x();
0251     int y = r.y();
0252     int h = 0; // height of this line
0253     int availableSpace = r.width() + spacing();
0254     int expandingWidgets = 0; // number of widgets in the line with QSizePolicy == Expanding
0255     QListIterator<QLayoutItem*> it(d->list);
0256     QList<QLayoutItem*> currentLine;
0257     QSize minSize, sizeHint(20, 20);
0258     int minSizeHeight = 0 - spacing();
0259 
0260     while (it.hasNext()) {
0261         QLayoutItem *o = it.next();
0262         if (o->isEmpty()) // do not consider hidden widgets
0263             continue;
0264 
0265 //  qDebug() << o->widget()->className() << " " << o->widget()->name();
0266         QSize oSizeHint = o->sizeHint(); // we cache these ones because it can take
0267         // a while to get it (eg for child layouts)
0268         if ((x + oSizeHint.width()) > r.right() && h > 0) {
0269             // do the layout of current line
0270             int sizeHintWidth, minSizeWidth, lineMinHeight;
0271             doHorizontalLayoutForLine(r, currentLine,
0272                                       spacing(), d->justify, y, h, availableSpace, expandingWidgets,
0273                                       sizeHintWidth, minSizeWidth, lineMinHeight, testOnly);
0274 
0275             sizeHint = sizeHint.expandedTo(QSize(sizeHintWidth, 0));
0276             minSize = minSize.expandedTo(QSize(minSizeWidth, 0));
0277             minSizeHeight = minSizeHeight + spacing() + lineMinHeight;
0278             // start a new line
0279             y = y + spacing() + h;
0280             h = 0;
0281             x = r.x();
0282             currentLine.clear();
0283             expandingWidgets = 0;
0284             availableSpace = r.width() + spacing();
0285         }
0286 
0287         x = x + spacing() + oSizeHint.width();
0288         h = qMax(h,  oSizeHint.height());
0289         currentLine.append(o);
0290         if (o->expandingDirections() & Qt::Horizontal)
0291             ++expandingWidgets;
0292         availableSpace = qMax(0, availableSpace - spacing() - oSizeHint.width());
0293     }
0294 
0295     // don't forget to layout the last line
0296     int sizeHintWidth, minSizeWidth, lineMinHeight;
0297     doHorizontalLayoutForLine(r, currentLine,
0298                               spacing(), d->justify, y, h, availableSpace, expandingWidgets,
0299                               sizeHintWidth, minSizeWidth, lineMinHeight, testOnly);
0300 
0301     sizeHint = sizeHint.expandedTo(QSize(sizeHintWidth, y + spacing() + h));
0302     minSizeHeight = minSizeHeight + spacing() + lineMinHeight;
0303     minSize = minSize.expandedTo(QSize(minSizeWidth, minSizeHeight));
0304 
0305     // store sizeHint() and minimumSize()
0306     d->cached_sizeHint = sizeHint + QSize(2 * margin(), 2 * margin());
0307     d->cached_minSize = minSize + QSize(2 * margin() , 2 * margin());
0308     // return our height
0309     return y + h - r.y();
0310 }
0311 
0312 inline void doVerticalLayoutForLine(const QRect &r, const QList<QLayoutItem*>& currentLine,
0313                                     int spacing, bool justify, int& x, int& w, int& availableSpace, int& expandingWidgets,
0314                                     int& sizeHintHeight, int& minSizeHeight, int& colMinWidth, bool testOnly)
0315 {
0316     QListIterator<QLayoutItem*> it2(currentLine);
0317     int wy = r.y();
0318     sizeHintHeight = 0 - spacing;
0319     minSizeHeight = 0 - spacing;
0320     colMinWidth = 0;
0321     while (it2.hasNext()) {
0322         QLayoutItem *item = it2.next();
0323         QSize itemSizeHint = item->sizeHint(); // we cache these ones because it can take
0324         QSize itemMinSize = item->minimumSize(); // a while to get them
0325         QSize s;
0326         if (justify) {
0327             if (expandingWidgets != 0) {
0328                 if (item->expandingDirections() & Qt::Vertical)
0329                     s = QSize(
0330                             itemSizeHint.width(),
0331                             qMin(itemSizeHint.height() + availableSpace / expandingWidgets, r.height())
0332                         );
0333                 else
0334                     s = QSize(itemSizeHint.width(), qMin(itemSizeHint.height(), r.height()));
0335             } else
0336                 s = QSize(
0337                         itemSizeHint.width(),
0338                         qMin(itemSizeHint.height() + availableSpace / (int)currentLine.count(), r.height())
0339                     );
0340         } else
0341             s = QSize(itemSizeHint.width(), qMin(itemSizeHint.height(), r.height()));
0342         if (!testOnly) {
0343             // adjust horizontal position depending on vertical alignment
0344             int add_x;
0345             if (item->alignment() & Qt::AlignRight)
0346                 add_x = w - s.width() - 1;
0347             else if (item->alignment() & Qt::AlignHCenter)
0348                 add_x = (w - s.width() - 1) / 2;
0349             else
0350                 add_x = 0; // Qt::AlignLeft
0351             item->setGeometry(QRect(QPoint(x + add_x, wy), s));
0352         }
0353         wy = wy + s.height() + spacing;
0354         minSizeHeight = minSizeHeight + spacing + itemMinSize.height();
0355         sizeHintHeight = sizeHintHeight + spacing + itemSizeHint.height();
0356         colMinWidth = qMax(colMinWidth, itemMinSize.width());
0357     }
0358 }
0359 
0360 int KexiFlowLayout::doVerticalLayout(const QRect &r, bool testOnly)
0361 {
0362     int x = r.x();
0363     int y = r.y();
0364     int w = 0; // width of this line
0365     int availableSpace = r.height() + spacing();
0366     int expandingWidgets = 0; // number of widgets in the line with QSizePolicy == Expanding
0367     QListIterator<QLayoutItem*> it(d->list);
0368     QList<QLayoutItem*> currentLine;
0369     QSize minSize, sizeHint(20, 20);
0370     int minSizeWidth = 0 - spacing();
0371 
0372     while (it.hasNext()) {
0373         QLayoutItem *o = it.next();
0374         if (o->isEmpty()) // do not consider hidden widgets
0375             continue;
0376 
0377         QSize oSizeHint = o->sizeHint(); // we cache these ones because it can take
0378         // a while to get it (eg for child layouts)
0379         if (y + oSizeHint.height() > r.bottom() && w > 0) {
0380             // do the layout of current line
0381             int sizeHintHeight, minSizeHeight, colMinWidth;
0382             doVerticalLayoutForLine(r, currentLine,
0383                                     spacing(), d->justify, y, w, availableSpace, expandingWidgets,
0384                                     sizeHintHeight, minSizeHeight, colMinWidth, testOnly);
0385 
0386             sizeHint = sizeHint.expandedTo(QSize(0, sizeHintHeight));
0387             minSize = minSize.expandedTo(QSize(0, minSizeHeight));
0388             minSizeWidth = minSizeWidth + spacing() + colMinWidth;
0389             // start a new column
0390             x = x + spacing() + w;
0391             w = 0;
0392             y = r.y();
0393             currentLine.clear();
0394             expandingWidgets = 0;
0395             availableSpace = r.height() + spacing();
0396         }
0397 
0398         y = y + spacing() + oSizeHint.height();
0399         w = qMax(w,  oSizeHint.width());
0400         currentLine.append(o);
0401         if (o->expandingDirections() & Qt::Vertical)
0402             ++expandingWidgets;
0403         availableSpace = qMax(0, availableSpace - spacing() - oSizeHint.height());
0404     }
0405 
0406     // don't forget to layout the last line
0407     int sizeHintHeight, minSizeHeight, colMinWidth;
0408     doVerticalLayoutForLine(r, currentLine,
0409                             spacing(), d->justify, y, w, availableSpace, expandingWidgets,
0410                             sizeHintHeight, minSizeHeight, colMinWidth, testOnly);
0411 
0412     sizeHint = sizeHint.expandedTo(QSize(x + spacing() + w, sizeHintHeight));
0413     minSizeWidth = minSizeWidth + spacing() + colMinWidth;
0414     minSize = minSize.expandedTo(QSize(minSizeWidth, minSizeHeight));
0415 
0416     // store sizeHint() and minimumSize()
0417     d->cached_sizeHint = sizeHint + QSize(2 * margin(), 2 * margin());
0418     d->cached_minSize = minSize + QSize(2 * margin(), 2 * margin());
0419     // return our width
0420     return x + w - r.x();
0421 }
0422 
0423 QLayoutItem *KexiFlowLayout::itemAt(int index) const
0424 {
0425     return d->list.value(index);
0426 }
0427 
0428 QLayoutItem *KexiFlowLayout::takeAt(int index)
0429 {
0430     if (index >= 0 && index < d->list.size())
0431         return d->list.takeAt(index);
0432 
0433     return 0;
0434 }
0435 
0436 void KexiFlowLayout::setOrientation(Qt::Orientation orientation)
0437 {
0438     d->orientation = orientation;
0439 }
0440 
0441 Qt::Orientation KexiFlowLayout::orientation() const
0442 {
0443     return d->orientation;
0444 }
0445 
0446 void KexiFlowLayout::setJustified(bool justify)
0447 {
0448     d->justify = justify;
0449 }
0450 
0451 bool KexiFlowLayout::isJustified() const
0452 {
0453     return d->justify;
0454 }