2 Copyright (c) 2008 Ambroz Bizjak <ambro@b4ever.net>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
14 #include "itemspace.h"
16 ItemSpace::ItemSpace()
17 : spaceAlignment(Qt::AlignTop
|Qt::AlignLeft
),
18 workingGeom(QSizeF()),
25 void ItemSpace::setWorkingArea(QSizeF area
)
27 if (workingGeom
.isValid()) {
28 // if the working area size changed and alignment includes right or bottom,
29 // the difference is added to all positions to keep items in the same place
30 // relative to the borders of alignment
31 if (((spaceAlignment
& Qt::AlignRight
) || (spaceAlignment
& Qt::AlignBottom
)) &&
32 (area
.width() != workingGeom
.width() || area
.height() != workingGeom
.height())) {
33 offsetPositions(QPointF(area
.width()-workingGeom
.width(), area
.height()-workingGeom
.height()));
39 void ItemSpace::activate()
41 for (int groupId
= 0; groupId
< m_groups
.size(); groupId
++) {
42 ItemGroup
&group
= m_groups
[groupId
];
44 for (int itemId
= 0; itemId
< group
.m_groupItems
.size(); itemId
++) {
45 ItemSpaceItem
&item
= group
.m_groupItems
[itemId
];
51 Push items intersecting working area borders inside.
52 For borders adjunct to the corner of alignment, allow pushing
53 over the opposite border (and items there may be temporarily placed).
57 push
= screenSpacing
- item
.lastGeometry
.left();
59 item
.animateMovement
= true;
60 power
= PushAwayFromPreferred
;
61 if ((spaceAlignment
& Qt::AlignLeft
)) {
62 power
|= PushOverBorder
;
64 performPush(groupId
, DirRight
, push
, power
);
68 push
= item
.lastGeometry
.right()+screenSpacing
- workingGeom
.width();
70 item
.animateMovement
= true;
71 power
= PushAwayFromPreferred
;
72 if ((spaceAlignment
& Qt::AlignRight
)) {
73 power
|= PushOverBorder
;
75 performPush(groupId
, DirLeft
, push
, power
);
79 push
= screenSpacing
- item
.lastGeometry
.top();
81 item
.animateMovement
= true;
82 power
= PushAwayFromPreferred
;
83 if ((spaceAlignment
& Qt::AlignTop
)) {
84 power
|= PushOverBorder
;
86 performPush(groupId
, DirDown
, push
, power
);
90 push
= item
.lastGeometry
.bottom()+screenSpacing
- workingGeom
.height();
92 item
.animateMovement
= true;
93 power
= PushAwayFromPreferred
;
94 if ((spaceAlignment
& Qt::AlignBottom
)) {
95 power
|= PushOverBorder
;
97 performPush(groupId
, DirUp
, push
, power
);
101 Push items back towards their perferred positions.
102 Cannot push items out of the working area,
103 cannot push items away from their preferred positions.
107 push
= item
.preferredGeometry
.left() - item
.lastGeometry
.left();
109 performPush(groupId
, DirRight
, push
, NoPower
);
110 } else if (push
< 0) {
111 performPush(groupId
, DirLeft
, -push
, NoPower
);
114 push
= item
.preferredGeometry
.top() - item
.lastGeometry
.top();
116 performPush(groupId
, DirDown
, push
, NoPower
);
117 } else if (push
< 0) {
118 performPush(groupId
, DirUp
, -push
, NoPower
);
125 qreal
ItemSpace::positionVisibility (QRectF geom
)
127 QRectF visibleArea
= QRectF(QPointF(), workingGeom
);
128 QRectF visibleItemPart
= visibleArea
.intersected(geom
);
129 qreal itemSurface
= geom
.width() * geom
.height();
130 qreal itemVisibleSurface
= visibleItemPart
.width() * visibleItemPart
.height();
131 return (itemVisibleSurface
/ itemSurface
);
134 void ItemSpace::offsetPositions(const QPointF
&offset
)
136 for (int groupId
= 0; groupId
< m_groups
.size(); groupId
++) {
137 ItemGroup
&group
= m_groups
[groupId
];
139 for (int itemId
= 0; itemId
< group
.m_groupItems
.size(); itemId
++) {
140 ItemSpaceItem
&item
= group
.m_groupItems
[itemId
];
142 item
.preferredGeometry
.adjust(offset
.x(), offset
.y(), offset
.x(), offset
.y());
143 item
.lastGeometry
.adjust(offset
.x(), offset
.y(), offset
.x(), offset
.y());
148 qreal
ItemSpace::performPush(int groupId
, Direction direction
, qreal amount
, PushPower power
)
150 ItemGroup
&group
= m_groups
[groupId
];
152 preparePush(direction
, power
);
153 group
.addRequest(this, ItemGroup::Request(-1, 0, amount
));
154 group
.applyResults(this, -1);
155 return group
.m_pushAvailable
;
158 bool ItemSpace::positionedProperly(QRectF itemGeom
)
160 QRectF fullGeom
= itemGeom
.adjusted(-placementSpacing
, -placementSpacing
, placementSpacing
, placementSpacing
);
161 return (QRectF(QPointF(), workingGeom
).contains(fullGeom
));
164 QList
<QPointF
> ItemSpace::positionVertically(
165 const QSizeF
&itemSize
,
171 qreal spL
= placementSpacing
;
172 qreal spR
= placementSpacing
;
173 qreal spT
= placementSpacing
;
174 qreal spB
= placementSpacing
;
175 QList
<QPointF
> possiblePositions
;
177 // basically, position searching is done by repetedly looking for obstacles at
178 // one position and looking for a next position to try
180 // add spacing to the size
181 QSizeF size
= QSizeF(itemSize
.width()+spL
+spR
, itemSize
.height()+spT
+spB
);
183 // the initial x coordinate to start testing
184 // start either on the left or the right, and advance inside
185 qreal x
= ((align
& Qt::AlignLeft
) ? 0 : workingGeom
.width()-size
.width());
186 // try different x coordinates
187 for (int i
= 0; i
< 100; ++i
) {
188 // stop testing if we're limited by the working area and positions at the next x would reach outside
189 bool outOfX
= ((align
& Qt::AlignLeft
) ? (x
+ size
.width() > workingGeom
.width()) : (x
< 0));
190 if (outOfX
&& limitedSpace
) {
194 // the initial y coordinate to start testing heights at the current x
195 // start either on the top or the bottom, and advance inside
196 qreal y
= ((align
& Qt::AlignTop
) ? 0 : workingGeom
.height()-size
.height());
197 // try different y coordinates
199 // stop testing at this x if we're limited by the working area and positions at the next y would reach outside
200 bool outOfY
= ((align
& Qt::AlignTop
) ? (y
+ size
.height() > workingGeom
.height()) : (y
< 0));
201 if (outOfY
&& limitedSpace
) {
205 // Z would come here :)
207 // Check for intersecting items, or a new y coordinate to try.
208 // Suppose we're aligning to top:
209 // Find all items that intersect the region we're testing.
210 // If no items were found, we have space.
211 // Oterwise pick the one with the lowest bottom border
212 // and use that border as the new y coordinate to try.
213 // The logic is inverted when aligning to bottom.
216 if ((align
& Qt::AlignTop
)) {
217 a
= itemInRegionEndingLastVert(QRectF(x
, y
, size
.width(), size
.height()));
219 a
= itemInRegionStartingFirstVert(QRectF(x
, y
, size
.width(), size
.height()));
223 // found a valid position
224 possiblePositions
.append(QPointF(x
+spL
, y
+spT
));
226 return possiblePositions
;
228 // don't look at this X anymore, one position is enough
232 Q_ASSERT( ((align
& Qt::AlignTop
) && a
.bottom() > y
) || ((align
& Qt::AlignBottom
) && a
.y() - size
.height() < y
) );
234 y
= ((align
& Qt::AlignTop
) ? a
.bottom() : a
.y() - size
.height());
237 // Find next possible x coordinate
238 // Suppose we're aligning to left:
239 // Take a vertical strap of the area we have been testing previously,
240 // extending over the height of the working area.
241 // Find all items that intersect the region we're testing.
242 // If no items were found, stop all testing.
243 // Otherwise, pick the one with the most-left right border
244 // and use that border as the new x coordinate to try.
245 // The logic is inverted when aligning to right.
248 if ((align
& Qt::AlignLeft
)) {
249 a
= itemInRegionEndingFirstHoriz(QRectF(x
, 0, size
.width(), workingGeom
.height()));
251 a
= itemInRegionStartingLastHoriz(QRectF(x
, 0, size
.width(), workingGeom
.height()));
258 Q_ASSERT( ((align
& Qt::AlignLeft
) && a
.right() > x
) || ((align
& Qt::AlignRight
) && a
.x() - size
.width() < x
) );
260 x
= ((align
& Qt::AlignLeft
) ? a
.right() : a
.x() - size
.width());
263 return possiblePositions
;
266 QRectF
ItemSpace::itemInRegionStartingFirstVert(const QRectF
®ion
) const
268 QRectF ret
= QRectF(0,0,-1,-1);
269 qreal l
= std::numeric_limits
<qreal
>::max();
271 for (int groupId
= 0; groupId
< m_groups
.size(); groupId
++) {
272 const ItemGroup
&group
= m_groups
[groupId
];
274 for (int itemId
= 0; itemId
< group
.m_groupItems
.size(); itemId
++) {
275 const ItemSpaceItem
&item
= group
.m_groupItems
[itemId
];
277 if (!item
.lastGeometry
.isValid()) {
280 qreal cl
= item
.lastGeometry
.y();
281 if (item
.lastGeometry
.intersects(region
) && cl
< l
) {
282 ret
= item
.lastGeometry
;
290 QRectF
ItemSpace::itemInRegionEndingLastVert(const QRectF
®ion
) const
292 QRectF ret
= QRectF(0,0,-1,-1);
295 for (int groupId
= 0; groupId
< m_groups
.size(); groupId
++) {
296 const ItemGroup
&group
= m_groups
[groupId
];
298 for (int itemId
= 0; itemId
< group
.m_groupItems
.size(); itemId
++) {
299 const ItemSpaceItem
&item
= group
.m_groupItems
[itemId
];
301 if (!item
.lastGeometry
.isValid()) {
304 qreal cl
= item
.lastGeometry
.y() + item
.lastGeometry
.height();
305 if (item
.lastGeometry
.intersects(region
) && cl
> l
) {
306 ret
= item
.lastGeometry
;
314 QRectF
ItemSpace::itemInRegionEndingFirstHoriz(const QRectF
®ion
) const
316 QRectF ret
= QRectF(0,0,-1,-1);
317 qreal l
= std::numeric_limits
<qreal
>::max();
319 for (int groupId
= 0; groupId
< m_groups
.size(); groupId
++) {
320 const ItemGroup
&group
= m_groups
[groupId
];
322 for (int itemId
= 0; itemId
< group
.m_groupItems
.size(); itemId
++) {
323 const ItemSpaceItem
&item
= group
.m_groupItems
[itemId
];
325 if (!item
.lastGeometry
.isValid()) {
328 qreal cl
= item
.lastGeometry
.x() + item
.lastGeometry
.width();
329 if (item
.lastGeometry
.intersects(region
) && cl
< l
) {
330 ret
= item
.lastGeometry
;
338 QRectF
ItemSpace::itemInRegionStartingLastHoriz(const QRectF
®ion
) const
340 QRectF ret
= QRectF(0,0,-1,-1);
343 for (int groupId
= 0; groupId
< m_groups
.size(); groupId
++) {
344 const ItemGroup
&group
= m_groups
[groupId
];
346 for (int itemId
= 0; itemId
< group
.m_groupItems
.size(); itemId
++) {
347 const ItemSpaceItem
&item
= group
.m_groupItems
[itemId
];
349 if (!item
.lastGeometry
.isValid()) {
352 qreal cl
= item
.lastGeometry
.x();
353 if (item
.lastGeometry
.intersects(region
) && cl
> l
) {
354 ret
= item
.lastGeometry
;
362 ItemSpace::ItemGroup::Request::Request(
364 qreal sourceGroupPushRequested
,
367 : m_sourceGroup(sourceGroup
),
368 m_sourceGroupPushRequested(sourceGroupPushRequested
),
369 m_pushRequested(pushRequested
),
374 void ItemSpace::ItemGroup::Request::activate (ItemSpace
*itemSpace
, ItemGroup
*group
)
376 // don't do anything if the group was already asked to move at
377 // least as much as we ask
378 if (group
->m_largestPushRequested
>= m_pushRequested
) {
382 qreal largest
= group
->m_largestPushRequested
;
383 // record our request as the largest
384 group
->m_largestPushRequested
= m_pushRequested
;
385 // don't do anything if the group already hit an unmovable obstacle
386 if (group
->m_pushAvailable
< largest
) {
390 // set the available push to our requested value
391 // and limit it as obstacles are found
392 group
->m_pushAvailable
= m_pushRequested
;
394 // look for obstacles for every item in the group
395 for (int itemId
= 0; itemId
< group
->m_groupItems
.size(); itemId
++) {
396 ItemSpaceItem
&item
= group
->m_groupItems
[itemId
];
397 QRectF origGeom
= item
.lastGeometry
;
398 QRectF fullGeom
= origGeom
.adjusted(-itemSpace
->shiftingSpacing
, -itemSpace
->shiftingSpacing
,
399 itemSpace
->shiftingSpacing
, itemSpace
->shiftingSpacing
);
401 // limit push by screen boundaries
402 if (!(itemSpace
->m_power
& PushOverBorder
)) {
404 switch (itemSpace
->m_direction
) {
406 limit
= origGeom
.left() - itemSpace
->screenSpacing
;
409 limit
= itemSpace
->workingGeom
.width() - itemSpace
->screenSpacing
- origGeom
.right();
412 limit
= origGeom
.top() - itemSpace
->screenSpacing
;
415 limit
= itemSpace
->workingGeom
.height() - itemSpace
->screenSpacing
- origGeom
.bottom();
418 group
->m_pushAvailable
= qMax(qreal(0.0), qMin(group
->m_pushAvailable
, limit
));
419 if (group
->m_pushAvailable
== 0) {
424 // limit push to not push the item away from its preferred position
425 if (!(itemSpace
->m_power
& PushAwayFromPreferred
) && item
.pushBack
) {
427 switch (itemSpace
->m_direction
) {
429 limit
= origGeom
.left() - item
.preferredGeometry
.left();
432 limit
= -(origGeom
.left() - item
.preferredGeometry
.left());
435 limit
= origGeom
.top() - item
.preferredGeometry
.top();
438 limit
= -(origGeom
.top() - item
.preferredGeometry
.top());
441 limit
= qMax(qreal(0.0), limit
);
442 group
->m_pushAvailable
= qMin(group
->m_pushAvailable
, limit
);
443 if (group
->m_pushAvailable
== 0) {
448 // look for items in the way
449 for (int testGroupId
= 0; testGroupId
< itemSpace
->m_groups
.size(); testGroupId
++) {
451 if (testGroupId
== group
->m_id
|| group
->groupIsAbove(itemSpace
, asa
, testGroupId
)) {
454 ItemGroup
&testGroup
= itemSpace
->m_groups
[testGroupId
];
456 // calculate how much the offending group needs to be pushed
458 for (int testItemId
= 0; testItemId
< testGroup
.m_groupItems
.size(); testItemId
++) {
459 ItemSpaceItem
&testItem
= testGroup
.m_groupItems
[testItemId
];
461 QRectF newlyTakenSpace
;
463 switch (itemSpace
->m_direction
) {
465 newlyTakenSpace
= QRectF(fullGeom
.left() - group
->m_pushAvailable
, fullGeom
.top(), group
->m_pushAvailable
, fullGeom
.height());
466 push
= testItem
.lastGeometry
.right() - newlyTakenSpace
.left();
469 newlyTakenSpace
= QRectF(fullGeom
.right(), fullGeom
.top(), group
->m_pushAvailable
, fullGeom
.height());
470 push
= newlyTakenSpace
.right() - testItem
.lastGeometry
.left();
473 newlyTakenSpace
= QRectF(fullGeom
.left(), fullGeom
.top() - group
->m_pushAvailable
, fullGeom
.width(), group
->m_pushAvailable
);
474 push
= testItem
.lastGeometry
.bottom() - newlyTakenSpace
.top();
477 newlyTakenSpace
= QRectF(fullGeom
.left(), fullGeom
.bottom(), fullGeom
.width(), group
->m_pushAvailable
);
478 push
= newlyTakenSpace
.bottom() - testItem
.lastGeometry
.top();
482 // check if it is an obstacle
483 if (testItem
.lastGeometry
.intersects(newlyTakenSpace
)) {
484 groupPush
= qMax(groupPush
, push
);
488 if (groupPush
== 0) {
492 // post a move request to the obstacle
493 if (!group
->m_obstacles
.contains(testGroupId
)) {
494 group
->m_obstacles
.append(testGroupId
);
496 testGroup
.addRequest(itemSpace
, Request(group
->m_id
, group
->m_pushAvailable
, groupPush
));
498 // limit our push by how much the obstacle can actually move
499 if (testGroup
.m_pushAvailable
< groupPush
) {
500 group
->m_pushAvailable
= qMax(qreal(0.0), group
->m_pushAvailable
- (groupPush
- testGroup
.m_pushAvailable
));
501 if (group
->m_pushAvailable
== 0) {
509 void ItemSpace::ItemGroup::resetPush(int id
)
512 m_largestPushRequested
= 0,
513 m_pushAvailable
= std::numeric_limits
<qreal
>::max();
514 m_requests
= QList
<Request
>();
515 m_obstacles
= QList
<int>();
518 void ItemSpace::ItemGroup::addRequest (ItemSpace
*itemSpace
, const class Request
&request
)
520 m_requests
.append(request
);
521 m_requests
.last().activate(itemSpace
, this);
524 void ItemSpace::ItemGroup::applyResults(ItemSpace
*itemSpace
, int cameFrom
)
526 bool notComplete
= false;
527 for (int i
= 0; i
< m_requests
.size(); i
++) {
528 Request
&request
= m_requests
[i
];
529 if (request
.m_sourceGroup
== -1) {
533 if (request
.m_sourceGroup
== cameFrom
) {
534 qreal pushLost
= request
.m_sourceGroupPushRequested
- itemSpace
->m_groups
[cameFrom
].m_pushAvailable
;
535 request
.m_pushRequested
-= pushLost
;
536 request
.m_compensated
= true;
537 } else if (!request
.m_compensated
) {
546 qreal totalPushRequired
= 0;
547 for (int i
= 0; i
< m_requests
.size(); i
++) {
548 Request
&request
= m_requests
[i
];
549 totalPushRequired
= qMax(totalPushRequired
, request
.m_pushRequested
);
551 m_pushAvailable
= qMin(m_pushAvailable
, totalPushRequired
);
553 for (int groupId
= 0; groupId
< m_groupItems
.size(); groupId
++) {
554 ItemSpaceItem
&groupItem
= m_groupItems
[groupId
];
556 switch (itemSpace
->m_direction
) {
558 groupItem
.lastGeometry
= groupItem
.lastGeometry
.adjusted(-m_pushAvailable
, 0, -m_pushAvailable
, 0);
561 groupItem
.lastGeometry
= groupItem
.lastGeometry
.adjusted(m_pushAvailable
, 0, m_pushAvailable
, 0);
564 groupItem
.lastGeometry
= groupItem
.lastGeometry
.adjusted(0, -m_pushAvailable
, 0, -m_pushAvailable
);
567 groupItem
.lastGeometry
= groupItem
.lastGeometry
.adjusted(0, m_pushAvailable
, 0, m_pushAvailable
);
572 foreach (int obstacleId
, m_obstacles
) {
573 itemSpace
->m_groups
[obstacleId
].applyResults(itemSpace
, m_id
);
577 bool ItemSpace::ItemGroup::groupIsAbove(ItemSpace
*itemSpace
, QList
<int> &visited
, int groupId
)
579 foreach (const Request
&request
, m_requests
) {
580 if (request
.m_sourceGroup
== -1 || visited
.contains(request
.m_sourceGroup
)) {
583 if (request
.m_sourceGroup
== groupId
) {
586 visited
.append(request
.m_sourceGroup
);
587 if (itemSpace
->m_groups
[request
.m_sourceGroup
].groupIsAbove(itemSpace
, visited
, groupId
)) {
595 void ItemSpace::addItem(ItemSpaceItem newItem
)
597 QList
<ItemSpaceItem
> newGroupItems
;
598 QRectF newItemGeom
= newItem
.lastGeometry
.adjusted(-shiftingSpacing
, -shiftingSpacing
,
599 shiftingSpacing
, shiftingSpacing
);
601 // look for items overlapping with the new item
602 for (int groupId
= 0; groupId
< m_groups
.size();) {
603 ItemGroup
&group
= m_groups
[groupId
];
605 // if any item in the group overlaps it, save its items and remove the group
606 bool removeGroup
= false;
607 for (int itemId
= 0; itemId
< group
.m_groupItems
.size(); itemId
++) {
608 ItemSpaceItem
&item
= group
.m_groupItems
[itemId
];
609 if (newItemGeom
.intersects(item
.lastGeometry
)) {
616 newGroupItems
<< group
.m_groupItems
;
617 m_groups
.removeAt(groupId
);
623 // create the new group
624 m_groups
.append(ItemGroup());
625 ItemGroup
&newGroup
= m_groups
.last();
626 newGroup
.m_groupItems
.append(newItem
);
627 newGroup
.m_groupItems
<< newGroupItems
;
631 void ItemSpace::removeItem(int removeGroup
, int removeItemInGroup
)
633 // remove items from group
634 m_groups
[removeGroup
].m_groupItems
.removeAt(removeItemInGroup
);
635 // save other group items
636 QList
<ItemSpaceItem
> otherGroupItems
= m_groups
[removeGroup
].m_groupItems
;
638 m_groups
.removeAt(removeGroup
);
639 // re-add other group items
640 foreach (const ItemSpaceItem
&item
, otherGroupItems
) {
646 void ItemSpace::updateItem(int group
, int itemInGroup
)
648 ItemSpaceItem copy
= m_groups
[group
].m_groupItems
[itemInGroup
];
649 removeItem(group
, itemInGroup
);
653 bool ItemSpace::locateItemByPosition(int pos
, int *groupIndex
, int *itemInGroup
) const
656 for (int groupId
= 0; groupId
< m_groups
.size(); groupId
++) {
657 ItemGroup group
= m_groups
[groupId
];
658 if (current
+ group
.m_groupItems
.size() > pos
) {
659 *groupIndex
= groupId
;
660 *itemInGroup
= pos
- current
;
663 current
+= group
.m_groupItems
.size();
668 bool ItemSpace::locateItemByUser(QVariant user
, int *groupIndex
, int *itemInGroup
) const
670 for (int groupId
= 0; groupId
< m_groups
.size(); groupId
++) {
671 ItemGroup group
= m_groups
[groupId
];
672 for (int itemId
= 0; itemId
< group
.m_groupItems
.size(); itemId
++) {
673 ItemSpaceItem
&item
= group
.m_groupItems
[itemId
];
674 if (item
.user
== user
) {
675 *groupIndex
= groupId
;
676 *itemInGroup
= itemId
;
684 void ItemSpace::preparePush(Direction direction
, PushPower power
)
686 m_direction
= direction
;
689 for (int i
=0; i
<m_groups
.size(); i
++) {
690 m_groups
[i
].resetPush(i
);