// Copyright(c) 2017-2018 Alejandro Sirgo Rica & Contributors
//
// This file is part of Flameshot.
//
// Flameshot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Flameshot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Flameshot. If not, see .
#include "buttonhandler.h"
#include "src/utils/globalvalues.h"
#include
#include
#include
// ButtonHandler is a habdler for every active button. It makes easier to
// manipulate the buttons as a unit.
ButtonHandler::ButtonHandler(const QVector &v,
QObject *parent) :
QObject(parent)
{
setButtons(v);
init();
}
ButtonHandler::ButtonHandler(QObject *parent) :
QObject(parent)
{
init();
}
void ButtonHandler::hide() {
for (CaptureButton *b: m_vectorButtons)
b->hide();
}
void ButtonHandler::show() {
if (m_vectorButtons.isEmpty() || m_vectorButtons.first()->isVisible()) {
return;
}
for (CaptureButton *b: m_vectorButtons)
b->animatedShow();
}
bool ButtonHandler::isVisible() const {
bool ret = true;
for (const CaptureButton *b: m_vectorButtons) {
if (!b->isVisible()) {
ret = false;
break;
}
}
return ret;
}
bool ButtonHandler::buttonsAreInside() const {
return m_buttonsAreInside;
}
size_t ButtonHandler::size() const {
return m_vectorButtons.size();
}
// updatePosition updates the position of the buttons around the
// selection area. Ignores the sides blocked by the end of the screen.
// When the selection is too small it works on a virtual selection with
// the original in the center.
void ButtonHandler::updatePosition(const QRect &selection) {
resetRegionTrack();
const int vecLength = m_vectorButtons.size();
if (vecLength == 0) {
return;
}
// Copy of the selection area for internal modifications
m_selection = intersectWithAreas(selection);
updateBlockedSides();
ensureSelectionMinimunSize();
// Indicates the actual button to be moved
int elemIndicator = 0;
while (elemIndicator < vecLength) {
// Add them inside the area when there is no more space
if (m_allSidesBlocked) {
m_selection = selection;
positionButtonsInside(elemIndicator);
break; // the while
}
// Number of buttons per row column
int buttonsPerRow = (m_selection.width() + m_separator) / (m_buttonExtendedSize);
int buttonsPerCol = (m_selection.height() + m_separator) / (m_buttonExtendedSize);
// Buttons to be placed in the corners
int extraButtons = (vecLength - elemIndicator) -
(buttonsPerRow + buttonsPerCol) * 2;
int elemsAtCorners = extraButtons > 4 ? 4 : extraButtons;
int maxExtra = 2;
if (m_oneHorizontalBlocked) {
maxExtra = 1;
} else if (m_horizontalyBlocked) {
maxExtra = 0;
}
int elemCornersTop = qBound(0, elemsAtCorners, maxExtra);
elemsAtCorners -= elemCornersTop;
int elemCornersBotton = qBound(0, elemsAtCorners, maxExtra);
// Add buttons at the button of the seletion
if (!m_blockedBotton) {
int addCounter = buttonsPerRow + elemCornersBotton;
// Don't add more than we have
addCounter = qBound(0, addCounter, vecLength - elemIndicator);
QPoint center = QPoint(m_selection.center().x(),
m_selection.bottom() + m_separator);
if (addCounter > buttonsPerRow) {
adjustHorizontalCenter(center);
}
// ElemIndicator, elemsAtCorners
QVector positions = horizontalPoints(center, addCounter, true);
moveButtonsToPoints(positions, elemIndicator);
}
// Add buttons at the right side of the seletion
if (!m_blockedRight && elemIndicator < vecLength) {
int addCounter = buttonsPerCol;
addCounter = qBound(0, addCounter, vecLength - elemIndicator);
QPoint center = QPoint(m_selection.right() + m_separator,
m_selection.center().y());
QVector positions = verticalPoints(center, addCounter, false);
moveButtonsToPoints(positions, elemIndicator);
}
// Add buttons at the top of the seletion
if (!m_blockedTop && elemIndicator < vecLength) {
int addCounter = buttonsPerRow + elemCornersTop;
addCounter = qBound(0, addCounter, vecLength - elemIndicator);
QPoint center = QPoint(m_selection.center().x(),
m_selection.top() - m_buttonExtendedSize);
if (addCounter == 1 + buttonsPerRow) {
adjustHorizontalCenter(center);
}
QVector positions = horizontalPoints(center, addCounter, false);
moveButtonsToPoints(positions, elemIndicator);
}
// Add buttons at the left side of the seletion
if (!m_blockedLeft && elemIndicator < vecLength) {
int addCounter = buttonsPerCol;
addCounter = qBound(0, addCounter, vecLength - elemIndicator);
QPoint center = QPoint(m_selection.left() - m_buttonExtendedSize,
m_selection.center().y());
QVector positions = verticalPoints(center, addCounter, true);
moveButtonsToPoints(positions, elemIndicator);
}
// If there are elements for the next cycle, increase the size of the
// base area
if (elemIndicator < vecLength && !(m_allSidesBlocked)) {
expandSelection();
}
updateBlockedSides();
}
}
// horizontalPoints is an auxiliar method for the button position computation.
// starts from a known center and keeps adding elements horizontally
// and returns the computed positions.
QVector ButtonHandler::horizontalPoints(
const QPoint ¢er, const int elements, const bool leftToRight) const
{
QVector res;
// Distance from the center to start adding buttons
int shift = 0;
if (elements % 2 == 0) {
shift = m_buttonExtendedSize * (elements / 2) - (m_separator / 2);
} else {
shift = m_buttonExtendedSize * ((elements-1) / 2) + m_buttonBaseSize / 2;
}
if (!leftToRight) { shift -= m_buttonBaseSize; }
int x = leftToRight ? center.x() - shift :
center.x() + shift;
QPoint i(x, center.y());
while (elements > res.length()) {
res.append(i);
leftToRight ? i.setX(i.x() + m_buttonExtendedSize) :
i.setX(i.x() - m_buttonExtendedSize);
}
return res;
}
// verticalPoints is an auxiliar method for the button position computation.
// starts from a known center and keeps adding elements vertically
// and returns the computed positions.
QVector ButtonHandler::verticalPoints(
const QPoint ¢er, const int elements, const bool upToDown) const
{
QVector res;
// Distance from the center to start adding buttons
int shift = 0;
if (elements % 2 == 0) {
shift = m_buttonExtendedSize * (elements / 2) - (m_separator / 2);
} else {
shift = m_buttonExtendedSize * ((elements-1) / 2) + m_buttonBaseSize / 2;
}
if (!upToDown) { shift -= m_buttonBaseSize; }
int y = upToDown ? center.y() - shift :
center.y() + shift;
QPoint i(center.x(), y);
while (elements > res.length()) {
res.append(i);
upToDown ? i.setY(i.y() + m_buttonExtendedSize) :
i.setY(i.y() - m_buttonExtendedSize);
}
return res;
}
QRect ButtonHandler::intersectWithAreas(const QRect &rect) {
QRect res;
for(const QRect &r : m_screenRegions.rects()) {
QRect temp = rect.intersected(r);
if (temp.height() * temp.width() > res.height() * res.width()) {
res = temp;
}
}
return res;
}
void ButtonHandler::init() {
m_separator = GlobalValues::buttonBaseSize() / 4;
}
void ButtonHandler::resetRegionTrack() {
m_buttonsAreInside = false;
}
void ButtonHandler::updateBlockedSides() {
const int EXTENSION = m_separator * 2 + m_buttonBaseSize;
// Right
QPoint pointA(m_selection.right() + EXTENSION,
m_selection.bottom());
QPoint pointB(pointA.x(),
m_selection.top());
m_blockedRight = !(m_screenRegions.contains(pointA) &&
m_screenRegions.contains(pointB));
// Left
pointA.setX(m_selection.left() - EXTENSION);
pointB.setX(pointA.x());
m_blockedLeft = !(m_screenRegions.contains(pointA) &&
m_screenRegions.contains(pointB));
// Botton
pointA = QPoint(m_selection.left(),
m_selection.bottom() + EXTENSION);
pointB = QPoint(m_selection.right(),
pointA.y());
m_blockedBotton = !(m_screenRegions.contains(pointA) &&
m_screenRegions.contains(pointB));
// Top
pointA.setY(m_selection.top() - EXTENSION);
pointB.setY(pointA.y());
m_blockedTop = !(m_screenRegions.contains(pointA) &&
m_screenRegions.contains(pointB));
// Auxiliar
m_oneHorizontalBlocked = (!m_blockedRight && m_blockedLeft) ||
(m_blockedRight && !m_blockedLeft);
m_horizontalyBlocked = (m_blockedRight && m_blockedLeft);
m_allSidesBlocked = (m_blockedBotton && m_horizontalyBlocked && m_blockedTop);
}
void ButtonHandler::expandSelection() {
int &s = m_buttonExtendedSize;
m_selection = m_selection + QMargins(s, s, s, s);
m_selection = intersectWithAreas(m_selection);
}
void ButtonHandler::positionButtonsInside(int index) {
// Position the buttons in the botton-center of the main but inside of the
// selection.
QRect mainArea = m_selection;
mainArea = intersectWithAreas(mainArea);
const int buttonsPerRow = (mainArea.width()) / (m_buttonExtendedSize);
if (buttonsPerRow == 0) {
return;
}
QPoint center = QPoint(mainArea.center().x(),
mainArea.bottom() - m_buttonExtendedSize);
while (m_vectorButtons.size() > index) {
int addCounter = buttonsPerRow;
addCounter = qBound(0, addCounter, m_vectorButtons.size() - index);
QVector positions = horizontalPoints(center, addCounter, true);
moveButtonsToPoints(positions, index);
center.setY(center.y() - m_buttonExtendedSize);
}
m_buttonsAreInside = true;
}
void ButtonHandler::ensureSelectionMinimunSize() {
// Detect if a side is smaller than a button in order to prevent collision
// and redimension the base area the the base size of a single button per side
if (m_selection.width() < m_buttonBaseSize) {
if (!m_blockedLeft) {
m_selection.setX(m_selection.x() -
(m_buttonBaseSize-m_selection.width()) / 2);
}
m_selection.setWidth(m_buttonBaseSize);
}
if (m_selection.height() < m_buttonBaseSize) {
if (!m_blockedTop) {
m_selection.setY(m_selection.y() -
(m_buttonBaseSize-m_selection.height()) / 2);
}
m_selection.setHeight(m_buttonBaseSize);
}
}
void ButtonHandler::moveButtonsToPoints(
const QVector &points, int &index)
{
for (const QPoint &p: points) {
auto button = m_vectorButtons[index];
button->move(p);
++index;
}
}
void ButtonHandler::adjustHorizontalCenter(QPoint ¢er) {
if (m_blockedLeft) {
center.setX(center.x() + m_buttonExtendedSize/2);
} else if (m_blockedRight) {
center.setX(center.x() - m_buttonExtendedSize/2);
}
}
// setButtons redefines the buttons of the button handler
void ButtonHandler::setButtons(const QVector v) {
if (v.isEmpty())
return;
for (CaptureButton *b: m_vectorButtons)
delete(b);
m_vectorButtons = v;
m_buttonBaseSize = GlobalValues::buttonBaseSize();
m_buttonExtendedSize = m_buttonBaseSize + m_separator;
}
bool ButtonHandler::contains(const QPoint &p) const {
QPoint first(m_vectorButtons.first()->pos());
QPoint last(m_vectorButtons.last()->pos());
bool firstIsTopLeft = (first.x() <= last.x() && first.y() <= last.y());
QPoint topLeft = firstIsTopLeft ? first : last;
QPoint bottonRight = firstIsTopLeft ? last : first;
topLeft += QPoint(-m_separator, -m_separator);
bottonRight += QPoint(m_buttonExtendedSize, m_buttonExtendedSize);
QRegion r(QRect(topLeft, bottonRight).normalized());
return r.contains(p);
}
void ButtonHandler::updateScreenRegions(const QVector &rects) {
m_screenRegions = QRegion();
for (const QRect &rect: rects) {
m_screenRegions += rect;
}
}
void ButtonHandler::updateScreenRegions(const QRect &rect) {
m_screenRegions = QRegion(rect);
}