File indexing completed on 2024-05-12 15:54:19
0001 /* 0002 * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved. 0003 * 0004 * This file is part of the KD Chart library. 0005 * 0006 * This program is free software; you can redistribute it and/or 0007 * modify it under the terms of the GNU General Public License as 0008 * published by the Free Software Foundation; either version 2 of 0009 * the License, or (at your option) any later version. 0010 * 0011 * This program is distributed in the hope that it will be useful, 0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0014 * GNU General Public License for more details. 0015 * 0016 * You should have received a copy of the GNU General Public License 0017 * along with this program. If not, see <https://www.gnu.org/licenses/>. 0018 */ 0019 0020 #include "KChartTextLabelCache.h" 0021 0022 #include <cmath> 0023 0024 #include <QtDebug> 0025 #include <QImage> 0026 #include <QPixmap> 0027 #include <QPainter> 0028 #include <QApplication> 0029 0030 #ifndef NDEBUG 0031 int HitCount = 0; 0032 int MissCount = 0; 0033 #define INC_HIT_COUNT { ++HitCount; } 0034 #define INC_MISS_COUNT { ++MissCount; } 0035 #define DUMP_CACHE_STATS \ 0036 if ( HitCount != 0 && MissCount != 0 ) { \ 0037 int total = HitCount + MissCount; \ 0038 qreal hitQuote = ( 1.0 * HitCount ) / total; \ 0039 qDebug() << "PrerenderedLabel dtor: hits/misses/total:" \ 0040 << HitCount << "/" << MissCount << "/" << total \ 0041 << "(" << 100 * hitQuote << "% hits)"; \ 0042 } 0043 #else 0044 #define INC_HIT_COUNT 0045 #define INC_MISS_COUNT 0046 #define DUMP_CACHE_STATS 0047 #endif 0048 0049 PrerenderedElement::PrerenderedElement() 0050 : m_referencePoint( KChartEnums::PositionNorthWest ) 0051 { 0052 } 0053 0054 void PrerenderedElement::setPosition( const QPointF& position ) 0055 { // this does not invalidate the element 0056 m_position = position; 0057 } 0058 0059 const QPointF& PrerenderedElement::position() const 0060 { 0061 return m_position; 0062 } 0063 0064 void PrerenderedElement::setReferencePoint( KChartEnums::PositionValue point ) 0065 { // this does not invalidate the element 0066 m_referencePoint = point; 0067 } 0068 0069 KChartEnums::PositionValue PrerenderedElement::referencePoint() const 0070 { 0071 return m_referencePoint; 0072 } 0073 0074 PrerenderedLabel::PrerenderedLabel() 0075 : PrerenderedElement() 0076 , m_dirty( true ) 0077 , m_font( qApp->font() ) 0078 , m_brush( Qt::black ) 0079 , m_pen( Qt::black ) // do not use anything invisible 0080 , m_angle( 0.0 ) 0081 { 0082 } 0083 0084 PrerenderedLabel::~PrerenderedLabel() 0085 { 0086 DUMP_CACHE_STATS; 0087 } 0088 0089 void PrerenderedLabel::invalidate() const 0090 { 0091 m_dirty = true; 0092 } 0093 0094 void PrerenderedLabel::setFont( const QFont& font ) 0095 { 0096 m_font = font; 0097 invalidate(); 0098 } 0099 0100 const QFont& PrerenderedLabel::font() const 0101 { 0102 return m_font; 0103 } 0104 0105 void PrerenderedLabel::setText( const QString& text ) 0106 { 0107 m_text = text; 0108 invalidate(); 0109 } 0110 0111 const QString& PrerenderedLabel::text() const 0112 { 0113 return m_text; 0114 } 0115 0116 void PrerenderedLabel::setBrush( const QBrush& brush ) 0117 { 0118 m_brush = brush; 0119 invalidate(); 0120 } 0121 0122 const QBrush& PrerenderedLabel::brush() const 0123 { 0124 return m_brush; 0125 } 0126 0127 void PrerenderedLabel::setAngle( qreal angle ) 0128 { 0129 m_angle = angle; 0130 invalidate(); 0131 } 0132 0133 qreal PrerenderedLabel::angle() const 0134 { 0135 return m_angle; 0136 } 0137 0138 const QPixmap& PrerenderedLabel::pixmap() const 0139 { 0140 if ( m_dirty ) { 0141 INC_MISS_COUNT; 0142 paint(); 0143 } else { 0144 INC_HIT_COUNT; 0145 } 0146 return m_pixmap; 0147 } 0148 0149 void PrerenderedLabel::paint() const 0150 { 0151 // FIXME find a better value using font metrics of text (this 0152 // requires finding the diameter of the circle formed by rotating 0153 // the bounding rect around the center): 0154 const int Width = 1000; 0155 const int Height = Width; 0156 0157 QRectF boundingRect; 0158 const QColor FullTransparent( 255, 255, 255, 0 ); 0159 #ifdef Q_WS_X11 0160 QImage pixmap( Width, Height, QImage::Format_ARGB32_Premultiplied ); 0161 qWarning() << "PrerenderedLabel::paint: using QImage for prerendered labels " 0162 << "to work around XRender/Qt4 bug."; 0163 #else 0164 QPixmap pixmap( Width, Height ); 0165 #endif 0166 // pixmap.fill( FullTransparent ); 0167 { 0168 static const QPointF Center ( 0.0, 0.0 ); 0169 QPointF textBottomRight; 0170 QPainter painter( &pixmap ); 0171 painter.setRenderHint(QPainter::TextAntialiasing, true ); 0172 painter.setRenderHint(QPainter::Antialiasing, true ); 0173 0174 // QImage (X11 workaround) does not have fill(): 0175 painter.setPen( FullTransparent ); 0176 painter.setBrush( FullTransparent ); 0177 QPainter::CompositionMode mode = painter.compositionMode(); 0178 painter.setCompositionMode( QPainter::CompositionMode_Clear ); 0179 painter.drawRect( 0, 0, Width, Height ); 0180 painter.setCompositionMode( mode ); 0181 0182 QMatrix matrix; 0183 matrix.translate( 0.5 * Width, 0.5 * Height ); 0184 matrix.rotate( m_angle ); 0185 painter.setWorldMatrix( matrix ); 0186 0187 painter.setPen( m_pen ); 0188 painter.setBrush( m_brush ); 0189 painter.setFont( m_font ); 0190 QRectF container( -0.5 * Width, -0.5 * Height, Width, 0.5 * Height ); 0191 painter.drawText( container, Qt::AlignHCenter | Qt::AlignBottom, 0192 m_text, &boundingRect ); 0193 m_referenceBottomLeft = QPointF( boundingRect.bottomLeft().x(), 0.0 ); 0194 textBottomRight = QPointF( boundingRect.bottomRight().x(), 0.0 ); 0195 m_textAscendVector = boundingRect.topRight() - textBottomRight; 0196 m_textBaseLineVector = textBottomRight - m_referenceBottomLeft; 0197 0198 // FIXME translate topright by char height 0199 boundingRect = matrix.mapRect( boundingRect ); 0200 m_referenceBottomLeft = matrix.map( m_referenceBottomLeft ) 0201 - boundingRect.topLeft(); 0202 textBottomRight = matrix.map( textBottomRight ) 0203 - boundingRect.topLeft(); 0204 m_textAscendVector = matrix.map( m_textAscendVector ) 0205 - matrix.map( Center ); 0206 m_textBaseLineVector = matrix.map( m_textBaseLineVector ) 0207 - matrix.map( Center ); 0208 } 0209 0210 m_dirty = false; // now all the calculation vectors are valid 0211 0212 QPixmap temp( static_cast<int>( boundingRect.width() ), 0213 static_cast<int>( boundingRect.height() ) ); 0214 { 0215 temp.fill( FullTransparent ); 0216 QPainter painter( &temp ); 0217 #ifdef Q_WS_X11 0218 painter.drawImage( QPointF( 0.0, 0.0 ), pixmap, boundingRect ); 0219 #else 0220 painter.drawPixmap( QPointF( 0.0, 0.0 ), pixmap, boundingRect ); 0221 #endif 0222 // #define PRERENDEREDLABEL_DEBUG 0223 #ifdef PRERENDEREDLABEL_DEBUG 0224 painter.setPen( QPen( Qt::red, 2 ) ); 0225 painter.setBrush( Qt::red ); 0226 // paint markers for the reference points 0227 QList<KChartEnums::PositionValue> positions; 0228 positions << KChartEnums::PositionCenter 0229 << KChartEnums::PositionNorthWest 0230 << KChartEnums::PositionNorth 0231 << KChartEnums::PositionNorthEast 0232 << KChartEnums::PositionEast 0233 << KChartEnums::PositionSouthEast 0234 << KChartEnums::PositionSouth 0235 << KChartEnums::PositionSouthWest 0236 << KChartEnums::PositionWest; 0237 Q_FOREACH( KChartEnums::PositionValue position, positions ) { //krazy:exclude=foreach 0238 static const double Radius = 0.5; 0239 static const double Diameter = 2 * Radius; 0240 0241 QPointF point ( referencePointLocation( position ) ); 0242 painter.drawEllipse( QRectF( point - QPointF( Radius, Radius ), 0243 QSizeF( Diameter, Diameter ) ) ); 0244 } 0245 #endif 0246 } 0247 0248 m_pixmap = temp; 0249 } 0250 0251 QPointF PrerenderedLabel::referencePointLocation() const 0252 { 0253 return referencePointLocation( referencePoint() ); 0254 } 0255 0256 QPointF PrerenderedLabel::referencePointLocation( KChartEnums::PositionValue position ) const 0257 { 0258 if ( m_dirty ) { 0259 INC_MISS_COUNT; 0260 paint(); 0261 } else { 0262 INC_HIT_COUNT; 0263 } 0264 0265 switch ( position ) { 0266 case KChartEnums::PositionCenter: 0267 return m_referenceBottomLeft + 0.5 * m_textBaseLineVector + 0.5 * m_textAscendVector; 0268 case KChartEnums::PositionNorthWest: 0269 return m_referenceBottomLeft + m_textAscendVector; 0270 case KChartEnums::PositionNorth: 0271 return m_referenceBottomLeft + 0.5 * m_textBaseLineVector + m_textAscendVector; 0272 case KChartEnums::PositionNorthEast: 0273 return m_referenceBottomLeft + m_textBaseLineVector + m_textAscendVector; 0274 case KChartEnums::PositionEast: 0275 return m_referenceBottomLeft + 0.5 * m_textAscendVector; 0276 case KChartEnums::PositionSouthEast: 0277 return m_referenceBottomLeft + m_textBaseLineVector; 0278 case KChartEnums::PositionSouth: 0279 return m_referenceBottomLeft + 0.5 * m_textBaseLineVector; 0280 case KChartEnums::PositionSouthWest: 0281 return m_referenceBottomLeft; 0282 case KChartEnums::PositionWest: 0283 return m_referenceBottomLeft + m_textBaseLineVector + 0.5 * m_textAscendVector; 0284 0285 case KChartEnums::PositionUnknown: // intentional fall-through 0286 case KChartEnums::PositionFloating: // intentional fall-through 0287 return QPointF(); 0288 } 0289 0290 return QPointF(); 0291 }