summaryrefslogtreecommitdiffstats
path: root/src/charts/legend/legendlayout.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/charts/legend/legendlayout.cpp')
-rw-r--r--src/charts/legend/legendlayout.cpp506
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