/**************************************************************************** ** ** 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" QTCOMMERCIALCHART_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 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; } } qSort(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 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; } QTCOMMERCIALCHART_END_NAMESPACE