Settings: unify duplicated code
[lxde/liblxqt.git] / lxqtgridlayout.cpp
1 /* BEGIN_COMMON_COPYRIGHT_HEADER
2 * (c)LGPL2+
3 *
4 * LXQt - a lightweight, Qt based, desktop toolset
5 * http://razor-qt.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 <math.h>
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:
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
81 ItemMoveAnimation(QLayoutItem *item)
82 : mItem(item)
83 {
84 setDuration(150);
85 }
86
87 void updateCurrentValue(const QVariant &current)
88 {
89 mItem->setGeometry(current.toRect());
90 }
91
92 private:
93 QLayoutItem* mItem;
94
95 };
96 }
97
98
99 /************************************************
100
101 ************************************************/
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 ************************************************/
119 GridLayoutPrivate::~GridLayoutPrivate()
120 {
121 qDeleteAll(mItems);
122 }
123
124
125 /************************************************
126
127 ************************************************/
128 void GridLayoutPrivate::updateCache()
129 {
130 mCellSizeHint = QSize(0, 0);
131 mCellMaxSize = QSize(0, 0);
132 mVisibleCount = 0;
133
134 for (int i=0; i<mItems.count(); ++i)
135 {
136 QLayoutItem *item = mItems.at(i);
137 if (!item->widget() || item->widget()->isHidden())
138 continue;
139
140 int h = qBound(item->minimumSize().height(),
141 item->sizeHint().height(),
142 item->maximumSize().height());
143
144 int w = qBound(item->minimumSize().width(),
145 item->sizeHint().width(),
146 item->maximumSize().width());
147
148 mCellSizeHint.rheight() = qMax(mCellSizeHint.height(), h);
149 mCellSizeHint.rwidth() = qMax(mCellSizeHint.width(), w);
150
151 mCellMaxSize.rheight() = qMax(mCellMaxSize.height(), item->maximumSize().height());
152 mCellMaxSize.rwidth() = qMax(mCellMaxSize.width(), item->maximumSize().width());
153 mVisibleCount++;
154
155 #if 0
156 qDebug() << "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-";
157 qDebug() << "item.min" << item->minimumSize().width();
158 qDebug() << "item.sz " << item->sizeHint().width();
159 qDebug() << "item.max" << item->maximumSize().width();
160 qDebug() << "w h" << w << h;
161 qDebug() << "wid.sizeHint" << item->widget()->sizeHint();
162 qDebug() << "mCellSizeHint:" << mCellSizeHint;
163 qDebug() << "mCellMaxSize: " << mCellMaxSize;
164 qDebug() << "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-";
165 #endif
166
167 }
168 mCellSizeHint.rwidth() = qBound(mPrefCellMinSize.width(), mCellSizeHint.width(), mPrefCellMaxSize.width());
169 mCellSizeHint.rheight()= qBound(mPrefCellMinSize.height(), mCellSizeHint.height(), mPrefCellMaxSize.height());
170 mIsValid = !mCellSizeHint.isEmpty();
171 }
172
173
174 /************************************************
175
176 ************************************************/
177 int GridLayoutPrivate::rows() const
178 {
179 if (mRowCount)
180 return mRowCount;
181
182 if (!mColumnCount)
183 return 1;
184
185 return ceil(mVisibleCount * 1.0 / mColumnCount);
186 }
187
188
189 /************************************************
190
191 ************************************************/
192 int GridLayoutPrivate::cols() const
193 {
194 if (mColumnCount)
195 return mColumnCount;
196
197 int rows = mRowCount;
198 if (!rows)
199 rows = 1;
200
201 return ceil(mVisibleCount * 1.0 / rows);
202 }
203
204 void GridLayoutPrivate::setItemGeometry(QLayoutItem * item, QRect const & geometry)
205 {
206 mOccupiedGeometry |= geometry;
207 if (mAnimate)
208 {
209 ItemMoveAnimation::animate(item, geometry, this);
210 } else
211 {
212 item->setGeometry(geometry);
213 }
214 }
215
216
217 /************************************************
218
219 ************************************************/
220 GridLayout::GridLayout(QWidget *parent):
221 QLayout(parent),
222 d_ptr(new GridLayoutPrivate())
223 {
224 }
225
226
227 /************************************************
228
229 ************************************************/
230 GridLayout::~GridLayout()
231 {
232 delete d_ptr;
233 }
234
235
236 /************************************************
237
238 ************************************************/
239 void GridLayout::addItem(QLayoutItem *item)
240 {
241 d_ptr->mItems.append(item);
242 }
243
244
245 /************************************************
246
247 ************************************************/
248 QLayoutItem *GridLayout::itemAt(int index) const
249 {
250 Q_D(const GridLayout);
251 if (index < 0 || index >= d->mItems.count())
252 return 0;
253
254 return d->mItems.at(index);
255 }
256
257
258 /************************************************
259
260 ************************************************/
261 QLayoutItem *GridLayout::takeAt(int index)
262 {
263 Q_D(GridLayout);
264 if (index < 0 || index >= d->mItems.count())
265 return 0;
266
267 QLayoutItem *item = d->mItems.takeAt(index);
268 return item;
269 }
270
271
272 /************************************************
273
274 ************************************************/
275 int GridLayout::count() const
276 {
277 Q_D(const GridLayout);
278 return d->mItems.count();
279 }
280
281
282 /************************************************
283
284 ************************************************/
285 void GridLayout::invalidate()
286 {
287 Q_D(GridLayout);
288 d->mIsValid = false;
289 QLayout::invalidate();
290 }
291
292
293 /************************************************
294
295 ************************************************/
296 int GridLayout::rowCount() const
297 {
298 Q_D(const GridLayout);
299 return d->mRowCount;
300 }
301
302
303 /************************************************
304
305 ************************************************/
306 void GridLayout::setRowCount(int value)
307 {
308 Q_D(GridLayout);
309 if (d->mRowCount != value)
310 {
311 d->mRowCount = value;
312 invalidate();
313 }
314 }
315
316
317 /************************************************
318
319 ************************************************/
320 int GridLayout::columnCount() const
321 {
322 Q_D(const GridLayout);
323 return d->mColumnCount;
324 }
325
326
327 /************************************************
328
329 ************************************************/
330 void GridLayout::setColumnCount(int value)
331 {
332 Q_D(GridLayout);
333 if (d->mColumnCount != value)
334 {
335 d->mColumnCount = value;
336 invalidate();
337 }
338 }
339
340
341 /************************************************
342
343 ************************************************/
344 GridLayout::Direction GridLayout::direction() const
345 {
346 Q_D(const GridLayout);
347 return d->mDirection;
348 }
349
350
351 /************************************************
352
353 ************************************************/
354 void GridLayout::setDirection(GridLayout::Direction value)
355 {
356 Q_D(GridLayout);
357 if (d->mDirection != value)
358 {
359 d->mDirection = value;
360 invalidate();
361 }
362 }
363
364 /************************************************
365
366 ************************************************/
367 GridLayout::Stretch GridLayout::stretch() const
368 {
369 Q_D(const GridLayout);
370 return d->mStretch;
371 }
372
373 /************************************************
374
375 ************************************************/
376 void GridLayout::setStretch(Stretch value)
377 {
378 Q_D(GridLayout);
379 if (d->mStretch != value)
380 {
381 d->mStretch = value;
382 invalidate();
383 }
384 }
385
386
387 /************************************************
388
389 ************************************************/
390 void GridLayout::moveItem(int from, int to, bool withAnimation /*= false*/)
391 {
392 Q_D(GridLayout);
393 d->mAnimate = withAnimation;
394 d->mItems.move(from, to);
395 invalidate();
396 }
397
398
399 /************************************************
400
401 ************************************************/
402 bool GridLayout::animatedMoveInProgress() const
403 {
404 Q_D(const GridLayout);
405 return 0 < d->mAnimatedItems;
406 }
407
408
409 /************************************************
410
411 ************************************************/
412 QSize GridLayout::cellMinimumSize() const
413 {
414 Q_D(const GridLayout);
415 return d->mPrefCellMinSize;
416 }
417
418
419 /************************************************
420
421 ************************************************/
422 void GridLayout::setCellMinimumSize(QSize minSize)
423 {
424 Q_D(GridLayout);
425 if (d->mPrefCellMinSize != minSize)
426 {
427 d->mPrefCellMinSize = minSize;
428 invalidate();
429 }
430 }
431
432
433 /************************************************
434
435 ************************************************/
436 void GridLayout::setCellMinimumHeight(int value)
437 {
438 Q_D(GridLayout);
439 if (d->mPrefCellMinSize.height() != value)
440 {
441 d->mPrefCellMinSize.setHeight(value);
442 invalidate();
443 }
444 }
445
446
447 /************************************************
448
449 ************************************************/
450 void GridLayout::setCellMinimumWidth(int value)
451 {
452 Q_D(GridLayout);
453 if (d->mPrefCellMinSize.width() != value)
454 {
455 d->mPrefCellMinSize.setWidth(value);
456 invalidate();
457 }
458 }
459
460
461 /************************************************
462
463 ************************************************/
464 QSize GridLayout::cellMaximumSize() const
465 {
466 Q_D(const GridLayout);
467 return d->mPrefCellMaxSize;
468 }
469
470
471 /************************************************
472
473 ************************************************/
474 void GridLayout::setCellMaximumSize(QSize maxSize)
475 {
476 Q_D(GridLayout);
477 if (d->mPrefCellMaxSize != maxSize)
478 {
479 d->mPrefCellMaxSize = maxSize;
480 invalidate();
481 }
482 }
483
484
485 /************************************************
486
487 ************************************************/
488 void GridLayout::setCellMaximumHeight(int value)
489 {
490 Q_D(GridLayout);
491 if (d->mPrefCellMaxSize.height() != value)
492 {
493 d->mPrefCellMaxSize.setHeight(value);
494 invalidate();
495 }
496 }
497
498
499 /************************************************
500
501 ************************************************/
502 void GridLayout::setCellMaximumWidth(int value)
503 {
504 Q_D(GridLayout);
505 if (d->mPrefCellMaxSize.width() != value)
506 {
507 d->mPrefCellMaxSize.setWidth(value);
508 invalidate();
509 }
510 }
511
512
513 /************************************************
514
515 ************************************************/
516 void GridLayout::setCellFixedSize(QSize size)
517 {
518 Q_D(GridLayout);
519 if (d->mPrefCellMinSize != size ||
520 d->mPrefCellMaxSize != size)
521 {
522 d->mPrefCellMinSize = size;
523 d->mPrefCellMaxSize = size;
524 invalidate();
525 }
526 }
527
528
529 /************************************************
530
531 ************************************************/
532 void GridLayout::setCellFixedHeight(int value)
533 {
534 Q_D(GridLayout);
535 if (d->mPrefCellMinSize.height() != value ||
536 d->mPrefCellMaxSize.height() != value)
537 {
538 d->mPrefCellMinSize.setHeight(value);
539 d->mPrefCellMaxSize.setHeight(value);
540 invalidate();
541 }
542 }
543
544
545 /************************************************
546
547 ************************************************/
548 void GridLayout::setCellFixedWidth(int value)
549 {
550 Q_D(GridLayout);
551 if (d->mPrefCellMinSize.width() != value ||
552 d->mPrefCellMaxSize.width() != value)
553 {
554 d->mPrefCellMinSize.setWidth(value);
555 d->mPrefCellMaxSize.setWidth(value);
556 invalidate();
557 }
558 }
559
560
561 /************************************************
562
563 ************************************************/
564 QSize GridLayout::sizeHint() const
565 {
566 Q_D(const GridLayout);
567
568 if (!d->mIsValid)
569 const_cast<GridLayoutPrivate*>(d)->updateCache();
570
571 return QSize(d->cols() * d->mCellSizeHint.width(),
572 d->rows() * d->mCellSizeHint.height());
573 }
574
575
576 /************************************************
577
578 ************************************************/
579 void GridLayout::setGeometry(const QRect &geometry)
580 {
581 Q_D(GridLayout);
582
583 const bool visual_h_reversed = parentWidget() && parentWidget()->isRightToLeft();
584
585 QLayout::setGeometry(geometry);
586 const QPoint occupied_start = visual_h_reversed ? geometry.topRight() : geometry.topLeft();
587 d->mOccupiedGeometry.setTopLeft(occupied_start);
588 d->mOccupiedGeometry.setBottomRight(occupied_start);
589
590 if (!d->mIsValid)
591 d->updateCache();
592
593 int y = geometry.top();
594 int x = geometry.left();
595
596 // For historical reasons QRect::right returns left() + width() - 1
597 // and QRect::bottom() returns top() + height() - 1;
598 // So we use left() + height() and top() + height()
599 //
600 // http://qt-project.org/doc/qt-4.8/qrect.html
601
602 const int maxX = geometry.left() + geometry.width();
603 const int maxY = geometry.top() + geometry.height();
604
605 const bool stretch_h = d->mStretch.testFlag(StretchHorizontal);
606 const bool stretch_v = d->mStretch.testFlag(StretchVertical);
607
608 const int cols = d->cols();
609 int itemWidth = 0;
610 if (stretch_h && 0 < cols)
611 itemWidth = qMin(geometry.width() / cols, d->mCellMaxSize.width());
612 else
613 itemWidth = d->mCellSizeHint.width();
614 itemWidth = qBound(qMin(d->mPrefCellMinSize.width(), maxX), itemWidth, d->mPrefCellMaxSize.width());
615 const int widthRemain = stretch_h && 0 < itemWidth ? geometry.width() % itemWidth : 0;
616
617 const int rows = d->rows();
618 int itemHeight = 0;
619 if (stretch_v && 0 < rows)
620 itemHeight = qMin(geometry.height() / rows, d->mCellMaxSize.height());
621 else
622 itemHeight = d->mCellSizeHint.height();
623 itemHeight = qBound(qMin(d->mPrefCellMinSize.height(), maxY), itemHeight, d->mPrefCellMaxSize.height());
624 const int heightRemain = stretch_v && 0 < itemHeight ? geometry.height() % itemHeight : 0;
625
626 #if 0
627 qDebug() << "** GridLayout::setGeometry *******************************";
628 qDebug() << "Geometry:" << geometry;
629 qDebug() << "CellSize:" << d->mCellSizeHint;
630 qDebug() << "Constraints:" << "min" << d->mPrefCellMinSize << "max" << d->mPrefCellMaxSize;
631 qDebug() << "Count" << count();
632 qDebug() << "Cols:" << d->cols() << "(" << d->mColumnCount << ")";
633 qDebug() << "Rows:" << d->rows() << "(" << d->mRowCount << ")";
634 qDebug() << "Stretch:" << "h:" << (d->mStretch.testFlag(StretchHorizontal)) << " v:" << (d->mStretch.testFlag(StretchVertical));
635 qDebug() << "Item:" << "h:" << itemHeight << " w:" << itemWidth;
636 #endif
637
638 int remain_height = heightRemain;
639 int remain_width = widthRemain;
640 if (d->mDirection == LeftToRight)
641 {
642 int height = itemHeight + (0 < remain_height-- ? 1 : 0);
643 foreach(QLayoutItem *item, d->mItems)
644 {
645 if (!item->widget() || item->widget()->isHidden())
646 continue;
647 int width = itemWidth + (0 < remain_width-- ? 1 : 0);
648
649 if (x + width > maxX)
650 {
651 x = geometry.left();
652 y += height;
653
654 height = itemHeight + (0 < remain_height-- ? 1 : 0);
655 remain_width = widthRemain;
656 }
657
658 const int left = visual_h_reversed ? geometry.left() + geometry.right() - x - width + 1 : x;
659 d->setItemGeometry(item, QRect(left, y, width, height));
660 x += width;
661 }
662 }
663 else
664 {
665 int width = itemWidth + (0 < remain_width-- ? 1 : 0);
666 foreach(QLayoutItem *item, d->mItems)
667 {
668 if (!item->widget() || item->widget()->isHidden())
669 continue;
670 int height = itemHeight + (0 < remain_height-- ? 1 : 0);
671
672 if (y + height > maxY)
673 {
674 y = geometry.top();
675 x += width;
676
677 width = itemWidth + (0 < remain_width-- ? 1 : 0);
678 remain_height = heightRemain;
679 }
680 const int left = visual_h_reversed ? geometry.left() + geometry.right() - x - width + 1 : x;
681 d->setItemGeometry(item, QRect(left, y, width, height));
682 y += height;
683 }
684 }
685 d->mAnimate = false;
686 }
687
688 /************************************************
689
690 ************************************************/
691 QRect GridLayout::occupiedGeometry() const
692 {
693 return d_func()->mOccupiedGeometry;
694 }