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