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 ¤t) 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