1 /* BEGIN_COMMON_COPYRIGHT_HEADER
2  * (c)LGPL2+
3  *
4  * LXQt - a lightweight, Qt based, desktop toolset
5  * https://lxqt.org
6  *
7  * Copyright: 2012 Razor team
8  * Authors:
9  *   Alexander Sokoloff <sokoloff.a@gmail.com>
10  *
11  * This program or library is free software; you can redistribute it
12  * and/or modify it under the terms of the GNU Lesser General Public
13  * License as published by the Free Software Foundation; either
14  * version 2.1 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Lesser General Public License for more details.
20 
21  * You should have received a copy of the GNU Lesser General
22  * Public License along with this library; if not, write to the
23  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24  * Boston, MA 02110-1301 USA
25  *
26  * END_COMMON_COPYRIGHT_HEADER */
27 
28 
29 #include "lxqtgridlayout.h"
30 #include <QDebug>
31 #include <cmath>
32 #include <QWidget>
33 #include <QVariantAnimation>
34 
35 using namespace LXQt;
36 
37 class LXQt::GridLayoutPrivate
38 {
39 public:
40     GridLayoutPrivate();
41     ~GridLayoutPrivate();
42 
43     QList<QLayoutItem*> mItems;
44     int mRowCount;
45     int mColumnCount;
46     GridLayout::Direction mDirection;
47 
48     bool mIsValid;
49     QSize mCellSizeHint;
50     QSize mCellMaxSize;
51     int mVisibleCount;
52     GridLayout::Stretch mStretch;
53     bool mAnimate;
54     int mAnimatedItems; //!< counter of currently animated items
55 
56 
57     void updateCache();
58     int rows() const;
59     int cols() const;
60     void setItemGeometry(QLayoutItem * item, QRect const & geometry);
61     QSize mPrefCellMinSize;
62     QSize mPrefCellMaxSize;
63     QRect mOccupiedGeometry;
64 };
65 
66 namespace
67 {
68     class ItemMoveAnimation : public QVariantAnimation
69     {
70     public:
animate(QLayoutItem * item,QRect const & geometry,LXQt::GridLayoutPrivate * layout)71         static void animate(QLayoutItem * item, QRect const & geometry, LXQt::GridLayoutPrivate * layout)
72         {
73             ItemMoveAnimation* animation = new ItemMoveAnimation(item);
74             animation->setStartValue(item->geometry());
75             animation->setEndValue(geometry);
76             ++layout->mAnimatedItems;
77             connect(animation, &QAbstractAnimation::finished, [layout] { --layout->mAnimatedItems; Q_ASSERT(0 <= layout->mAnimatedItems); });
78             animation->start(DeleteWhenStopped);
79         }
80 
ItemMoveAnimation(QLayoutItem * item)81         ItemMoveAnimation(QLayoutItem *item)
82             : mItem(item)
83         {
84             setDuration(150);
85         }
86 
updateCurrentValue(const QVariant & current)87         void updateCurrentValue(const QVariant &current) override
88         {
89             mItem->setGeometry(current.toRect());
90         }
91 
92     private:
93         QLayoutItem* mItem;
94 
95     };
96 }
97 
98 
99 /************************************************
100 
101  ************************************************/
GridLayoutPrivate()102 GridLayoutPrivate::GridLayoutPrivate()
103 {
104     mColumnCount = 0;
105     mRowCount = 0;
106     mDirection = GridLayout::LeftToRight;
107     mIsValid = false;
108     mVisibleCount = 0;
109     mStretch = GridLayout::StretchHorizontal | GridLayout::StretchVertical;
110     mAnimate = false;
111     mAnimatedItems = 0;
112     mPrefCellMinSize = QSize(0,0);
113     mPrefCellMaxSize = QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
114 }
115 
116 /************************************************
117 
118  ************************************************/
~GridLayoutPrivate()119 GridLayoutPrivate::~GridLayoutPrivate()
120 {
121     qDeleteAll(mItems);
122 }
123 
124 
125 /************************************************
126 
127  ************************************************/
updateCache()128 void GridLayoutPrivate::updateCache()
129 {
130     mCellSizeHint = QSize(0, 0);
131     mCellMaxSize = QSize(0, 0);
132     mVisibleCount = 0;
133 
134     const int N = mItems.count();
135     for (int i=0; i < N; ++i)
136     {
137         QLayoutItem *item = mItems.at(i);
138         if (!item->widget() || item->widget()->isHidden())
139             continue;
140 
141         int h = qBound(item->minimumSize().height(),
142                        item->sizeHint().height(),
143                        item->maximumSize().height());
144 
145         int w = qBound(item->minimumSize().width(),
146                        item->sizeHint().width(),
147                        item->maximumSize().width());
148 
149         mCellSizeHint.rheight() = qMax(mCellSizeHint.height(), h);
150         mCellSizeHint.rwidth()  = qMax(mCellSizeHint.width(), w);
151 
152         mCellMaxSize.rheight() = qMax(mCellMaxSize.height(), item->maximumSize().height());
153         mCellMaxSize.rwidth()  = qMax(mCellMaxSize.width(), item->maximumSize().width());
154         mVisibleCount++;
155 
156 #if 0
157         qDebug() << "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-";
158         qDebug() << "item.min" << item->minimumSize().width();
159         qDebug() << "item.sz " << item->sizeHint().width();
160         qDebug() << "item.max" << item->maximumSize().width();
161         qDebug() << "w h" << w << h;
162         qDebug() << "wid.sizeHint" << item->widget()->sizeHint();
163         qDebug() << "mCellSizeHint:" << mCellSizeHint;
164         qDebug() << "mCellMaxSize: " << mCellMaxSize;
165         qDebug() << "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-";
166 #endif
167 
168     }
169     mCellSizeHint.rwidth() = qBound(mPrefCellMinSize.width(),  mCellSizeHint.width(),  mPrefCellMaxSize.width());
170     mCellSizeHint.rheight()= qBound(mPrefCellMinSize.height(), mCellSizeHint.height(), mPrefCellMaxSize.height());
171     mIsValid = !mCellSizeHint.isEmpty();
172 }
173 
174 
175 /************************************************
176 
177  ************************************************/
rows() const178 int GridLayoutPrivate::rows() const
179 {
180     if (mRowCount)
181         return mRowCount;
182 
183     if (!mColumnCount)
184         return 1;
185 
186     return ceil(mVisibleCount * 1.0 / mColumnCount);
187 }
188 
189 
190 /************************************************
191 
192  ************************************************/
cols() const193 int GridLayoutPrivate::cols() const
194 {
195     if (mColumnCount)
196         return mColumnCount;
197 
198     int rows = mRowCount;
199     if (!rows)
200         rows = 1;
201 
202     return ceil(mVisibleCount * 1.0 / rows);
203 }
204 
setItemGeometry(QLayoutItem * item,QRect const & geometry)205 void GridLayoutPrivate::setItemGeometry(QLayoutItem * item, QRect const & geometry)
206 {
207     mOccupiedGeometry |= geometry;
208     if (mAnimate)
209     {
210         ItemMoveAnimation::animate(item, geometry, this);
211     } else
212     {
213         item->setGeometry(geometry);
214     }
215 }
216 
217 
218 /************************************************
219 
220  ************************************************/
GridLayout(QWidget * parent)221 GridLayout::GridLayout(QWidget *parent):
222     QLayout(parent),
223     d_ptr(new GridLayoutPrivate())
224 {
225     // no space between items by default
226     setSpacing(0);
227 }
228 
229 
230 /************************************************
231 
232  ************************************************/
~GridLayout()233 GridLayout::~GridLayout()
234 {
235     delete d_ptr;
236 }
237 
238 
239 /************************************************
240 
241  ************************************************/
addItem(QLayoutItem * item)242 void GridLayout::addItem(QLayoutItem *item)
243 {
244     d_ptr->mItems.append(item);
245 }
246 
247 
248 /************************************************
249 
250  ************************************************/
itemAt(int index) const251 QLayoutItem *GridLayout::itemAt(int index) const
252 {
253     Q_D(const GridLayout);
254     if (index < 0 || index >= d->mItems.count())
255         return nullptr;
256 
257     return d->mItems.at(index);
258 }
259 
260 
261 /************************************************
262 
263  ************************************************/
takeAt(int index)264 QLayoutItem *GridLayout::takeAt(int index)
265 {
266     Q_D(GridLayout);
267     if (index < 0 || index >= d->mItems.count())
268         return nullptr;
269 
270     QLayoutItem *item = d->mItems.takeAt(index);
271     return item;
272 }
273 
274 
275 /************************************************
276 
277  ************************************************/
count() const278 int GridLayout::count() const
279 {
280     Q_D(const GridLayout);
281     return d->mItems.count();
282 }
283 
284 
285 /************************************************
286 
287  ************************************************/
invalidate()288 void GridLayout::invalidate()
289 {
290     Q_D(GridLayout);
291     d->mIsValid = false;
292     QLayout::invalidate();
293 }
294 
295 
296 /************************************************
297 
298  ************************************************/
rowCount() const299 int GridLayout::rowCount() const
300 {
301     Q_D(const GridLayout);
302     return d->mRowCount;
303 }
304 
305 
306 /************************************************
307 
308  ************************************************/
setRowCount(int value)309 void GridLayout::setRowCount(int value)
310 {
311     Q_D(GridLayout);
312     if (d->mRowCount != value)
313     {
314         d->mRowCount = value;
315         invalidate();
316     }
317 }
318 
319 
320 /************************************************
321 
322  ************************************************/
columnCount() const323 int GridLayout::columnCount() const
324 {
325     Q_D(const GridLayout);
326     return d->mColumnCount;
327 }
328 
329 
330 /************************************************
331 
332  ************************************************/
setColumnCount(int value)333 void GridLayout::setColumnCount(int value)
334 {
335     Q_D(GridLayout);
336     if (d->mColumnCount != value)
337     {
338         d->mColumnCount = value;
339         invalidate();
340     }
341 }
342 
343 
344 /************************************************
345 
346  ************************************************/
direction() const347 GridLayout::Direction GridLayout::direction() const
348 {
349     Q_D(const GridLayout);
350     return d->mDirection;
351 }
352 
353 
354 /************************************************
355 
356  ************************************************/
setDirection(GridLayout::Direction value)357 void GridLayout::setDirection(GridLayout::Direction value)
358 {
359     Q_D(GridLayout);
360     if (d->mDirection != value)
361     {
362         d->mDirection = value;
363         invalidate();
364     }
365 }
366 
367 /************************************************
368 
369  ************************************************/
stretch() const370 GridLayout::Stretch GridLayout::stretch() const
371 {
372     Q_D(const GridLayout);
373     return d->mStretch;
374 }
375 
376 /************************************************
377 
378  ************************************************/
setStretch(Stretch value)379 void GridLayout::setStretch(Stretch value)
380 {
381     Q_D(GridLayout);
382     if (d->mStretch != value)
383     {
384         d->mStretch = value;
385         invalidate();
386     }
387 }
388 
389 
390 /************************************************
391 
392  ************************************************/
moveItem(int from,int to,bool withAnimation)393 void GridLayout::moveItem(int from, int to, bool withAnimation /*= false*/)
394 {
395     Q_D(GridLayout);
396     d->mAnimate = withAnimation;
397     d->mItems.move(from, to);
398     invalidate();
399 }
400 
401 
402 /************************************************
403 
404  ************************************************/
animatedMoveInProgress() const405 bool GridLayout::animatedMoveInProgress() const
406 {
407     Q_D(const GridLayout);
408     return 0 < d->mAnimatedItems;
409 }
410 
411 
412 /************************************************
413 
414  ************************************************/
cellMinimumSize() const415 QSize GridLayout::cellMinimumSize() const
416 {
417     Q_D(const GridLayout);
418     return d->mPrefCellMinSize;
419 }
420 
421 
422 /************************************************
423 
424  ************************************************/
setCellMinimumSize(QSize minSize)425 void GridLayout::setCellMinimumSize(QSize minSize)
426 {
427     Q_D(GridLayout);
428     if (d->mPrefCellMinSize != minSize)
429     {
430         d->mPrefCellMinSize = minSize;
431         invalidate();
432     }
433 }
434 
435 
436 /************************************************
437 
438  ************************************************/
setCellMinimumHeight(int value)439 void GridLayout::setCellMinimumHeight(int value)
440 {
441     Q_D(GridLayout);
442     if (d->mPrefCellMinSize.height() != value)
443     {
444         d->mPrefCellMinSize.setHeight(value);
445         invalidate();
446     }
447 }
448 
449 
450 /************************************************
451 
452  ************************************************/
setCellMinimumWidth(int value)453 void GridLayout::setCellMinimumWidth(int value)
454 {
455     Q_D(GridLayout);
456     if (d->mPrefCellMinSize.width() != value)
457     {
458         d->mPrefCellMinSize.setWidth(value);
459         invalidate();
460     }
461 }
462 
463 
464 /************************************************
465 
466  ************************************************/
cellMaximumSize() const467 QSize GridLayout::cellMaximumSize() const
468 {
469     Q_D(const GridLayout);
470     return d->mPrefCellMaxSize;
471 }
472 
473 
474 /************************************************
475 
476  ************************************************/
setCellMaximumSize(QSize maxSize)477 void GridLayout::setCellMaximumSize(QSize maxSize)
478 {
479     Q_D(GridLayout);
480     if (d->mPrefCellMaxSize != maxSize)
481     {
482         d->mPrefCellMaxSize = maxSize;
483         invalidate();
484     }
485 }
486 
487 
488 /************************************************
489 
490  ************************************************/
setCellMaximumHeight(int value)491 void GridLayout::setCellMaximumHeight(int value)
492 {
493     Q_D(GridLayout);
494     if (d->mPrefCellMaxSize.height() != value)
495     {
496         d->mPrefCellMaxSize.setHeight(value);
497         invalidate();
498     }
499 }
500 
501 
502 /************************************************
503 
504  ************************************************/
setCellMaximumWidth(int value)505 void GridLayout::setCellMaximumWidth(int value)
506 {
507     Q_D(GridLayout);
508     if (d->mPrefCellMaxSize.width() != value)
509     {
510         d->mPrefCellMaxSize.setWidth(value);
511         invalidate();
512     }
513 }
514 
515 
516 /************************************************
517 
518  ************************************************/
setCellFixedSize(QSize size)519 void GridLayout::setCellFixedSize(QSize size)
520 {
521     Q_D(GridLayout);
522     if (d->mPrefCellMinSize != size ||
523         d->mPrefCellMaxSize != size)
524     {
525         d->mPrefCellMinSize = size;
526         d->mPrefCellMaxSize = size;
527         invalidate();
528     }
529 }
530 
531 
532 /************************************************
533 
534  ************************************************/
setCellFixedHeight(int value)535 void GridLayout::setCellFixedHeight(int value)
536 {
537     Q_D(GridLayout);
538     if (d->mPrefCellMinSize.height() != value ||
539         d->mPrefCellMaxSize.height() != value)
540     {
541         d->mPrefCellMinSize.setHeight(value);
542         d->mPrefCellMaxSize.setHeight(value);
543         invalidate();
544     }
545 }
546 
547 
548 /************************************************
549 
550  ************************************************/
setCellFixedWidth(int value)551 void GridLayout::setCellFixedWidth(int value)
552 {
553     Q_D(GridLayout);
554     if (d->mPrefCellMinSize.width() != value ||
555         d->mPrefCellMaxSize.width() != value)
556     {
557         d->mPrefCellMinSize.setWidth(value);
558         d->mPrefCellMaxSize.setWidth(value);
559         invalidate();
560     }
561 }
562 
563 
564 /************************************************
565 
566  ************************************************/
sizeHint() const567 QSize GridLayout::sizeHint() const
568 {
569     Q_D(const GridLayout);
570 
571     if (!d->mIsValid)
572         const_cast<GridLayoutPrivate*>(d)->updateCache();
573 
574     if (d->mVisibleCount == 0)
575         return {0, 0};
576 
577     const int sp = spacing();
578     return {d->cols() * (d->mCellSizeHint.width() + sp) - sp,
579             d->rows() * (d->mCellSizeHint.height() + sp) - sp};
580 }
581 
582 
583 /************************************************
584 
585  ************************************************/
setGeometry(const QRect & geometry)586 void GridLayout::setGeometry(const QRect &geometry)
587 {
588     Q_D(GridLayout);
589 
590     const bool visual_h_reversed = parentWidget() && parentWidget()->isRightToLeft();
591 
592     QLayout::setGeometry(geometry);
593     const QPoint occupied_start = visual_h_reversed ? geometry.topRight() : geometry.topLeft();
594     d->mOccupiedGeometry.setTopLeft(occupied_start);
595     d->mOccupiedGeometry.setBottomRight(occupied_start);
596 
597     if (!d->mIsValid)
598         d->updateCache();
599 
600     int y = geometry.top();
601     int x = geometry.left();
602 
603     // For historical reasons QRect::right returns left() + width() - 1
604     // and QRect::bottom() returns top() + height() - 1;
605     // So we use left() + height() and top() + height()
606     //
607     // http://qt-project.org/doc/qt-4.8/qrect.html
608 
609     const int maxX = geometry.left() + geometry.width();
610     const int maxY = geometry.top() + geometry.height();
611 
612     const bool stretch_h = d->mStretch.testFlag(StretchHorizontal);
613     const bool stretch_v = d->mStretch.testFlag(StretchVertical);
614     const int sp = spacing();
615 
616     const int cols = d->cols();
617     int itemWidth = 0;
618     int widthRemain = 0;
619     if (stretch_h && 0 < cols)
620     {
621         itemWidth = qMin((geometry.width() + sp) / cols - sp, d->mCellMaxSize.width());
622         widthRemain = (geometry.width() + sp) % cols;
623     }
624     else
625     {
626         itemWidth = d->mCellSizeHint.width();
627     }
628     itemWidth = qBound(qMin(d->mPrefCellMinSize.width(), maxX), itemWidth, d->mPrefCellMaxSize.width());
629 
630     const int rows = d->rows();
631     int itemHeight = 0;
632     int heightRemain = 0;
633     if (stretch_v && 0 < rows)
634     {
635         itemHeight = qMin((geometry.height() + sp) / rows - sp, d->mCellMaxSize.height());
636         heightRemain = (geometry.height() + sp) % rows;
637     }
638     else
639     {
640         itemHeight = d->mCellSizeHint.height();
641     }
642     itemHeight = qBound(qMin(d->mPrefCellMinSize.height(), maxY), itemHeight, d->mPrefCellMaxSize.height());
643 
644 #if 0
645     qDebug() << "** GridLayout::setGeometry *******************************";
646     qDebug() << "Geometry:" << geometry;
647     qDebug() << "CellSize:" << d->mCellSizeHint;
648     qDebug() << "Constraints:" << "min" << d->mPrefCellMinSize << "max" << d->mPrefCellMaxSize;
649     qDebug() << "Count" << count();
650     qDebug() << "Cols:" << d->cols() << "(" << d->mColumnCount << ")";
651     qDebug() << "Rows:" << d->rows() << "(" << d->mRowCount << ")";
652     qDebug() << "Stretch:" << "h:" << (d->mStretch.testFlag(StretchHorizontal)) << " v:" << (d->mStretch.testFlag(StretchVertical));
653     qDebug() << "Item:" << "h:" << itemHeight << " w:" << itemWidth;
654 #endif
655 
656     int remain_height = heightRemain;
657     int remain_width = widthRemain;
658     if (d->mDirection == LeftToRight)
659     {
660         int height = itemHeight + (0 < remain_height-- ? 1 : 0);
661         for (QLayoutItem *item : qAsConst(d->mItems))
662         {
663             if (!item->widget() || item->widget()->isHidden())
664                 continue;
665             int width = itemWidth + (0 < remain_width-- ? 1 : 0);
666 
667             if (x + width > maxX)
668             {
669                 x = geometry.left();
670                 y += height + sp;
671 
672                 height = itemHeight + (0 < remain_height-- ? 1 : 0);
673                 remain_width = widthRemain;
674             }
675 
676             const int left = visual_h_reversed ? geometry.left() + geometry.right() - x - width + 1 : x;
677             d->setItemGeometry(item, QRect(left, y, width, height));
678             x += width + sp;
679         }
680     }
681     else
682     {
683         int width = itemWidth + (0 < remain_width-- ? 1 : 0);
684         for (QLayoutItem *item : qAsConst(d->mItems))
685         {
686             if (!item->widget() || item->widget()->isHidden())
687                 continue;
688             int height = itemHeight + (0 < remain_height-- ? 1 : 0);
689 
690             if (y + height > maxY)
691             {
692                 y = geometry.top();
693                 x += width + sp;
694 
695                 width = itemWidth + (0 < remain_width-- ? 1 : 0);
696                 remain_height = heightRemain;
697             }
698             const int left = visual_h_reversed ? geometry.left() + geometry.right() - x - width + 1 : x;
699             d->setItemGeometry(item, QRect(left, y, width, height));
700             y += height + sp;
701         }
702     }
703     d->mAnimate = false;
704 }
705 
706 /************************************************
707 
708  ************************************************/
occupiedGeometry() const709 QRect GridLayout::occupiedGeometry() const
710 {
711     return d_func()->mOccupiedGeometry;
712 }
713