libyui-qt  2.47.1.1
QY2Styler.cc
1 /*
2  Copyright (C) 2000-2012 Novell, Inc
3  This library is free software; you can redistribute it and/or modify
4  it under the terms of the GNU Lesser General Public License as
5  published by the Free Software Foundation; either version 2.1 of the
6  License, or (at your option) version 3.0 of the License. This library
7  is distributed in the hope that it will be useful, but WITHOUT ANY
8  WARRANTY; without even the implied warranty of MERCHANTABILITY or
9  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
10  License for more details. You should have received a copy of the GNU
11  Lesser General Public License along with this library; if not, write
12  to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
13  Floor, Boston, MA 02110-1301 USA
14 */
15 
16 
17 /*-/
18 
19  File: QY2Styler.cc
20 
21  Author: Stefan Kulow <coolo@suse.de>
22 
23 /-*/
24 
25 
26 #define YUILogComponent "qt-styler"
27 #include <yui/YUILog.h>
28 #include <yui/YUIException.h>
29 #include <yui/Libyui_config.h>
30 #include <YSettings.h>
31 
32 #include "QY2Styler.h"
33 #include <QDebug>
34 #include <QFile>
35 #include <QString>
36 #include <QStringList>
37 #include <QApplication>
38 #include <QWidget>
39 #include <QPainter>
40 #include <QSvgRenderer>
41 #include <iostream>
42 #include <QPixmapCache>
43 #include <QFileInfo>
44 #include <QRegularExpression>
45 
46 #define LOGGING_CAUSES_QT4_THREADING_PROBLEMS 1
47 
48 std::ostream & operator<<( std::ostream & stream, const QString & str );
49 std::ostream & operator<<( std::ostream & stream, const QStringList & strList );
50 std::ostream & operator<<( std::ostream & stream, const QWidget * widget );
51 
52 using namespace std;
53 
54 
55 QY2Styler::QY2Styler( QObject * parent,
56  const QString & defaultStyleSheet,
57  const QString & alternateStyleSheet)
58  : QObject( parent )
59 {
60  QPixmapCache::setCacheLimit( 5 * 1024 );
61  yuiDebug() << "Styler created" << std::endl;
62  setDefaultStyleSheet(defaultStyleSheet);
63  setAlternateStyleSheet(alternateStyleSheet);
64  _currentStyleSheet = QString( "" );
65 }
66 
67 
68 QY2Styler *
69 QY2Styler::styler()
70 {
71  static QY2Styler * styler = 0;
72 
73  if ( ! styler )
74  {
75  yuiDebug() << "Creating QY2Styler singleton" << std::endl;
76 
77  QString y2style = getenv("Y2STYLE");
78  QString y2altstyle = getenv("Y2ALTSTYLE");
79  QString y2alttheme = y2altstyle + ".qss";
80  styler = new QY2Styler( qApp, y2style, y2alttheme );
81 
82  YUI_CHECK_NEW( styler );
83  if (y2altstyle.isEmpty() || !styler->styleSheetExists(y2alttheme))
84  styler->loadDefaultStyleSheet();
85  else
86  styler->loadAlternateStyleSheet();
87  }
88  return styler;
89 }
90 
91 bool QY2Styler::styleSheetExists(const QString & styleSheet)
92 {
93  QFileInfo fileInfo(themeDir() + styleSheet);
94  return fileInfo.isFile();
95 }
96 
97 void QY2Styler::setDefaultStyleSheet(const QString & styleSheet)
98 {
99  if (!styleSheetExists(styleSheet)) return;
100  _defaultStyleSheet = styleSheet;
101  yuiDebug() << "Setting high-contrast style sheet to "
102  << _defaultStyleSheet << std::endl;
103 }
104 
105 void QY2Styler::setAlternateStyleSheet(const QString & styleSheet)
106 {
107  if (!styleSheetExists(styleSheet)) return;
108  _alternateStyleSheet = styleSheet;
109  yuiDebug() << "Setting default style sheet to "
110  << _alternateStyleSheet << std::endl;
111 }
112 
114 {
115  if (!loadStyleSheet(_defaultStyleSheet)) return false;
116  _usingAlternateStyleSheet = false;
117  return true;
118 }
119 
121 {
122  if (!loadStyleSheet(_alternateStyleSheet)) return false;
123  _usingAlternateStyleSheet = true;
124  return true;
125 }
126 
127 bool QY2Styler::loadStyleSheet( const QString & filename )
128 {
129  QFile file( themeDir() + filename );
130 
131  if ( file.open( QIODevice::ReadOnly ) )
132  {
133  yuiMilestone() << "Using style sheet \"" << file.fileName() << "\"" << std::endl;
134  QString text = file.readAll();
135  _currentStyleSheet = QString(filename);
136  setStyleSheet( text );
137  return true;
138  }
139  else
140  {
141  yuiMilestone() << "Couldn't open style sheet \"" << file.fileName() << "\"" << std::endl;
142  return false;
143  }
144 }
145 
146 const QString QY2Styler::buildStyleSheet(QString content)
147 {
148  QStringList alreadyImportedFilenames;
149  return buildStyleSheet(content, alreadyImportedFilenames);
150 }
151 
152 const QString QY2Styler::buildStyleSheet(QString content, QStringList & alreadyImportedFilenames)
153 {
154  QRegularExpression re(" *@import +url\\(\"(.+)\"\\);");
155 
156  QRegularExpressionMatchIterator it = re.globalMatch(content);
157  while (it.hasNext()) {
158  QRegularExpressionMatch match = it.next();
159  QString fullPath = themeDir() + match.captured(1);
160  content.replace(match.captured(0), buildStyleSheetFromFile(fullPath, alreadyImportedFilenames));
161  }
162  return content;
163 }
164 
165 const QString QY2Styler::buildStyleSheetFromFile(const QString & filename, QStringList & alreadyImportedFilenames)
166 {
167  QFile file(filename);
168 
169  if ( !alreadyImportedFilenames.contains(filename) && file.open( QIODevice::ReadOnly ) ) {
170  alreadyImportedFilenames << filename;
171  return buildStyleSheet(QString(file.readAll()), alreadyImportedFilenames);
172  }
173  else
174  return "";
175 }
176 
177 void QY2Styler::setStyleSheet( const QString & text )
178 {
179  _style = buildStyleSheet(text);
180  processUrls( _style );
181 
182  QWidget *child;
183  QList< QWidget* > childlist;
184 
185  foreach( childlist, _children )
186  foreach( child, childlist )
187  child->setStyleSheet( _style );
188 
189  foreach( QWidget *registered_widget, _registered_widgets )
190  registered_widget->setStyleSheet( _style );
191 }
192 
194 {
197  else
199 }
200 
201 void QY2Styler::processUrls( QString & text )
202 {
203  QString result;
204  QStringList lines = text.split( '\n' );
205  QRegExp urlRegex( ": *url\\((.*)\\)" );
206  QRegExp backgroundRegex( "^ */\\* *Background: *([^ ]*) *([^ ]*) *\\*/$" );
207  QRegExp richTextRegex( "^ */\\* *Richtext: *([^ ]*) *\\*/$" );
208 
209  _backgrounds.clear();
210 
211  for ( QStringList::const_iterator it = lines.begin(); it != lines.end(); ++it )
212  {
213  QString line = *it;
214 
215  // Replace file name inside url() with full path (from themeDir() )
216 
217  if ( urlRegex.indexIn( line ) >= 0 )
218  {
219  QString fileName = urlRegex.cap( 1 );
220  QString fullPath = themeDir() + fileName;
221  yuiDebug() << "Expanding " << fileName << "\tto " << fullPath << std::endl;
222  line.replace( urlRegex, ": url(" + fullPath + ")");
223  }
224 
225  if ( backgroundRegex.exactMatch( line ) )
226  {
227  QStringList name = backgroundRegex.cap( 1 ).split( '#' );
228  QString fullPath = themeDir() + backgroundRegex.cap( 2 );
229  yuiDebug() << "Expanding background " << name[0] << "\tto " << fullPath << std::endl;
230 
231  _backgrounds[ name[0] ].filename = fullPath;
232  _backgrounds[ name[0] ].full = false;
233 
234  if ( name.size() > 1 )
235  _backgrounds[ name[0] ].full = ( name[1] == "full" );
236  }
237 
238  if ( richTextRegex.exactMatch( line ) )
239  {
240  QString filename = richTextRegex.cap( 1 );
241  QFile file( themeDir() + "/" + filename );
242 
243  if ( file.open( QIODevice::ReadOnly ) )
244  {
245  yuiDebug() << "Reading " << file.fileName();
246  _textStyle = file.readAll();
247  }
248  else
249  {
250  yuiError() << "Can't read " << file.fileName();
251  }
252  }
253 
254  result += line;
255  }
256 
257  text = result;
258 }
259 
260 
261 QString
263 {
264  return QString(YSettings::themeDir().c_str());
265 }
266 
267 
268 void QY2Styler::registerWidget( QWidget * widget )
269 {
270  widget->installEventFilter( this );
271  widget->setAutoFillBackground( true );
272  widget->setStyleSheet( _style );
273  _registered_widgets.push_back( widget );
274 }
275 
276 
277 void QY2Styler::unregisterWidget( QWidget *widget )
278 {
279  _children.remove( widget );
280  _registered_widgets.removeOne( widget );
281 }
282 
283 
284 void QY2Styler::registerChildWidget( QWidget * parent, QWidget * widget )
285 {
286  // Don't use yuiDebug() here - deadlock (reason unknown so far) in thread handling!
287 
288  qDebug() << "Registering " << widget << " for parent " << parent << endl;
289  widget->installEventFilter( this );
290  _children[parent].push_back( widget );
291 }
292 
293 
294 QImage
295 QY2Styler::getScaled( const QString name, const QSize & size )
296 {
297  QImage image = _backgrounds[name].pix;
298 
299  if ( size != image.size() )
300  image = image.scaled( size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
301  else
302  image = image.convertToFormat( QImage::Format_ARGB32 );
303 
304  if ( image.isNull() )
305  yuiError() << "Can't load pixmap from " << name << std::endl;
306 #if 1
307  else
308  yuiMilestone() << "Loaded pixmap from \"" << name
309  << "\" size: " << image.size().width() << "x" << image.size().height()
310  << std::endl;
311 #endif
312 
313  return image;
314 }
315 
316 
317 void QY2Styler::renderParent( QWidget * wid )
318 {
319  // yuiDebug() << "Rendering " << wid << std::endl;
320  QString name = wid->objectName();
321 
322  // TODO
323  wid->setPalette( QApplication::palette() );
324 
325  // if the parent does not have a background, this does not make sense
326  if ( _backgrounds[name].pix.isNull() )
327  return;
328 
329  QRect fillRect = wid->contentsRect();
330  if ( _backgrounds[name].full )
331  fillRect = wid->rect();
332 
333  QImage back;
334 
335  if ( _backgrounds[name].lastscale != fillRect.size() )
336  {
337  _backgrounds[name].scaled = getScaled( name, fillRect.size() );
338  _backgrounds[name].lastscale = fillRect.size();
339  }
340 
341  back = _backgrounds[name].scaled;
342 
343  QPainter pain( &back );
344  QWidget *child;
345 
346 
347  foreach( child, _children[wid] )
348  {
349  // yuiDebug() << "foreach " << child << " " << wid << std::endl;
350  QString name = child->objectName();
351 
352  if (! child->isVisible() || _backgrounds[name].pix.isNull() )
353  continue;
354 
355  QRect fillRect = child->contentsRect();
356  if ( _backgrounds[name].full )
357  fillRect = child->rect();
358 
359  QString key = QString( "style_%1_%2_%3" ).arg( name ).arg( fillRect.width() ).arg( fillRect.height() );
360  QPixmap scaled;
361 
362  if ( QPixmapCache::find( key, scaled ) )
363  {
364  // yuiDebug() << "found " << key << std::endl;
365  }
366  else
367  {
368  scaled = QPixmap::fromImage( getScaled( name, fillRect.size() ) );
369  QPixmapCache::insert( key, scaled );
370  }
371  pain.drawPixmap( wid->mapFromGlobal( child->mapToGlobal( fillRect.topLeft() ) ), scaled );
372  }
373 
374  QPixmap result = QPixmap::fromImage( back );
375 
376  QPalette p = wid->palette();
377  p.setBrush(QPalette::Window, result );
378  wid->setPalette( p );
379 }
380 
381 
382 bool
383 QY2Styler::updateRendering( QWidget *wid )
384 {
385  if (!wid)
386  return false;
387 
388  QString name = wid->objectName();
389 
390  if (! wid->isVisible() || !wid->updatesEnabled() )
391  return false;
392 
393  if ( _backgrounds[name].pix.isNull() )
394  {
395  QString back = _backgrounds[ name ].filename;
396 
397  if ( back.isEmpty() )
398  {
399  _backgrounds[ name ].pix = QImage();
400  }
401  else
402  {
403  QImage image ( back );
404  _backgrounds[ name ].pix = image;
405 
406  if ( image.isNull() )
407  {
408  yuiError() << "Couldn't load background image \"" << back
409  << "\" for \"" << name << "\""
410  << std::endl;
411  }
412  else
413  {
414  yuiDebug() << "Loading background image \"" << back
415  << "\" for " << name << "\""
416  << std::endl;
417  }
418  }
419  }
420 
421 
422  // if it's a child itself, we have to do the full blow action
423 
424  if ( !_children.contains( wid ) )
425  {
426  QWidget *parent = wid->parentWidget();
427  while ( parent && !_children.contains( parent ) )
428  parent = parent->parentWidget();
429  if (!parent)
430  return false;
431  renderParent( parent );
432  }
433  else
434  {
435  renderParent( wid );
436  }
437 
438  return true;
439 }
440 
441 
442 bool
443 QY2Styler::eventFilter( QObject * obj, QEvent * ev )
444 {
445  if ( ev->type() == QEvent::Resize ||
446  ev->type() == QEvent::Show ||
447  ev->type() == QEvent::LayoutRequest ||
448  ev->type() == QEvent::UpdateRequest )
449  updateRendering( qobject_cast<QWidget*>( obj ) );
450 
451  return QObject::eventFilter( obj, ev );
452 }
453 
454 
455 
456 
457 std::ostream & operator<<( std::ostream & stream, const QString & str )
458 {
459  return stream << qPrintable( str );
460 }
461 
462 
463 std::ostream & operator<<( std::ostream & stream, const QStringList & strList )
464 {
465  stream << "[ ";
466 
467  for ( QStringList::const_iterator it = strList.begin();
468  it != strList.end();
469  ++it )
470  {
471  stream << qPrintable( *it ) << " ";
472  }
473 
474  stream << " ]";
475 
476  return stream;
477 }
478 
479 
480 std::ostream & operator<<( std::ostream & stream, const QWidget * widget )
481 {
482 #if LOGGING_CAUSES_QT4_THREADING_PROBLEMS
483 
484  // QObject::metaObject()->className() and QObject::objectName() can cause
485  // YQUI to hang, probably due to threading problems.
486 
487  stream << "QWidget at " << hex << (void *) widget << dec;
488 #else
489  if ( widget )
490  {
491  if ( widget->metaObject() )
492  stream << widget->metaObject()->className();
493  else
494  stream << "<QWidget>";
495 
496  if ( ! widget->objectName().isEmpty() )
497  stream << " \"" << qPrintable( widget->objectName() ) << "\"";
498 
499  stream << " at " << hex << widget << dec;
500  }
501  else // ! widget
502  {
503  stream << "<NULL QWidget>";
504  }
505 #endif
506 
507 
508  return stream;
509 }
510 
511 
512 #include "QY2Styler.moc"
bool styleSheetExists(const QString &file)
Determines if an style sheet exists.
Definition: QY2Styler.cc:91
QY2Styler(QObject *parent, const QString &defaultStyleSheet="", const QString &alternateStyleSheet="")
Constructor.
Definition: QY2Styler.cc:55
const QString buildStyleSheet(QString content)
Build a stylesheet from a string.
Definition: QY2Styler.cc:146
STL namespace.
void toggleAlternateStyleSheet()
Toggle between default/alternate style sheets.
Definition: QY2Styler.cc:193
const QString buildStyleSheetFromFile(const QString &filename, QStringList &alreadyImportedFilenames)
Build a stylesheet from a file.
Definition: QY2Styler.cc:165
void setStyleSheet(const QString &text)
Applies a style sheet from a string.
Definition: QY2Styler.cc:177
void registerWidget(QWidget *widget)
Registers a widget and applies the style sheet.
Definition: QY2Styler.cc:268
bool loadStyleSheet(const QString &file)
Loads and apply a style sheet from a file.
Definition: QY2Styler.cc:127
bool usingAlternateStyleSheet()
Determines if the alternate style is being used.
Definition: QY2Styler.h:155
void processUrls(QString &text)
Search and replace some self-defined macros in the style sheet.
Definition: QY2Styler.cc:201
bool loadDefaultStyleSheet()
Loads the default stylesheet.
Definition: QY2Styler.cc:113
void setDefaultStyleSheet(const QString &styleSheet)
Set style sheet for the default theme.
Definition: QY2Styler.cc:97
void setAlternateStyleSheet(const QString &styleSheet)
Set style sheet for the alternate theme.
Definition: QY2Styler.cc:105
bool loadAlternateStyleSheet()
Loads the alternate stylesheet.
Definition: QY2Styler.cc:120
QString themeDir() const
Returns the path to the style sheets directory.
Definition: QY2Styler.cc:262
void registerChildWidget(QWidget *parent, QWidget *widget)
Registers a child widget.
Definition: QY2Styler.cc:284
void unregisterWidget(QWidget *widget)
Unregisters a widget.
Definition: QY2Styler.cc:277