diff options
Diffstat (limited to 'src/charts/legend/legendlayout.cpp')
-rw-r--r-- | src/charts/legend/legendlayout.cpp | 506 |
1 files changed, 506 insertions, 0 deletions
diff --git a/src/charts/legend/legendlayout.cpp b/src/charts/legend/legendlayout.cpp new file mode 100644 index 00000000..b4e443c0 --- /dev/null +++ b/src/charts/legend/legendlayout.cpp @@ -0,0 +1,506 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Enterprise Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "legendlayout_p.h" +#include "chartpresenter_p.h" +#include "qlegend_p.h" +#include "abstractchartlayout_p.h" + +#include "qlegendmarker_p.h" +#include "legendmarkeritem_p.h" +#include "qlegendmarker.h" + +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); + + if (m_width < geometry.width()) + m_legend->d_ptr->items()->setPos(geometry.width() / 2 - m_width / 2, geometry.top()); + else + m_legend->d_ptr->items()->setPos(geometry.topLeft()); + 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); + } + } + + if (m_height < geometry.height()) + m_legend->d_ptr->items()->setPos(geometry.left(), geometry.height() / 2 - m_height / 2); + else + m_legend->d_ptr->items()->setPos(geometry.topLeft()); + 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 |