GobChartsWidget  1.0
gobchartstextitem.cpp
1 /* Copyright (C) 2012 by William Hallatt.
2  *
3  * This file forms part of the "GobChartsWidget" library.
4  *
5  * This library is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have downloaded a copy of the GNU General Public License
16  * (GNUGPL.txt) and GNU Lesser General Public License (GNULGPL.txt)
17  * along with this library. If not, see <http://www.gnu.org/licenses/>.
18  *
19  * The official website for this project is www.goblincoding.com and,
20  * although not compulsory, it would be appreciated if all works of whatever
21  * nature referring to or using this library include a reference to this site.
22  */
23 
24 #include "gobchartstextitem.h"
25 #include "utils/globalincludes.h"
26 
27 #include <QtCore/qmath.h>
28 #include <QFontMetrics>
29 #include <QTextDocument>
30 #include <QTextOption>
31 #include <QDomDocument>
32 #include <QMessageBox>
33 #include <QTextCursor>
34 
35 /*--------------------------------------------------------------------------------*/
36 
37 /* Used to calculate the margin of difference between a GobChartTextItem's height and allowed
38  height in which resize events will not be triggered. Inclusion of this "dead" space prevents
39  the item's growth from triggering a shrinking calculation and vice versa. */
40 qreal PERCENTAGE_DEAD_SPACE = 0.05;
41 
42 /* Used to calculate the margin of difference between a GobChartTextItem's width
43  based on the total available width of the provided rectangle. */
44 qreal PERCENTAGE_WIDTH_MARGIN = 0.05;
45 
46 
47 /*------------------------ NONMEMBER UTILITY FUNCTIONS ---------------------------*/
48 
49 bool WiderThanNew( const QRectF &memberRect, const QRectF &newTextRect )
50 {
51  return ( qFloor( memberRect.width() ) > qFloor( newTextRect.width() ) );
52 }
53 
54 /*--------------------------------------------------------------------------------*/
55 
56 bool HigherThanNew( const QRectF &memberRect, const QRectF &newTextRect )
57 {
58  return ( qFloor( memberRect.height() ) > qFloor( newTextRect.height() ) );
59 }
60 
61 /*--------------------------------------------------------------------------------*/
62 
63 bool HasSameHeight( const QRectF &memberRect, const QRectF &newTextRect )
64 {
65  return ( qFloor( memberRect.height() ) == qFloor( newTextRect.height() ) );
66 }
67 
68 /*--------------------------------------------------------------------------------*/
69 
70 bool HasSameWidth( const QRectF &memberRect, const QRectF &newTextRect )
71 {
72  return ( qFloor( memberRect.width() ) == qFloor( newTextRect.width() ) );
73 }
74 
75 /*--------------------------------------------------------------------------------*/
76 
77 bool LargerThanNew( const QRectF &memberRect, const QRectF &newTextRect )
78 {
79  return !( ( ( WiderThanNew ( memberRect, newTextRect ) || HasSameWidth ( memberRect, newTextRect ) ) && HigherThanNew( memberRect, newTextRect ) ) ||
80  ( ( HigherThanNew( memberRect, newTextRect ) || HasSameHeight( memberRect, newTextRect ) ) && WiderThanNew ( memberRect, newTextRect ) ) );
81 }
82 
83 
84 /*------------------------------- MEMBER FUNCTIONS -------------------------------*/
85 
86 GobChartsTextItem::GobChartsTextItem( Qt::Orientation orientation, QString uniqueID, QGraphicsItem *parent ):
87  QGraphicsTextItem( parent ),
88  m_orientation ( orientation ),
89  m_alignment ( Qt::AlignHCenter ),
90  m_textDocument ( NULLPOINTER ),
91  m_identity ( uniqueID ),
92  m_rectF (),
93  m_busyResizing ( false ),
94  m_maxFontSize ( 11 )
95 {
96  setTextInteractionFlags( Qt::TextEditable | Qt::TextEditorInteraction );
97 
98  /* Only catering for a standard bottom to top orientation, i.e. if you tilt your
99  head to the left, you should be able to read the text left to right without difficulty. */
100  if( m_orientation == Qt::Vertical )
101  {
102  setRotation( -90 );
103  }
104 
105  /* Cleanup of m_textDocument is handled by the object tree, "this" takes ownership. */
106  m_textDocument = new QTextDocument( this );
107 
108  /* Direct changes to the item's text will trigger a resize. */
109  connect( m_textDocument, SIGNAL( contentsChanged() ), this, SLOT( resize() ) );
110 
111  /* Set the alignment. */
112  QTextOption textOption( m_alignment );
113  m_textDocument->setDefaultTextOption( textOption );
114  setDocument( m_textDocument );
115 }
116 
117 /*--------------------------------------------------------------------------------*/
118 
119 void GobChartsTextItem::setRectF( const QRectF &rect )
120 {
121  qreal widthMargin = rect.width() * PERCENTAGE_WIDTH_MARGIN;
122  qreal heightMargin = rect.height() * PERCENTAGE_DEAD_SPACE;
123 
124  QPointF pos;
125 
126  if( m_orientation == Qt::Horizontal )
127  {
128  /* For horizontal orientations, do exactly what you'd expect. */
129  m_rectF.setWidth ( rect.width() - widthMargin );
130  m_rectF.setHeight( rect.height() - heightMargin );
131 
132  pos = rect.topLeft();
133  pos.ry() += ( heightMargin / 2 );
134  } else {
135  /* For vertical orientations, swap width and height. */
136  m_rectF.setHeight( rect.width() - widthMargin );
137  m_rectF.setWidth ( rect.height() - heightMargin );
138 
139  pos = rect.bottomLeft();
140  pos.ry() -= ( heightMargin / 2 );
141  }
142 
143  pos.rx() += ( widthMargin / 2 );
144  setPos( pos );
145 
146  setTextWidth( m_rectF.width() );
147  resize();
148 }
149 
150 /*--------------------------------------------------------------------------------*/
151 
152 void GobChartsTextItem::setMaxFontSize( const QFont &font )
153 {
154  m_maxFontSize = font.pointSize();
155  setFont( font );
156 }
157 
158 /*--------------------------------------------------------------------------------*/
159 
160 void GobChartsTextItem::setFont( const QFont &font )
161 {
162  QGraphicsTextItem::setFont( font );
163  resize();
164 }
165 
166 /*--------------------------------------------------------------------------------*/
167 
168 void GobChartsTextItem::setPlainText( const QString &text )
169 {
170  QGraphicsTextItem::setPlainText( text );
171 
172  QTextCursor cursor( textCursor() );
173  cursor.movePosition( QTextCursor::EndOfLine );
174  setTextCursor( cursor );
175 }
176 
177 /*--------------------------------------------------------------------------------*/
178 
179 void GobChartsTextItem::setAlignment( Qt::Alignment alignment )
180 {
181  /* Formatting the text document triggers a resize through
182  the QTextDocument::contentsChanged() signal. */
183  m_alignment = alignment;
184  QTextOption textOption( m_alignment );
185  m_textDocument->setDefaultTextOption( textOption );
186 }
187 
188 /*--------------------------------------------------------------------------------*/
189 
190 Qt::Alignment GobChartsTextItem::alignment() const
191 {
192  return m_alignment;
193 }
194 
195 /*--------------------------------------------------------------------------------*/
196 
197 void GobChartsTextItem::resize()
198 {
199  /* Avoid triggering resizing recursively with the setFont() calls below. */
200  if( !m_busyResizing && !m_rectF.isNull() )
201  {
202  m_busyResizing = true;
203 
204  /* Creating a temporary item rather than updating this object's font the
205  whole time speeds things up a little bit (we delay setting the item's new
206  font until it's the desired size), but it is predominantly to make the
207  logic that follows easier to understand and the code more legible. */
208  QGraphicsTextItem tempItem( toPlainText() );
209  tempItem.setTextWidth( textWidth() );
210 
211  QFont newFont( font() );
212  tempItem.setFont( newFont );
213 
214  QRectF newRect( tempItem.boundingRect() );
215  bool fontTooSmall( WiderThanNew ( m_rectF, newRect ) || HigherThanNew( m_rectF, newRect ) );
216 
217  /* If the user set a specific point size, we don't want to exceed it. */
218  if( fontTooSmall && ( newFont.pointSize() < m_maxFontSize ) )
219  {
220  /* Increase the font size for as long as we stay within the acceptable boundaries. */
221  while( fontTooSmall && !LargerThanNew( m_rectF, newRect ) )
222  {
223  newFont.setPointSize( newFont.pointSize() + 1 );
224  tempItem.setFont( newFont );
225  newRect = tempItem.boundingRect();
226  fontTooSmall = ( WiderThanNew ( m_rectF, newRect ) || HigherThanNew( m_rectF, newRect ) );
227  }
228 
229  /* If the last iteration pushed the size beyond the acceptable
230  limits, decrease the font size with one. */
231  if( LargerThanNew( m_rectF, newRect ) && newFont.pointSize() > 1 )
232  {
233  newFont.setPointSize( newFont.pointSize() - 1 );
234  tempItem.setFont( newFont );
235  newRect = tempItem.boundingRect();
236  }
237  } else {
238  /* Decreases the font size until we're within the acceptable boundaries. */
239  while( ( LargerThanNew( m_rectF, newRect ) || newFont.pointSize() > m_maxFontSize ) &&
240  newFont.pointSize() > 1 )
241  {
242  newFont.setPointSize( newFont.pointSize() - 1 );
243  tempItem.setFont( newFont );
244  newRect = tempItem.boundingRect();
245  }
246  }
247 
248  setFont( newFont );
249  m_busyResizing = false;
250  }
251 }
252 
253 /*--------------------------------------------------------------------------------*/
254 
255 void GobChartsTextItem::receiveKeyEvent( QKeyEvent *event )
256 {
257  keyPressEvent( event );
258 }
259 
260 /*--------------------------------------------------------------------------------*/
261 
263 {
264  QDomDocument doc;
265  QDomElement root = doc.createElement( "LabelDetails" );
266  doc.appendChild( root );
267 
268  QDomElement fontElement = doc.createElement( "Font" );
269  fontElement.setAttribute( "value", font().toString() );
270  root.appendChild( fontElement );
271 
272  QDomElement fontColour = doc.createElement( "FontColour" );
273  fontColour.setAttribute( "red", defaultTextColor().red() );
274  fontColour.setAttribute( "green", defaultTextColor().green() );
275  fontColour.setAttribute( "blue", defaultTextColor().blue() );
276  root.appendChild( fontColour );
277 
278  QDomElement maxFont = doc.createElement( "MaxFontSize" );
279  maxFont.setAttribute( "value", m_maxFontSize );
280  root.appendChild( maxFont );
281 
282  QDomElement text = doc.createElement( "Text" );
283  text.setAttribute( "value", toPlainText() );
284  root.appendChild( text );
285 
286  QDomElement align = doc.createElement( "Alignment" );
287 
288  if( m_alignment == Qt::AlignLeft )
289  {
290  align.setAttribute( "value", "left" );
291  }
292  else if( m_alignment == Qt::AlignRight )
293  {
294  align.setAttribute( "value", "right" );
295  }
296  else
297  {
298  align.setAttribute( "value", "centre" );
299  }
300 
301  root.appendChild( align );
302 
303  return doc.toString( 2 );
304 }
305 
306 /*--------------------------------------------------------------------------------*/
307 
308 bool GobChartsTextItem::setStateXML( const QDomNode &node )
309 {
310  if( !node.isNull() )
311  {
312  QDomNode labelDetails = node.firstChildElement( "LabelDetails" );
313 
314  m_maxFontSize = labelDetails.firstChildElement( "MaxFontSize" ).attribute( "value" ).toInt();
315 
316  QFont tempFont;
317  tempFont.fromString( labelDetails.firstChildElement( "Font" ).attribute( "value" ) );
318  setFont( tempFont );
319 
320  setDefaultTextColor( QColor( labelDetails.firstChildElement( "FontColour" ).attribute( "red" ).toInt(),
321  labelDetails.firstChildElement( "FontColour" ).attribute( "green" ).toInt(),
322  labelDetails.firstChildElement( "FontColour" ).attribute( "blue" ).toInt() ) );
323 
324  setPlainText( labelDetails.firstChildElement( "Text" ).attribute( "value" ) );
325 
326  QString alignMent = labelDetails.firstChildElement( "Alignment" ).attribute( "value" );
327 
328  if( alignMent == "left" )
329  {
330  m_alignment = Qt::AlignLeft;
331  }
332  else if( alignMent == "right" )
333  {
334  m_alignment = Qt::AlignRight;
335  }
336  else
337  {
338  m_alignment = Qt::AlignCenter; // if we get an unknown, return the default
339  }
340 
341  return true;
342  }
343 
344  return false;
345 }
346 
347 /*--------------------------------------------------------------------------------*/
348 
349 void GobChartsTextItem::mousePressEvent( QGraphicsSceneMouseEvent *event )
350 {
351  QGraphicsTextItem::mousePressEvent( event );
352  emit identity( m_identity );
353 }
354 
355 /*--------------------------------------------------------------------------------*/