/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd ** All rights reserved. ** For any questions to The Qt Company, please use contact form at http://qt.io ** ** This file is part of the Qt Charts module. ** ** Licensees holding valid commercial license for Qt may use this file in ** accordance with the Qt License Agreement provided with the Software ** or, alternatively, in accordance with the terms contained in a written ** agreement between you and The Qt Company. ** ** If you have questions regarding the use of this file, please use ** contact form at http://qt.io ** ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QT_CHARTS_BEGIN_NAMESPACE ChartPresenter::ChartPresenter(QChart *chart, QChart::ChartType type) : QObject(chart), m_chart(chart), m_options(QChart::NoAnimation), m_animationDuration(ChartAnimationDuration), m_animationCurve(QEasingCurve::OutQuart), m_state(ShowState), m_background(0), m_plotAreaBackground(0), m_title(0), m_localizeNumbers(false) #ifndef QT_NO_OPENGL , m_glWidget(0) , m_glUseWidget(true) #endif { if (type == QChart::ChartTypeCartesian) m_layout = new CartesianChartLayout(this); else if (type == QChart::ChartTypePolar) m_layout = new PolarChartLayout(this); Q_ASSERT(m_layout); } ChartPresenter::~ChartPresenter() { #ifndef QT_NO_OPENGL delete m_glWidget.data(); #endif } void ChartPresenter::setGeometry(const QRectF rect) { if (m_rect != rect) { m_rect = rect; foreach (ChartItem *chart, m_chartItems) { chart->domain()->setSize(rect.size()); chart->setPos(rect.topLeft()); } #ifndef QT_NO_OPENGL if (!m_glWidget.isNull()) m_glWidget->setGeometry(m_rect.toRect()); #endif emit plotAreaChanged(m_rect); } } QRectF ChartPresenter::geometry() const { return m_rect; } void ChartPresenter::handleAxisAdded(QAbstractAxis *axis) { axis->d_ptr->initializeGraphics(rootItem()); axis->d_ptr->initializeAnimations(m_options, m_animationDuration, m_animationCurve); ChartAxisElement *item = axis->d_ptr->axisItem(); item->setPresenter(this); item->setThemeManager(m_chart->d_ptr->m_themeManager); m_axisItems<invalidate(); } void ChartPresenter::handleAxisRemoved(QAbstractAxis *axis) { ChartAxisElement *item = axis->d_ptr->m_item.take(); if (item->animation()) item->animation()->stopAndDestroyLater(); item->hide(); item->disconnect(); item->deleteLater(); m_axisItems.removeAll(item); m_axes.removeAll(axis); m_layout->invalidate(); } void ChartPresenter::handleSeriesAdded(QAbstractSeries *series) { series->d_ptr->initializeGraphics(rootItem()); series->d_ptr->initializeAnimations(m_options, m_animationDuration, m_animationCurve); series->d_ptr->setPresenter(this); ChartItem *chart = series->d_ptr->chartItem(); chart->setPresenter(this); chart->setThemeManager(m_chart->d_ptr->m_themeManager); chart->setDataSet(m_chart->d_ptr->m_dataset); chart->domain()->setSize(m_rect.size()); chart->setPos(m_rect.topLeft()); chart->handleDomainUpdated(); //this could be moved to intializeGraphics when animator is refactored m_chartItems<invalidate(); } void ChartPresenter::handleSeriesRemoved(QAbstractSeries *series) { ChartItem *chart = series->d_ptr->m_item.take(); chart->hide(); chart->disconnect(); chart->deleteLater(); if (chart->animation()) chart->animation()->stopAndDestroyLater(); m_chartItems.removeAll(chart); m_series.removeAll(series); m_layout->invalidate(); } void ChartPresenter::setAnimationOptions(QChart::AnimationOptions options) { if (m_options != options) { QChart::AnimationOptions oldOptions = m_options; m_options = options; if (options.testFlag(QChart::SeriesAnimations) != oldOptions.testFlag(QChart::SeriesAnimations)) { foreach (QAbstractSeries *series, m_series) series->d_ptr->initializeAnimations(m_options, m_animationDuration, m_animationCurve); } if (options.testFlag(QChart::GridAxisAnimations) != oldOptions.testFlag(QChart::GridAxisAnimations)) { foreach (QAbstractAxis *axis, m_axes) axis->d_ptr->initializeAnimations(m_options, m_animationDuration, m_animationCurve); } m_layout->invalidate(); // So that existing animations don't just stop halfway } } void ChartPresenter::setAnimationDuration(int msecs) { if (m_animationDuration != msecs) { m_animationDuration = msecs; foreach (QAbstractSeries *series, m_series) series->d_ptr->initializeAnimations(m_options, m_animationDuration, m_animationCurve); foreach (QAbstractAxis *axis, m_axes) axis->d_ptr->initializeAnimations(m_options, m_animationDuration, m_animationCurve); m_layout->invalidate(); // So that existing animations don't just stop halfway } } void ChartPresenter::setAnimationEasingCurve(const QEasingCurve &curve) { if (m_animationCurve != curve) { m_animationCurve = curve; foreach (QAbstractSeries *series, m_series) series->d_ptr->initializeAnimations(m_options, m_animationDuration, m_animationCurve); foreach (QAbstractAxis *axis, m_axes) axis->d_ptr->initializeAnimations(m_options, m_animationDuration, m_animationCurve); m_layout->invalidate(); // So that existing animations don't just stop halfway } } void ChartPresenter::setState(State state,QPointF point) { m_state=state; m_statePoint=point; } QChart::AnimationOptions ChartPresenter::animationOptions() const { return m_options; } void ChartPresenter::createBackgroundItem() { if (!m_background) { m_background = new ChartBackground(rootItem()); m_background->setPen(Qt::NoPen); // Theme doesn't touch pen so don't use default m_background->setBrush(QChartPrivate::defaultBrush()); m_background->setZValue(ChartPresenter::BackgroundZValue); } } void ChartPresenter::createPlotAreaBackgroundItem() { if (!m_plotAreaBackground) { if (m_chart->chartType() == QChart::ChartTypeCartesian) m_plotAreaBackground = new QGraphicsRectItem(rootItem()); else m_plotAreaBackground = new QGraphicsEllipseItem(rootItem()); // Use transparent pen instead of Qt::NoPen, as Qt::NoPen causes // antialising artifacts with axis lines for some reason. m_plotAreaBackground->setPen(QPen(Qt::transparent)); m_plotAreaBackground->setBrush(Qt::NoBrush); m_plotAreaBackground->setZValue(ChartPresenter::PlotAreaZValue); m_plotAreaBackground->setVisible(false); } } void ChartPresenter::createTitleItem() { if (!m_title) { m_title = new ChartTitle(rootItem()); m_title->setZValue(ChartPresenter::BackgroundZValue); } } void ChartPresenter::startAnimation(ChartAnimation *animation) { animation->stop(); QTimer::singleShot(0, animation, SLOT(startChartAnimation())); } void ChartPresenter::setBackgroundBrush(const QBrush &brush) { createBackgroundItem(); m_background->setBrush(brush); m_layout->invalidate(); } QBrush ChartPresenter::backgroundBrush() const { if (!m_background) return QBrush(); return m_background->brush(); } void ChartPresenter::setBackgroundPen(const QPen &pen) { createBackgroundItem(); m_background->setPen(pen); m_layout->invalidate(); } QPen ChartPresenter::backgroundPen() const { if (!m_background) return QPen(); return m_background->pen(); } void ChartPresenter::setBackgroundRoundness(qreal diameter) { createBackgroundItem(); m_background->setDiameter(diameter); m_layout->invalidate(); } qreal ChartPresenter::backgroundRoundness() const { if (!m_background) return 0; return m_background->diameter(); } void ChartPresenter::setPlotAreaBackgroundBrush(const QBrush &brush) { createPlotAreaBackgroundItem(); m_plotAreaBackground->setBrush(brush); m_layout->invalidate(); } QBrush ChartPresenter::plotAreaBackgroundBrush() const { if (!m_plotAreaBackground) return QBrush(); return m_plotAreaBackground->brush(); } void ChartPresenter::setPlotAreaBackgroundPen(const QPen &pen) { createPlotAreaBackgroundItem(); m_plotAreaBackground->setPen(pen); m_layout->invalidate(); } QPen ChartPresenter::plotAreaBackgroundPen() const { if (!m_plotAreaBackground) return QPen(); return m_plotAreaBackground->pen(); } void ChartPresenter::setTitle(const QString &title) { createTitleItem(); m_title->setText(title); m_layout->invalidate(); } QString ChartPresenter::title() const { if (!m_title) return QString(); return m_title->text(); } void ChartPresenter::setTitleFont(const QFont &font) { createTitleItem(); m_title->setFont(font); m_layout->invalidate(); } QFont ChartPresenter::titleFont() const { if (!m_title) return QFont(); return m_title->font(); } void ChartPresenter::setTitleBrush(const QBrush &brush) { createTitleItem(); m_title->setDefaultTextColor(brush.color()); m_layout->invalidate(); } QBrush ChartPresenter::titleBrush() const { if (!m_title) return QBrush(); return QBrush(m_title->defaultTextColor()); } void ChartPresenter::setBackgroundVisible(bool visible) { createBackgroundItem(); m_background->setVisible(visible); } bool ChartPresenter::isBackgroundVisible() const { if (!m_background) return false; return m_background->isVisible(); } void ChartPresenter::setPlotAreaBackgroundVisible(bool visible) { createPlotAreaBackgroundItem(); m_plotAreaBackground->setVisible(visible); } bool ChartPresenter::isPlotAreaBackgroundVisible() const { if (!m_plotAreaBackground) return false; return m_plotAreaBackground->isVisible(); } void ChartPresenter::setBackgroundDropShadowEnabled(bool enabled) { createBackgroundItem(); m_background->setDropShadowEnabled(enabled); } bool ChartPresenter::isBackgroundDropShadowEnabled() const { if (!m_background) return false; return m_background->isDropShadowEnabled(); } void ChartPresenter::setLocalizeNumbers(bool localize) { m_localizeNumbers = localize; m_layout->invalidate(); } void ChartPresenter::setLocale(const QLocale &locale) { m_locale = locale; m_layout->invalidate(); } AbstractChartLayout *ChartPresenter::layout() { return m_layout; } QLegend *ChartPresenter::legend() { return m_chart->legend(); } void ChartPresenter::setVisible(bool visible) { m_chart->setVisible(visible); } ChartBackground *ChartPresenter::backgroundElement() { return m_background; } QAbstractGraphicsShapeItem *ChartPresenter::plotAreaElement() { return m_plotAreaBackground; } QList ChartPresenter::axisItems() const { return m_axisItems; } QList ChartPresenter::chartItems() const { return m_chartItems; } ChartTitle *ChartPresenter::titleElement() { return m_title; } QRectF ChartPresenter::textBoundingRect(const QFont &font, const QString &text, qreal angle) { static QGraphicsTextItem dummyTextItem; static bool initMargin = true; if (initMargin) { dummyTextItem.document()->setDocumentMargin(textMargin()); initMargin = false; } dummyTextItem.setFont(font); dummyTextItem.setHtml(text); QRectF boundingRect = dummyTextItem.boundingRect(); // Take rotation into account if (angle) { QTransform transform; transform.rotate(angle); boundingRect = transform.mapRect(boundingRect); } return boundingRect; } // boundingRect parameter returns the rotated bounding rect of the text QString ChartPresenter::truncatedText(const QFont &font, const QString &text, qreal angle, qreal maxWidth, qreal maxHeight, QRectF &boundingRect) { QString truncatedString(text); boundingRect = textBoundingRect(font, truncatedString, angle); if (boundingRect.width() > maxWidth || boundingRect.height() > maxHeight) { // It can be assumed that almost any amount of string manipulation is faster // than calculating one bounding rectangle, so first prepare a list of truncated strings // to try. static QRegExp truncateMatcher(QStringLiteral("&#?[0-9a-zA-Z]*;$")); QVector testStrings(text.length()); int count(0); static QLatin1Char closeTag('>'); static QLatin1Char openTag('<'); static QLatin1Char semiColon(';'); static QLatin1String ellipsis("..."); while (truncatedString.length() > 1) { int chopIndex(-1); int chopCount(1); QChar lastChar(truncatedString.at(truncatedString.length() - 1)); if (lastChar == closeTag) chopIndex = truncatedString.lastIndexOf(openTag); else if (lastChar == semiColon) chopIndex = truncateMatcher.indexIn(truncatedString, 0); if (chopIndex != -1) chopCount = truncatedString.length() - chopIndex; truncatedString.chop(chopCount); testStrings[count] = truncatedString + ellipsis; count++; } // Binary search for best fit int minIndex(0); int maxIndex(count - 1); int bestIndex(count); QRectF checkRect; while (maxIndex >= minIndex) { int mid = (maxIndex + minIndex) / 2; checkRect = textBoundingRect(font, testStrings.at(mid), angle); if (checkRect.width() > maxWidth || checkRect.height() > maxHeight) { // Checked index too large, all under this are also too large minIndex = mid + 1; } else { // Checked index fits, all over this also fit maxIndex = mid - 1; bestIndex = mid; boundingRect = checkRect; } } // Default to "..." if nothing fits if (bestIndex == count) { boundingRect = textBoundingRect(font, ellipsis, angle); truncatedString = ellipsis; } else { truncatedString = testStrings.at(bestIndex); } } return truncatedString; } QString ChartPresenter::numberToString(double value, char f, int prec) { if (m_localizeNumbers) return m_locale.toString(value, f, prec); else return QString::number(value, f, prec); } QString ChartPresenter::numberToString(int value) { if (m_localizeNumbers) return m_locale.toString(value); else return QString::number(value); } void ChartPresenter::ensureGLWidget() { #ifndef QT_NO_OPENGL // GLWidget pointer is wrapped in QPointer as its parent is not in our control, and therefore // can potentially get deleted unexpectedly. if (m_glWidget.isNull() && m_glUseWidget && m_chart->scene()) { // Find the view of the scene. If the scene has multiple views, only the first view is // chosen. QList views = m_chart->scene()->views(); if (views.size()) { QGraphicsView *firstView = views.at(0); m_glWidget = new GLWidget(m_chart->d_ptr->m_dataset->glXYSeriesDataManager(), firstView); m_glWidget->setGeometry(m_rect.toRect()); m_glWidget->show(); } } #endif } #include "moc_chartpresenter_p.cpp" QT_CHARTS_END_NAMESPACE