1 // SPDX-License-Identifier: GPL-3.0-or-later
2 // SPDX-FileCopyrightText: 2017-2019 Alejandro Sirgo Rica & Contributors
3
4 #include "buttonhandler.h"
5 #include "src/utils/globalvalues.h"
6 #include <QPoint>
7 #include <QScreen>
8
9 // ButtonHandler is a habdler for every active button. It makes easier to
10 // manipulate the buttons as a unit.
11
ButtonHandler(const QVector<CaptureToolButton * > & v,QObject * parent)12 ButtonHandler::ButtonHandler(const QVector<CaptureToolButton*>& v,
13 QObject* parent)
14 : QObject(parent)
15 {
16 setButtons(v);
17 init();
18 }
19
ButtonHandler(QObject * parent)20 ButtonHandler::ButtonHandler(QObject* parent)
21 : QObject(parent)
22 {
23 init();
24 }
25
hide()26 void ButtonHandler::hide()
27 {
28 for (CaptureToolButton* b : m_vectorButtons)
29 b->hide();
30 }
31
show()32 void ButtonHandler::show()
33 {
34 if (m_vectorButtons.isEmpty() || m_vectorButtons.first()->isVisible()) {
35 return;
36 }
37 for (CaptureToolButton* b : m_vectorButtons)
38 b->animatedShow();
39 }
40
isVisible() const41 bool ButtonHandler::isVisible() const
42 {
43 bool ret = true;
44 for (const CaptureToolButton* b : m_vectorButtons) {
45 if (!b->isVisible()) {
46 ret = false;
47 break;
48 }
49 }
50 return ret;
51 }
52
buttonsAreInside() const53 bool ButtonHandler::buttonsAreInside() const
54 {
55 return m_buttonsAreInside;
56 }
57
size() const58 size_t ButtonHandler::size() const
59 {
60 return m_vectorButtons.size();
61 }
62
63 // updatePosition updates the position of the buttons around the
64 // selection area. Ignores the sides blocked by the end of the screen.
65 // When the selection is too small it works on a virtual selection with
66 // the original in the center.
updatePosition(const QRect & selection)67 void ButtonHandler::updatePosition(const QRect& selection)
68 {
69 resetRegionTrack();
70 const int vecLength = m_vectorButtons.size();
71 if (vecLength == 0) {
72 return;
73 }
74 // Copy of the selection area for internal modifications
75 m_selection = intersectWithAreas(selection);
76 updateBlockedSides();
77 ensureSelectionMinimunSize();
78 // Indicates the actual button to be moved
79 int elemIndicator = 0;
80
81 while (elemIndicator < vecLength) {
82
83 // Add them inside the area when there is no more space
84 if (m_allSidesBlocked) {
85 m_selection = selection;
86 positionButtonsInside(elemIndicator);
87 break; // the while
88 }
89 // Number of buttons per row column
90 int buttonsPerRow =
91 (m_selection.width() + m_separator) / (m_buttonExtendedSize);
92 int buttonsPerCol =
93 (m_selection.height() + m_separator) / (m_buttonExtendedSize);
94 // Buttons to be placed in the corners
95 int extraButtons =
96 (vecLength - elemIndicator) - (buttonsPerRow + buttonsPerCol) * 2;
97 int elemsAtCorners = extraButtons > 4 ? 4 : extraButtons;
98 int maxExtra = 2;
99 if (m_oneHorizontalBlocked) {
100 maxExtra = 1;
101 } else if (m_horizontalyBlocked) {
102 maxExtra = 0;
103 }
104 int elemCornersTop = qBound(0, elemsAtCorners, maxExtra);
105 elemsAtCorners -= elemCornersTop;
106 int elemCornersBotton = qBound(0, elemsAtCorners, maxExtra);
107
108 // Add buttons at the button of the selection
109 if (!m_blockedBotton) {
110 int addCounter = buttonsPerRow + elemCornersBotton;
111 // Don't add more than we have
112 addCounter = qBound(0, addCounter, vecLength - elemIndicator);
113 QPoint center = QPoint(m_selection.center().x(),
114 m_selection.bottom() + m_separator);
115 if (addCounter > buttonsPerRow) {
116 adjustHorizontalCenter(center);
117 }
118 // ElemIndicator, elemsAtCorners
119 QVector<QPoint> positions =
120 horizontalPoints(center, addCounter, true);
121 moveButtonsToPoints(positions, elemIndicator);
122 }
123 // Add buttons at the right side of the selection
124 if (!m_blockedRight && elemIndicator < vecLength) {
125 int addCounter = buttonsPerCol;
126 addCounter = qBound(0, addCounter, vecLength - elemIndicator);
127
128 QPoint center = QPoint(m_selection.right() + m_separator,
129 m_selection.center().y());
130 QVector<QPoint> positions =
131 verticalPoints(center, addCounter, false);
132 moveButtonsToPoints(positions, elemIndicator);
133 }
134 // Add buttons at the top of the selection
135 if (!m_blockedTop && elemIndicator < vecLength) {
136 int addCounter = buttonsPerRow + elemCornersTop;
137 addCounter = qBound(0, addCounter, vecLength - elemIndicator);
138 QPoint center = QPoint(m_selection.center().x(),
139 m_selection.top() - m_buttonExtendedSize);
140 if (addCounter == 1 + buttonsPerRow) {
141 adjustHorizontalCenter(center);
142 }
143 QVector<QPoint> positions =
144 horizontalPoints(center, addCounter, false);
145 moveButtonsToPoints(positions, elemIndicator);
146 }
147 // Add buttons at the left side of the selection
148 if (!m_blockedLeft && elemIndicator < vecLength) {
149 int addCounter = buttonsPerCol;
150 addCounter = qBound(0, addCounter, vecLength - elemIndicator);
151
152 QPoint center = QPoint(m_selection.left() - m_buttonExtendedSize,
153 m_selection.center().y());
154 QVector<QPoint> positions =
155 verticalPoints(center, addCounter, true);
156 moveButtonsToPoints(positions, elemIndicator);
157 }
158 // If there are elements for the next cycle, increase the size of the
159 // base area
160 if (elemIndicator < vecLength && !(m_allSidesBlocked)) {
161 expandSelection();
162 }
163 updateBlockedSides();
164 }
165 }
166
167 // horizontalPoints is an auxiliary method for the button position computation.
168 // starts from a known center and keeps adding elements horizontally
169 // and returns the computed positions.
horizontalPoints(const QPoint & center,const int elements,const bool leftToRight) const170 QVector<QPoint> ButtonHandler::horizontalPoints(const QPoint& center,
171 const int elements,
172 const bool leftToRight) const
173 {
174 QVector<QPoint> res;
175 // Distance from the center to start adding buttons
176 int shift = 0;
177 if (elements % 2 == 0) {
178 shift = m_buttonExtendedSize * (elements / 2) - (m_separator / 2);
179 } else {
180 shift =
181 m_buttonExtendedSize * ((elements - 1) / 2) + m_buttonBaseSize / 2;
182 }
183 if (!leftToRight) {
184 shift -= m_buttonBaseSize;
185 }
186 int x = leftToRight ? center.x() - shift : center.x() + shift;
187 QPoint i(x, center.y());
188 while (elements > res.length()) {
189 res.append(i);
190 leftToRight ? i.setX(i.x() + m_buttonExtendedSize)
191 : i.setX(i.x() - m_buttonExtendedSize);
192 }
193 return res;
194 }
195
196 // verticalPoints is an auxiliary method for the button position computation.
197 // starts from a known center and keeps adding elements vertically
198 // and returns the computed positions.
verticalPoints(const QPoint & center,const int elements,const bool upToDown) const199 QVector<QPoint> ButtonHandler::verticalPoints(const QPoint& center,
200 const int elements,
201 const bool upToDown) const
202 {
203 QVector<QPoint> res;
204 // Distance from the center to start adding buttons
205 int shift = 0;
206 if (elements % 2 == 0) {
207 shift = m_buttonExtendedSize * (elements / 2) - (m_separator / 2);
208 } else {
209 shift =
210 m_buttonExtendedSize * ((elements - 1) / 2) + m_buttonBaseSize / 2;
211 }
212 if (!upToDown) {
213 shift -= m_buttonBaseSize;
214 }
215 int y = upToDown ? center.y() - shift : center.y() + shift;
216 QPoint i(center.x(), y);
217 while (elements > res.length()) {
218 res.append(i);
219 upToDown ? i.setY(i.y() + m_buttonExtendedSize)
220 : i.setY(i.y() - m_buttonExtendedSize);
221 }
222 return res;
223 }
224
intersectWithAreas(const QRect & rect)225 QRect ButtonHandler::intersectWithAreas(const QRect& rect)
226 {
227 QRect res;
228 for (const QRect& r : m_screenRegions) {
229 QRect temp = rect.intersected(r);
230 if (temp.height() * temp.width() > res.height() * res.width()) {
231 res = temp;
232 }
233 }
234 return res;
235 }
236
init()237 void ButtonHandler::init()
238 {
239 m_separator = GlobalValues::buttonBaseSize() / 4;
240 }
241
resetRegionTrack()242 void ButtonHandler::resetRegionTrack()
243 {
244 m_buttonsAreInside = false;
245 }
246
updateBlockedSides()247 void ButtonHandler::updateBlockedSides()
248 {
249 const int EXTENSION = m_separator * 2 + m_buttonBaseSize;
250 // Right
251 QPoint pointA(m_selection.right() + EXTENSION, m_selection.bottom());
252 QPoint pointB(pointA.x(), m_selection.top());
253 m_blockedRight =
254 !(m_screenRegions.contains(pointA) && m_screenRegions.contains(pointB));
255 // Left
256 pointA.setX(m_selection.left() - EXTENSION);
257 pointB.setX(pointA.x());
258 m_blockedLeft =
259 !(m_screenRegions.contains(pointA) && m_screenRegions.contains(pointB));
260 // Bottom
261 pointA = QPoint(m_selection.left(), m_selection.bottom() + EXTENSION);
262 pointB = QPoint(m_selection.right(), pointA.y());
263 m_blockedBotton =
264 !(m_screenRegions.contains(pointA) && m_screenRegions.contains(pointB));
265 // Top
266 pointA.setY(m_selection.top() - EXTENSION);
267 pointB.setY(pointA.y());
268 m_blockedTop =
269 !(m_screenRegions.contains(pointA) && m_screenRegions.contains(pointB));
270 // Auxiliary
271 m_oneHorizontalBlocked =
272 (!m_blockedRight && m_blockedLeft) || (m_blockedRight && !m_blockedLeft);
273 m_horizontalyBlocked = (m_blockedRight && m_blockedLeft);
274 m_allSidesBlocked =
275 (m_blockedBotton && m_horizontalyBlocked && m_blockedTop);
276 }
277
expandSelection()278 void ButtonHandler::expandSelection()
279 {
280 int& s = m_buttonExtendedSize;
281 m_selection = m_selection + QMargins(s, s, s, s);
282 m_selection = intersectWithAreas(m_selection);
283 }
284
positionButtonsInside(int index)285 void ButtonHandler::positionButtonsInside(int index)
286 {
287 // Position the buttons in the botton-center of the main but inside of the
288 // selection.
289 QRect mainArea = m_selection;
290 mainArea = intersectWithAreas(mainArea);
291 const int buttonsPerRow = (mainArea.width()) / (m_buttonExtendedSize);
292 if (buttonsPerRow == 0) {
293 return;
294 }
295 QPoint center =
296 QPoint(mainArea.center().x(), mainArea.bottom() - m_buttonExtendedSize);
297
298 while (m_vectorButtons.size() > index) {
299 int addCounter = buttonsPerRow;
300 addCounter = qBound(0, addCounter, m_vectorButtons.size() - index);
301 QVector<QPoint> positions = horizontalPoints(center, addCounter, true);
302 moveButtonsToPoints(positions, index);
303 center.setY(center.y() - m_buttonExtendedSize);
304 }
305
306 m_buttonsAreInside = true;
307 }
308
ensureSelectionMinimunSize()309 void ButtonHandler::ensureSelectionMinimunSize()
310 {
311 // Detect if a side is smaller than a button in order to prevent collision
312 // and redimension the base area the the base size of a single button per
313 // side
314 if (m_selection.width() < m_buttonBaseSize) {
315 if (!m_blockedLeft) {
316 m_selection.setX(m_selection.x() -
317 (m_buttonBaseSize - m_selection.width()) / 2);
318 }
319 m_selection.setWidth(m_buttonBaseSize);
320 }
321 if (m_selection.height() < m_buttonBaseSize) {
322 if (!m_blockedTop) {
323 m_selection.setY(m_selection.y() -
324 (m_buttonBaseSize - m_selection.height()) / 2);
325 }
326 m_selection.setHeight(m_buttonBaseSize);
327 }
328 }
329
moveButtonsToPoints(const QVector<QPoint> & points,int & index)330 void ButtonHandler::moveButtonsToPoints(const QVector<QPoint>& points,
331 int& index)
332 {
333 for (const QPoint& p : points) {
334 auto button = m_vectorButtons[index];
335 button->move(p);
336 ++index;
337 }
338 }
339
adjustHorizontalCenter(QPoint & center)340 void ButtonHandler::adjustHorizontalCenter(QPoint& center)
341 {
342 if (m_blockedLeft) {
343 center.setX(center.x() + m_buttonExtendedSize / 2);
344 } else if (m_blockedRight) {
345 center.setX(center.x() - m_buttonExtendedSize / 2);
346 }
347 }
348
349 // setButtons redefines the buttons of the button handler
setButtons(const QVector<CaptureToolButton * > v)350 void ButtonHandler::setButtons(const QVector<CaptureToolButton*> v)
351 {
352 if (v.isEmpty())
353 return;
354
355 for (CaptureToolButton* b : m_vectorButtons)
356 delete (b);
357 m_vectorButtons = v;
358 m_buttonBaseSize = GlobalValues::buttonBaseSize();
359 m_buttonExtendedSize = m_buttonBaseSize + m_separator;
360 }
361
contains(const QPoint & p) const362 bool ButtonHandler::contains(const QPoint& p) const
363 {
364 QPoint first(m_vectorButtons.first()->pos());
365 QPoint last(m_vectorButtons.last()->pos());
366 bool firstIsTopLeft = (first.x() <= last.x() && first.y() <= last.y());
367 QPoint topLeft = firstIsTopLeft ? first : last;
368 QPoint bottonRight = firstIsTopLeft ? last : first;
369 topLeft += QPoint(-m_separator, -m_separator);
370 bottonRight += QPoint(m_buttonExtendedSize, m_buttonExtendedSize);
371 QRegion r(QRect(topLeft, bottonRight).normalized());
372 return r.contains(p);
373 }
374
updateScreenRegions(const QVector<QRect> & rects)375 void ButtonHandler::updateScreenRegions(const QVector<QRect>& rects)
376 {
377 m_screenRegions = QRegion();
378 for (const QRect& rect : rects) {
379 m_screenRegions += rect;
380 }
381 }
382
updateScreenRegions(const QRect & rect)383 void ButtonHandler::updateScreenRegions(const QRect& rect)
384 {
385 m_screenRegions = QRegion(rect);
386 }
387