legendlayout.cpp
521 lines
| 18.8 KiB
| text/x-c
|
CppLexer
winter
|
r0 | /**************************************************************************** | ||
** | ||||
** Copyright (C) 2016 The Qt Company Ltd. | ||||
** Contact: https://www.qt.io/licensing/ | ||||
** | ||||
** This file is part of the Qt Charts module of the Qt Toolkit. | ||||
** | ||||
** $QT_BEGIN_LICENSE:GPL$ | ||||
** Commercial License Usage | ||||
** Licensees holding valid commercial Qt licenses may use this file in | ||||
** accordance with the commercial license agreement provided with the | ||||
** Software or, alternatively, in accordance with the terms contained in | ||||
** a written agreement between you and The Qt Company. For licensing terms | ||||
** and conditions see https://www.qt.io/terms-conditions. For further | ||||
** information use the contact form at https://www.qt.io/contact-us. | ||||
** | ||||
** GNU General Public License Usage | ||||
** Alternatively, this file may be used under the terms of the GNU | ||||
** General Public License version 3 or (at your option) any later version | ||||
** approved by the KDE Free Qt Foundation. The licenses are as published by | ||||
** the Free Software Foundation and appearing in the file LICENSE.GPL3 | ||||
** included in the packaging of this file. Please review the following | ||||
** information to ensure the GNU General Public License requirements will | ||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html. | ||||
** | ||||
** $QT_END_LICENSE$ | ||||
** | ||||
****************************************************************************/ | ||||
#include <private/legendlayout_p.h> | ||||
#include <private/chartpresenter_p.h> | ||||
#include <private/qlegend_p.h> | ||||
#include <private/abstractchartlayout_p.h> | ||||
#include <private/qlegendmarker_p.h> | ||||
#include <private/legendmarkeritem_p.h> | ||||
#include <QtCharts/QLegendMarker> | ||||
QT_CHARTS_BEGIN_NAMESPACE | ||||
LegendLayout::LegendLayout(QLegend *legend) | ||||
: m_legend(legend), | ||||
m_offsetX(0), | ||||
m_offsetY(0) | ||||
{ | ||||
} | ||||
LegendLayout::~LegendLayout() | ||||
{ | ||||
} | ||||
void LegendLayout::setOffset(qreal x, qreal y) | ||||
{ | ||||
bool scrollHorizontal = true; | ||||
switch (m_legend->alignment()) { | ||||
case Qt::AlignTop: | ||||
case Qt::AlignBottom: | ||||
scrollHorizontal = true; | ||||
break; | ||||
case Qt::AlignLeft: | ||||
case Qt::AlignRight: | ||||
scrollHorizontal = false; | ||||
break; | ||||
} | ||||
// If detached, the scrolling direction is vertical instead of horizontal and vice versa. | ||||
if (!m_legend->isAttachedToChart()) | ||||
scrollHorizontal = !scrollHorizontal; | ||||
QRectF boundingRect = geometry(); | ||||
qreal left, top, right, bottom; | ||||
getContentsMargins(&left, &top, &right, &bottom); | ||||
boundingRect.adjust(left, top, -right, -bottom); | ||||
// Limit offset between m_minOffset and m_maxOffset | ||||
if (scrollHorizontal) { | ||||
if (m_width <= boundingRect.width()) | ||||
return; | ||||
if (x != m_offsetX) { | ||||
m_offsetX = qBound(m_minOffsetX, x, m_maxOffsetX); | ||||
m_legend->d_ptr->items()->setPos(-m_offsetX, boundingRect.top()); | ||||
} | ||||
} else { | ||||
if (m_height <= boundingRect.height()) | ||||
return; | ||||
if (y != m_offsetY) { | ||||
m_offsetY = qBound(m_minOffsetY, y, m_maxOffsetY); | ||||
m_legend->d_ptr->items()->setPos(boundingRect.left(), -m_offsetY); | ||||
} | ||||
} | ||||
} | ||||
QPointF LegendLayout::offset() const | ||||
{ | ||||
return QPointF(m_offsetX, m_offsetY); | ||||
} | ||||
void LegendLayout::invalidate() | ||||
{ | ||||
QGraphicsLayout::invalidate(); | ||||
if (m_legend->isAttachedToChart()) | ||||
m_legend->d_ptr->m_presenter->layout()->invalidate(); | ||||
} | ||||
void LegendLayout::setGeometry(const QRectF &rect) | ||||
{ | ||||
m_legend->d_ptr->items()->setVisible(m_legend->isVisible()); | ||||
QGraphicsLayout::setGeometry(rect); | ||||
if (m_legend->isAttachedToChart()) | ||||
setAttachedGeometry(rect); | ||||
else | ||||
setDettachedGeometry(rect); | ||||
} | ||||
void LegendLayout::setAttachedGeometry(const QRectF &rect) | ||||
{ | ||||
if (!rect.isValid()) | ||||
return; | ||||
qreal oldOffsetX = m_offsetX; | ||||
qreal oldOffsetY = m_offsetY; | ||||
m_offsetX = 0; | ||||
m_offsetY = 0; | ||||
QSizeF size(0, 0); | ||||
if (m_legend->d_ptr->markers().isEmpty()) { | ||||
return; | ||||
} | ||||
m_width = 0; | ||||
m_height = 0; | ||||
qreal left, top, right, bottom; | ||||
getContentsMargins(&left, &top, &right, &bottom); | ||||
QRectF geometry = rect.adjusted(left, top, -right, -bottom); | ||||
switch(m_legend->alignment()) { | ||||
case Qt::AlignTop: | ||||
case Qt::AlignBottom: { | ||||
// Calculate the space required for items and add them to a sorted list. | ||||
qreal markerItemsWidth = 0; | ||||
qreal itemMargins = 0; | ||||
QList<LegendWidthStruct *> legendWidthList; | ||||
foreach (QLegendMarker *marker, m_legend->d_ptr->markers()) { | ||||
LegendMarkerItem *item = marker->d_ptr->item(); | ||||
if (item->isVisible()) { | ||||
QSizeF dummySize; | ||||
qreal itemWidth = item->sizeHint(Qt::PreferredSize, dummySize).width(); | ||||
LegendWidthStruct *structItem = new LegendWidthStruct; | ||||
structItem->item = item; | ||||
structItem->width = itemWidth; | ||||
legendWidthList.append(structItem); | ||||
markerItemsWidth += itemWidth; | ||||
itemMargins += marker->d_ptr->item()->m_margin; | ||||
} | ||||
} | ||||
std::sort(legendWidthList.begin(), legendWidthList.end(), widthLongerThan); | ||||
// If the items would occupy more space than is available, start truncating them | ||||
// from the longest one. | ||||
qreal availableGeometry = geometry.width() - right - left * 2 - itemMargins; | ||||
if (markerItemsWidth >= availableGeometry && legendWidthList.count() > 0) { | ||||
bool truncated(false); | ||||
int count = legendWidthList.count(); | ||||
for (int i = 1; i < count; i++) { | ||||
int truncateIndex = i - 1; | ||||
while (legendWidthList.at(truncateIndex)->width >= legendWidthList.at(i)->width | ||||
&& !truncated) { | ||||
legendWidthList.at(truncateIndex)->width--; | ||||
markerItemsWidth--; | ||||
if (i > 1) { | ||||
// Truncate the items that are before the truncated one in the list. | ||||
for (int j = truncateIndex - 1; j >= 0; j--) { | ||||
if (legendWidthList.at(truncateIndex)->width | ||||
< legendWidthList.at(j)->width) { | ||||
legendWidthList.at(j)->width--; | ||||
markerItemsWidth--; | ||||
} | ||||
} | ||||
} | ||||
if (markerItemsWidth < availableGeometry) | ||||
truncated = true; | ||||
} | ||||
// Truncate the last item if needed. | ||||
if (i == count - 1) { | ||||
if (legendWidthList.at(count - 1)->width | ||||
> legendWidthList.at(truncateIndex)->width) { | ||||
legendWidthList.at(count - 1)->width--; | ||||
markerItemsWidth--; | ||||
} | ||||
} | ||||
if (truncated) | ||||
break; | ||||
} | ||||
// Items are of same width and all of them need to be truncated | ||||
// or there is just one item that is truncated. | ||||
while (markerItemsWidth >= availableGeometry) { | ||||
for (int i = 0; i < count; i++) { | ||||
legendWidthList.at(i)->width--; | ||||
markerItemsWidth--; | ||||
} | ||||
} | ||||
} | ||||
QPointF point(0,0); | ||||
int markerCount = m_legend->d_ptr->markers().count(); | ||||
for (int i = 0; i < markerCount; i++) { | ||||
QLegendMarker *marker; | ||||
if (m_legend->d_ptr->m_reverseMarkers) | ||||
marker = m_legend->d_ptr->markers().at(markerCount - 1 - i); | ||||
else | ||||
marker = m_legend->d_ptr->markers().at(i); | ||||
LegendMarkerItem *item = marker->d_ptr->item(); | ||||
if (item->isVisible()) { | ||||
QRectF itemRect = geometry; | ||||
qreal availableWidth = 0; | ||||
for (int i = 0; i < legendWidthList.size(); ++i) { | ||||
if (legendWidthList.at(i)->item == item) { | ||||
availableWidth = legendWidthList.at(i)->width; | ||||
break; | ||||
} | ||||
} | ||||
itemRect.setWidth(availableWidth); | ||||
item->setGeometry(itemRect); | ||||
item->setPos(point.x(),geometry.height()/2 - item->boundingRect().height()/2); | ||||
const QRectF &rect = item->boundingRect(); | ||||
size = size.expandedTo(rect.size()); | ||||
qreal w = rect.width(); | ||||
m_width = m_width + w - item->m_margin; | ||||
point.setX(point.x() + w); | ||||
} | ||||
} | ||||
// Delete structs from the container | ||||
qDeleteAll(legendWidthList); | ||||
// Round to full pixel via QPoint to avoid one pixel clipping on the edge in some cases | ||||
if (m_width < geometry.width()) { | ||||
m_legend->d_ptr->items()->setPos(QPoint(geometry.width() / 2 - m_width / 2, | ||||
geometry.top())); | ||||
} else { | ||||
m_legend->d_ptr->items()->setPos(geometry.topLeft().toPoint()); | ||||
} | ||||
m_height = size.height(); | ||||
} | ||||
break; | ||||
case Qt::AlignLeft: | ||||
case Qt::AlignRight: { | ||||
QPointF point(0,0); | ||||
int markerCount = m_legend->d_ptr->markers().count(); | ||||
for (int i = 0; i < markerCount; i++) { | ||||
QLegendMarker *marker; | ||||
if (m_legend->d_ptr->m_reverseMarkers) | ||||
marker = m_legend->d_ptr->markers().at(markerCount - 1 - i); | ||||
else | ||||
marker = m_legend->d_ptr->markers().at(i); | ||||
LegendMarkerItem *item = marker->d_ptr->item(); | ||||
if (item->isVisible()) { | ||||
item->setGeometry(geometry); | ||||
item->setPos(point); | ||||
const QRectF &rect = item->boundingRect(); | ||||
qreal h = rect.height(); | ||||
size = size.expandedTo(rect.size()); | ||||
m_height+=h; | ||||
point.setY(point.y() + h); | ||||
} | ||||
} | ||||
// Round to full pixel via QPoint to avoid one pixel clipping on the edge in some cases | ||||
if (m_height < geometry.height()) { | ||||
m_legend->d_ptr->items()->setPos(QPoint(geometry.left(), | ||||
geometry.height() / 2 - m_height / 2)); | ||||
} else { | ||||
m_legend->d_ptr->items()->setPos(geometry.topLeft().toPoint()); | ||||
} | ||||
m_width = size.width(); | ||||
break; | ||||
} | ||||
} | ||||
m_minOffsetX = -left; | ||||
m_minOffsetY = - top; | ||||
m_maxOffsetX = m_width - geometry.width() - right; | ||||
m_maxOffsetY = m_height - geometry.height() - bottom; | ||||
setOffset(oldOffsetX, oldOffsetY); | ||||
} | ||||
void LegendLayout::setDettachedGeometry(const QRectF &rect) | ||||
{ | ||||
if (!rect.isValid()) | ||||
return; | ||||
// Detached layout is different. | ||||
// In detached mode legend may have multiple rows and columns, so layout calculations | ||||
// differ a log from attached mode. | ||||
// Also the scrolling logic is bit different. | ||||
qreal oldOffsetX = m_offsetX; | ||||
qreal oldOffsetY = m_offsetY; | ||||
m_offsetX = 0; | ||||
m_offsetY = 0; | ||||
qreal left, top, right, bottom; | ||||
getContentsMargins(&left, &top, &right, &bottom); | ||||
QRectF geometry = rect.adjusted(left, top, -right, -bottom); | ||||
QSizeF size(0, 0); | ||||
QList<QLegendMarker *> markers = m_legend->d_ptr->markers(); | ||||
if (markers.isEmpty()) | ||||
return; | ||||
switch (m_legend->alignment()) { | ||||
case Qt::AlignTop: { | ||||
QPointF point(0, 0); | ||||
m_width = 0; | ||||
m_height = 0; | ||||
for (int i = 0; i < markers.count(); i++) { | ||||
LegendMarkerItem *item = markers.at(i)->d_ptr->item(); | ||||
if (item->isVisible()) { | ||||
item->setGeometry(geometry); | ||||
item->setPos(point.x(),point.y()); | ||||
const QRectF &boundingRect = item->boundingRect(); | ||||
qreal w = boundingRect.width(); | ||||
qreal h = boundingRect.height(); | ||||
m_width = qMax(m_width,w); | ||||
m_height = qMax(m_height,h); | ||||
point.setX(point.x() + w); | ||||
if (point.x() + w > geometry.left() + geometry.width() - right) { | ||||
// Next item would go off rect. | ||||
point.setX(0); | ||||
point.setY(point.y() + h); | ||||
if (i+1 < markers.count()) { | ||||
m_height += h; | ||||
} | ||||
} | ||||
} | ||||
} | ||||
m_legend->d_ptr->items()->setPos(geometry.topLeft()); | ||||
m_minOffsetX = -left; | ||||
m_minOffsetY = -top; | ||||
m_maxOffsetX = m_width - geometry.width() - right; | ||||
m_maxOffsetY = m_height - geometry.height() - bottom; | ||||
} | ||||
break; | ||||
case Qt::AlignBottom: { | ||||
QPointF point(0, geometry.height()); | ||||
m_width = 0; | ||||
m_height = 0; | ||||
for (int i = 0; i < markers.count(); i++) { | ||||
LegendMarkerItem *item = markers.at(i)->d_ptr->item(); | ||||
if (item->isVisible()) { | ||||
item->setGeometry(geometry); | ||||
const QRectF &boundingRect = item->boundingRect(); | ||||
qreal w = boundingRect.width(); | ||||
qreal h = boundingRect.height(); | ||||
m_width = qMax(m_width,w); | ||||
m_height = qMax(m_height,h); | ||||
item->setPos(point.x(),point.y() - h); | ||||
point.setX(point.x() + w); | ||||
if (point.x() + w > geometry.left() + geometry.width() - right) { | ||||
// Next item would go off rect. | ||||
point.setX(0); | ||||
point.setY(point.y() - h); | ||||
if (i+1 < markers.count()) { | ||||
m_height += h; | ||||
} | ||||
} | ||||
} | ||||
} | ||||
m_legend->d_ptr->items()->setPos(geometry.topLeft()); | ||||
m_minOffsetX = -left; | ||||
m_minOffsetY = -m_height + geometry.height() - top; | ||||
m_maxOffsetX = m_width - geometry.width() - right; | ||||
m_maxOffsetY = -bottom; | ||||
} | ||||
break; | ||||
case Qt::AlignLeft: { | ||||
QPointF point(0, 0); | ||||
m_width = 0; | ||||
m_height = 0; | ||||
qreal maxWidth = 0; | ||||
for (int i = 0; i < markers.count(); i++) { | ||||
LegendMarkerItem *item = markers.at(i)->d_ptr->item(); | ||||
if (item->isVisible()) { | ||||
item->setGeometry(geometry); | ||||
const QRectF &boundingRect = item->boundingRect(); | ||||
qreal w = boundingRect.width(); | ||||
qreal h = boundingRect.height(); | ||||
m_height = qMax(m_height,h); | ||||
maxWidth = qMax(maxWidth,w); | ||||
item->setPos(point.x(),point.y()); | ||||
point.setY(point.y() + h); | ||||
if (point.y() + h > geometry.bottom() - bottom) { | ||||
// Next item would go off rect. | ||||
point.setX(point.x() + maxWidth); | ||||
point.setY(0); | ||||
if (i+1 < markers.count()) { | ||||
m_width += maxWidth; | ||||
maxWidth = 0; | ||||
} | ||||
} | ||||
} | ||||
} | ||||
m_width += maxWidth; | ||||
m_legend->d_ptr->items()->setPos(geometry.topLeft()); | ||||
m_minOffsetX = -left; | ||||
m_minOffsetY = -top; | ||||
m_maxOffsetX = m_width - geometry.width() - right; | ||||
m_maxOffsetY = m_height - geometry.height() - bottom; | ||||
} | ||||
break; | ||||
case Qt::AlignRight: { | ||||
QPointF point(geometry.width(), 0); | ||||
m_width = 0; | ||||
m_height = 0; | ||||
qreal maxWidth = 0; | ||||
for (int i = 0; i < markers.count(); i++) { | ||||
LegendMarkerItem *item = markers.at(i)->d_ptr->item(); | ||||
if (item->isVisible()) { | ||||
item->setGeometry(geometry); | ||||
const QRectF &boundingRect = item->boundingRect(); | ||||
qreal w = boundingRect.width(); | ||||
qreal h = boundingRect.height(); | ||||
m_height = qMax(m_height,h); | ||||
maxWidth = qMax(maxWidth,w); | ||||
item->setPos(point.x() - w,point.y()); | ||||
point.setY(point.y() + h); | ||||
if (point.y() + h > geometry.bottom()-bottom) { | ||||
// Next item would go off rect. | ||||
point.setX(point.x() - maxWidth); | ||||
point.setY(0); | ||||
if (i+1 < markers.count()) { | ||||
m_width += maxWidth; | ||||
maxWidth = 0; | ||||
} | ||||
} | ||||
} | ||||
} | ||||
m_width += maxWidth; | ||||
m_legend->d_ptr->items()->setPos(geometry.topLeft()); | ||||
m_minOffsetX = - m_width + geometry.width() - left; | ||||
m_minOffsetY = -top; | ||||
m_maxOffsetX = - right; | ||||
m_maxOffsetY = m_height - geometry.height() - bottom; | ||||
} | ||||
break; | ||||
default: | ||||
break; | ||||
} | ||||
setOffset(oldOffsetX, oldOffsetY); | ||||
} | ||||
QSizeF LegendLayout::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const | ||||
{ | ||||
QSizeF size(0, 0); | ||||
qreal left, top, right, bottom; | ||||
getContentsMargins(&left, &top, &right, &bottom); | ||||
if(constraint.isValid()) { | ||||
foreach(QLegendMarker *marker, m_legend->d_ptr->markers()) { | ||||
LegendMarkerItem *item = marker->d_ptr->item(); | ||||
size = size.expandedTo(item->effectiveSizeHint(which)); | ||||
} | ||||
size = size.boundedTo(constraint); | ||||
} | ||||
else if (constraint.width() >= 0) { | ||||
qreal width = 0; | ||||
qreal height = 0; | ||||
foreach(QLegendMarker *marker, m_legend->d_ptr->markers()) { | ||||
LegendMarkerItem *item = marker->d_ptr->item(); | ||||
width+=item->effectiveSizeHint(which).width(); | ||||
height=qMax(height,item->effectiveSizeHint(which).height()); | ||||
} | ||||
size = QSizeF(qMin(constraint.width(),width), height); | ||||
} | ||||
else if (constraint.height() >= 0) { | ||||
qreal width = 0; | ||||
qreal height = 0; | ||||
foreach(QLegendMarker *marker, m_legend->d_ptr->markers()) { | ||||
LegendMarkerItem *item = marker->d_ptr->item(); | ||||
width=qMax(width,item->effectiveSizeHint(which).width()); | ||||
height+=height,item->effectiveSizeHint(which).height(); | ||||
} | ||||
size = QSizeF(width,qMin(constraint.height(),height)); | ||||
} | ||||
else { | ||||
foreach(QLegendMarker *marker, m_legend->d_ptr->markers()) { | ||||
LegendMarkerItem *item = marker->d_ptr->item(); | ||||
size = size.expandedTo(item->effectiveSizeHint(which)); | ||||
} | ||||
} | ||||
size += QSize(left + right, top + bottom); | ||||
return size; | ||||
} | ||||
bool LegendLayout::widthLongerThan(const LegendWidthStruct *item1, | ||||
const LegendWidthStruct *item2) | ||||
{ | ||||
return item1->width > item2->width; | ||||
} | ||||
QT_CHARTS_END_NAMESPACE | ||||