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 }