Make playlist items use the full width available to them and adjust correctly when...
[amarok.git] / src / osd.cpp
blobe9bc2ef35eac90bff3d1ba027b53caa26ddc6b37
1 /*
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
7 * osd.cpp: Shows some text in a pretty way independent to the WM
8 * begin: Fre Sep 26 2003
9 * copyright: (C) 2004 Christian Muehlhaeuser <chris@chris.de>
10 * (C) 2004-2006 Seb Ruiz <me@sebruiz.net>
11 * (C) 2004, 2005 Max Howell
12 * (C) 2005 Gábor Lehel <illissius@gmail.com>
15 #include "osd.h"
17 #include "amarok.h"
18 #include "amarokconfig.h"
19 #include "collectiondb.h" //for albumCover location
20 #include "debug.h"
21 #include "enginecontroller.h"
22 #include "playlist.h" //if osdUsePlaylistColumns()
23 #include "playlistitem.h" //ditto
24 #include "podcastbundle.h"
25 #include "qstringx.h"
26 #include "StarManager.h"
28 #include <KApplication>
29 #include <KPixmapEffect>
30 #include <KStandardDirs> //locate
32 #include <QBitmap>
33 #include <QDesktopWidget>
34 #include <QPainter>
35 #include <QPixmap>
36 #include <QRegExp>
37 #include <QTimer>
38 #include <QVector>
40 namespace ShadowEngine
42 QImage makeShadow( const QPixmap &textPixmap, const QColor &bgColor );
46 #define MOODBAR_HEIGHT 20
49 OSDWidget::OSDWidget( QWidget *parent, const char *name )
50 : QWidget( parent, Qt::Window | Qt::X11BypassWindowManagerHint | Qt::WindowStaysOnTopHint
51 | Qt::FramelessWindowHint | Qt::CustomizeWindowHint )
52 , m_duration( 2000 )
53 , m_timer( new QTimer( this ) )
54 , m_alignment( Middle )
55 , m_screen( 0 )
56 , m_y( MARGIN )
57 , m_drawShadow( false )
58 , m_rating( 0 )
59 , m_volume( false )
61 setObjectName( name );
62 setFocusPolicy( Qt::NoFocus );
63 unsetColors();
65 connect( m_timer, SIGNAL(timeout()), SLOT(hide()) );
66 connect( CollectionDB::instance(), SIGNAL( ratingChanged( const QString&, int ) ),
67 this, SLOT( ratingChanged( const QString&, int ) ) );
69 //or crashes, KWindowSystem bug I think, crashes in QWidget::icon()
70 kapp->setTopWidget( this );
73 void
74 OSDWidget::show( const QString &text, QImage newImage )
76 if ( !newImage.isNull() )
78 m_cover = newImage;
79 int w = m_scaledCover.width();
80 int h = m_scaledCover.height();
81 m_scaledCover = m_cover.scaled( w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
83 if( isShown() )
84 show();
87 void
88 OSDWidget::ratingChanged( const QString& path, int rating )
90 Meta::TrackPtr track = EngineController::instance()->currentTrack();
91 if( !track )
92 return;
93 if( track->playableUrl().isLocalFile() && track->playableUrl().path() == path )
94 ratingChanged( rating );
97 void
98 OSDWidget::ratingChanged( const short rating )
100 m_text = '\n' + i18n( "Rating changed" );
101 setRating( rating ); //Checks isEnabled() before doing anything
103 show();
107 void
108 OSDWidget::volChanged( unsigned char volume )
110 if ( isEnabled() )
112 m_volume = true;
113 m_newvolume = volume;
114 m_text = m_newvolume ? i18n("Volume: %1%", m_newvolume) : i18n("Mute");
116 show();
120 void
121 OSDWidget::show() //virtual
123 if ( !isEnabled() || m_text.isEmpty() )
124 return;
126 const uint M = fontMetrics().width( 'x' );
128 const QRect oldGeometry = QRect( pos(), size() );
129 const QRect newGeometry = determineMetrics( M );
131 if( newGeometry.width() > 0 && newGeometry.height() > 0 )
133 m_m = M;
134 m_size = newGeometry.size();
135 //render( M, newGeometry.size() );
136 setGeometry( newGeometry );
137 QWidget::show();
138 // bitBlt( this, 0, 0, &m_buffer );
140 if( m_duration ) //duration 0 -> stay forever
141 m_timer->start( m_duration, true ); //calls hide()
143 else
144 warning() << "Attempted to make an invalid sized OSD\n";
146 update();
149 QRect
150 OSDWidget::determineMetrics( const uint M )
152 // sometimes we only have a tiddly cover
153 const QSize minImageSize = m_cover.size().boundedTo( QSize(100,100) );
155 // determine a sensible maximum size, don't cover the whole desktop or cross the screen
156 const QSize margin( (M + MARGIN) * 2, (M + MARGIN) * 2 ); //margins
157 const QSize image = m_cover.isNull() ? QSize( 0, 0 ) : minImageSize;
158 const QSize max = QApplication::desktop()->screen( m_screen )->size() - margin;
160 // If we don't do that, the boundingRect() might not be suitable for drawText() (Qt issue N67674)
161 m_text.replace( QRegExp(" +\n"), "\n" );
162 // remove consecutive line breaks
163 m_text.replace( QRegExp("\n+"), "\n" );
165 // The osd cannot be larger than the screen
166 QRect rect = fontMetrics().boundingRect( 0, 0, max.width() - image.width(), max.height(),
167 Qt::AlignCenter | Qt::WordBreak, m_text );
169 if( m_volume )
171 static const QString tmp = QString ("******").insert( 3,
172 ( i18n("Volume: 100%").length() >= i18n("Mute").length() )?
173 i18n("Volume: 100%") : i18n("Mute") );
175 QRect tmpRect = fontMetrics().boundingRect( 0, 0,
176 max.width() - image.width(), max.height() - fontMetrics().height(),
177 Qt::AlignCenter | Qt::WordBreak, tmp );
178 tmpRect.setHeight( tmpRect.height() + fontMetrics().height() / 2 );
180 rect = tmpRect;
183 if( m_rating )
185 QPixmap* star = StarManager::instance()->getStar( 1 );
186 if( rect.width() < star->width() * 5 )
187 rect.setWidth( star->width() * 5 ); //changes right edge position
188 rect.setHeight( rect.height() + star->height() + M ); //changes bottom edge pos
191 if( useMoodbar() )
192 rect.setHeight( rect.height() + MOODBAR_HEIGHT + M );
194 if( !m_cover.isNull() )
196 const int availableWidth = max.width() - rect.width() - M; //WILL be >= (minImageSize.width() - M)
198 m_scaledCover = m_cover.scaled(
199 qMin( availableWidth, m_cover.width() ),
200 qMin( rect.height(), m_cover.height() ),
201 Qt::KeepAspectRatio, Qt::SmoothTransformation ); //this will force us to be with our bounds
203 int shadowWidth = 0;
204 if( m_drawShadow && !m_scaledCover.hasAlpha() &&
205 ( m_scaledCover.width() > 22 || m_scaledCover.height() > 22 ) )
206 shadowWidth = static_cast<uint>( m_scaledCover.width() / 100.0 * 6.0 );
208 const int widthIncludingImage = rect.width()
209 + m_scaledCover.width()
210 + shadowWidth
211 + M; //margin between text + image
213 rect.setWidth( widthIncludingImage );
216 // expand in all directions by M
217 rect.adjust( -M, -M, M, M );
219 const QSize newSize = rect.size();
220 const QRect screen = QApplication::desktop()->screenGeometry( m_screen );
221 QPoint newPos( MARGIN, m_y );
223 switch( m_alignment )
225 case Left:
226 break;
228 case Right:
229 newPos.rx() = screen.width() - MARGIN - newSize.width();
230 break;
232 case Center:
233 newPos.ry() = (screen.height() - newSize.height()) / 2;
235 //FALL THROUGH
237 case Middle:
238 newPos.rx() = (screen.width() - newSize.width()) / 2;
239 break;
242 //ensure we don't dip below the screen
243 if ( newPos.y() + newSize.height() > screen.height() - MARGIN )
244 newPos.ry() = screen.height() - MARGIN - newSize.height();
246 // correct for screen position
247 newPos += screen.topLeft();
249 return QRect( newPos, rect.size() );
252 void
253 OSDWidget::paintEvent( QPaintEvent* )
255 /// render with margin/spacing @param M and @param size
257 uint M = m_m;
258 QSize size = m_size;
260 QPoint point;
261 QRect rect( point, size );
262 rect.adjust( 0, 0, -1, -1 );
264 // From qt sources
265 const uint xround = (M * 200) / size.width();
266 const uint yround = (M * 200) / size.height();
268 { /// apply the mask
269 static QBitmap mask;
271 mask.resize( size );
272 mask.fill( Qt::color0 );
274 QPainter p( &mask );
275 p.setBrush( Qt::color1 );
276 p.drawRoundRect( rect, xround, yround );
277 setMask( mask );
280 QColor shadowColor;
282 int h,s,v;
283 foregroundColor().getHsv( &h, &s, &v );
284 shadowColor = v > 128 ? Qt::black : Qt::white;
287 int align = Qt::AlignCenter | Qt::WordBreak;
289 QPainter p( this );
291 p.fillRect( rect, backgroundColor() );
293 p.setPen( backgroundColor().dark() );
294 p.drawRoundRect( rect, xround, yround );
296 rect.adjust( M, M, -M, -M );
298 if( !m_cover.isNull() )
300 QRect r( rect );
301 r.setTop( (size.height() - m_scaledCover.height()) / 2 );
302 r.setSize( m_scaledCover.size() );
304 if( !m_scaledCover.hasAlpha() && m_drawShadow &&
305 ( m_scaledCover.width() > 22 || m_scaledCover.height() > 22 ) ) {
306 // don't draw a shadow for eg, the Amarok icon
307 QImage shadow;
308 const uint shadowSize = static_cast<uint>( m_scaledCover.width() / 100.0 * 6.0 );
310 const QString folder = Amarok::saveLocation( "covershadow-cache/" );
311 const QString file = QString( "shadow_albumcover%1x%2.png" ).arg( m_scaledCover.width() + shadowSize )
312 .arg( m_scaledCover.height() + shadowSize );
313 if ( QFile::exists( folder + file ) )
314 shadow.load( folder + file );
315 else {
316 shadow.load( KStandardDirs::locate( "data", "amarok/images/shadow_albumcover.png" ) );
317 shadow = shadow.scaled( m_scaledCover.width() + shadowSize, m_scaledCover.height() + shadowSize,
318 Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
319 shadow.save( folder + file, "PNG" );
322 QPixmap target;
323 target.convertFromImage( shadow ); //FIXME slow
324 copyBlt( &target, 0, 0, &m_scaledCover );
325 m_scaledCover = target;
326 r.setTop( (size.height() - m_scaledCover.height()) / 2 );
327 r.setSize( m_scaledCover.size() );
330 p.drawPixmap( r.topLeft(), m_scaledCover );
332 rect.rLeft() += m_scaledCover.width() + M;
335 QPixmap* star = StarManager::instance()->getStar( m_rating/2 );
336 int graphicsHeight = 0;
338 if( useMoodbar() )
340 QPixmap moodbar
341 = m_moodbarBundle.moodbar().draw( rect.width(), MOODBAR_HEIGHT );
342 QRect r( rect );
343 r.setTop( rect.bottom() - moodbar.height()
344 - (m_rating ? star->height() + M : 0) );
345 graphicsHeight += moodbar.height() + M;
347 p.drawPixmap( r.left(), r.top(), moodbar );
348 m_moodbarBundle = MetaBundle();
351 if( m_rating > 0 )
353 QRect r( rect );
355 //Align to center...
356 r.setLeft(( rect.left() + rect.width() / 2 ) - star->width() * m_rating / 4 );
357 r.setTop( rect.bottom() - star->height() );
358 graphicsHeight += star->height() + M;
360 bool half = m_rating%2;
362 if( half )
364 QPixmap* halfStar = StarManager::instance()->getHalfStar( m_rating/2 + 1 );
365 p.drawPixmap( r.left() + star->width() * ( m_rating / 2 ), r.top(), *halfStar );
366 star = StarManager::instance()->getStar( m_rating/2 + 1 );
369 for( int i = 0; i < m_rating/2; i++ )
371 p.drawPixmap( r.left() + i * star->width(), r.top(), *star );
374 m_rating = 0;
377 rect.setBottom( rect.bottom() - graphicsHeight );
379 // Draw "shadow" text effect (black outline)
380 if( m_drawShadow )
382 QPixmap pixmap( rect.size() + QSize(10,10) );
383 pixmap.fill( Qt::black );
385 QPainter p2( &pixmap );
386 p2.setFont( font() );
387 p2.setPen( Qt::white );
388 p2.setBrush( Qt::white );
389 p2.drawText( QRect(QPoint(5,5), rect.size()), align , m_text );
390 p2.end();
392 p.drawImage( rect.topLeft() - QPoint(5,5), ShadowEngine::makeShadow( pixmap, shadowColor ) );
395 p.setPen( foregroundColor() );
396 p.setFont( font() );
397 p.drawText( rect, align, m_text );
400 bool
401 OSDWidget::event( QEvent *e )
403 switch( e->type() )
405 case QEvent::ApplicationPaletteChange:
406 if( !AmarokConfig::osdUseCustomColors() )
407 unsetColors(); //use new palette's colours
408 return true;
410 default:
411 return QWidget::event( e );
415 void
416 OSDWidget::mousePressEvent( QMouseEvent* )
418 hide();
421 void
422 OSDWidget::unsetColors()
424 const QColorGroup c = QApplication::palette().active();
426 setPaletteForegroundColor( c.highlightedText() );
427 setPaletteBackgroundColor( c.highlight() );
430 void
431 OSDWidget::setScreen( int screen )
433 #if 0
434 const int n = QApplication::desktop()->numScreens();
435 m_screen = (screen >= n) ? n-1 : screen;
436 #endif
439 bool
440 OSDWidget::useMoodbar( void )
442 return (m_moodbarBundle.moodbar().state() == Moodbar::Loaded &&
443 AmarokConfig::showMoodbar() );
446 ////// OSDPreviewWidget below /////////////////////
448 #include <kcursor.h>
449 #include <kiconloader.h>
450 #include <klocale.h>
452 namespace Amarok
454 QImage icon() { return QImage( KIconLoader().iconPath( "amarok", -K3Icon::SizeHuge ) ); }
457 OSDPreviewWidget::OSDPreviewWidget( QWidget *parent )
458 : OSDWidget( parent, "osdpreview" )
459 , m_dragging( false )
461 m_text = i18n( "OSD Preview - drag to reposition" );
462 m_duration = 0;
463 m_alignment = Center;
464 m_cover = Amarok::icon();
467 void OSDPreviewWidget::mousePressEvent( QMouseEvent *event )
469 m_dragOffset = event->pos();
471 if( event->button() == Qt::LeftButton && !m_dragging ) {
472 grabMouse( Qt::SizeAllCursor );
473 m_dragging = true;
478 void OSDPreviewWidget::mouseReleaseEvent( QMouseEvent * /*event*/ )
480 if( m_dragging )
482 m_dragging = false;
483 releaseMouse();
485 // compute current Position && offset
486 QDesktopWidget *desktop = QApplication::desktop();
487 int currentScreen = desktop->screenNumber( pos() );
489 if( currentScreen != -1 ) {
490 // set new data
491 m_screen = currentScreen;
492 m_y = QWidget::y();
494 emit positionChanged();
500 void OSDPreviewWidget::mouseMoveEvent( QMouseEvent *e )
502 if( m_dragging && this == mouseGrabber() )
504 // Here we implement a "snap-to-grid" like positioning system for the preview widget
506 const QRect screen = QApplication::desktop()->screenGeometry( m_screen );
507 const uint hcenter = screen.width() / 2;
508 const uint eGlobalPosX = e->globalPos().x() - screen.left();
509 const uint snapZone = screen.width() / 24;
511 QPoint destination = e->globalPos() - m_dragOffset - screen.topLeft();
512 int maxY = screen.height() - height() - MARGIN;
513 if( destination.y() < MARGIN ) destination.ry() = MARGIN;
514 if( destination.y() > maxY ) destination.ry() = maxY;
516 if( eGlobalPosX < (hcenter-snapZone) ) {
517 m_alignment = Left;
518 destination.rx() = MARGIN;
520 else if( eGlobalPosX > (hcenter+snapZone) ) {
521 m_alignment = Right;
522 destination.rx() = screen.width() - MARGIN - width();
524 else {
525 const uint eGlobalPosY = e->globalPos().y() - screen.top();
526 const uint vcenter = screen.height()/2;
528 destination.rx() = hcenter - width()/2;
530 if( eGlobalPosY >= (vcenter-snapZone) && eGlobalPosY <= (vcenter+snapZone) )
532 m_alignment = Center;
533 destination.ry() = vcenter - height()/2;
535 else m_alignment = Middle;
538 destination += screen.topLeft();
540 move( destination );
546 ////// Amarok::OSD below /////////////////////
548 #include "metabundle.h"
550 Amarok::OSD::OSD(): OSDWidget( 0 )
552 connect( CollectionDB::instance(), SIGNAL( coverChanged( const QString&, const QString& ) ),
553 this, SLOT( slotCoverChanged( const QString&, const QString& ) ) );
554 connect( CollectionDB::instance(), SIGNAL( imageFetched( const QString& ) ),
555 this, SLOT( slotImageChanged( const QString& ) ) );
558 void
559 Amarok::OSD::show( const MetaBundle &bundle ) //slot
561 QString text = "";
562 if( bundle.url().isEmpty() )
563 text = i18n( "No track playing" );
565 else
567 QVector<QString> tags;
568 tags.append(bundle.prettyTitle());
569 for( int i = 0; i < PlaylistItem::NUM_COLUMNS; ++i )
570 tags.append(bundle.prettyText( i ));
572 if( bundle.length() <= 0 )
573 tags[PlaylistItem::Length+1].clear();
575 if( AmarokConfig::osdUsePlaylistColumns() )
577 QString tag;
578 QVector<int> availableTags; //eg, ones that aren't empty
579 static const QList<int> parens = //display these in parentheses
580 QList<int>() << PlaylistItem::PlayCount << PlaylistItem::Year << PlaylistItem::Comment
581 << PlaylistItem::Genre << PlaylistItem::Length << PlaylistItem::Bitrate
582 << PlaylistItem::LastPlayed << PlaylistItem::Score << PlaylistItem::Filesize;
583 OSDWidget::setMoodbar();
584 OSDWidget::setRating( 0 );
585 for( int i = 0, n = Playlist::instance()->numVisibleColumns(); i < n; ++i )
587 const int column = Playlist::instance()->mapToLogicalColumn( i );
588 if( !tags.at( column + 1 ).isEmpty() && column != PlaylistItem::Rating )
589 availableTags.append(column);
590 if( column == PlaylistItem::Rating )
591 OSDWidget::setRating( bundle.rating() );
592 else if( column == PlaylistItem::Mood )
593 OSDWidget::setMoodbar( bundle );
596 for( int n = availableTags.count(), i = 0; i < n; ++i )
598 const int column = availableTags.at( i );
599 QString append = ( i == 0 ) ? ""
600 : ( n > 1 && i == n / 2 ) ? "\n"
601 : ( parens.contains( column ) || parens.contains( availableTags.at( i - 1 ) ) ) ? " "
602 : i18n(" - ");
603 append += ( parens.contains( column ) ? "(%1)" : "%1" );
604 text += append.arg( tags.at( column + 1 ) );
607 else
609 QMap<QString, QString> args;
610 args["prettytitle"] = bundle.prettyTitle();
611 for( int i = 0; i < PlaylistItem::NUM_COLUMNS; ++i )
612 args[bundle.exactColumnName( i ).toLower()] = bundle.prettyText( i );
614 if( bundle.length() <= 0 )
615 args["length"].clear();
618 uint time=EngineController::instance()->engine()->position();
619 uint sec=(time/1000)%60; //is there a better way to calculate the time?
620 time /= 1000;
621 uint min=(time/60)%60;
622 time /= 60;
623 uint hour=(time/60)%60;
624 QString timeformat="";
625 if(hour!=0)
627 timeformat += QString::number(hour);
628 timeformat +=':';
630 timeformat +=QString::number(min);
631 timeformat +=':';
632 if(sec<10)
633 timeformat +='0';
634 timeformat +=QString::number(sec);
635 args["elapsed"]=timeformat;
636 QStringx osd = AmarokConfig::osdText();
638 // hacky, but works...
639 if( osd.contains( "%rating" ) )
640 OSDWidget::setRating( AmarokConfig::useRatings() ? bundle.rating() : 0 );
641 else
642 OSDWidget::setRating( 0 );
644 osd.replace( "%rating", "" );
646 if( osd.contains( "%moodbar" ) && AmarokConfig::showMoodbar() )
647 OSDWidget::setMoodbar( bundle );
648 osd.replace( "%moodbar", "" );
650 text = osd.namedOptArgs( args );
652 // KDE 3.3 rejects \n in the .kcfg file, and KConfig turns \n into \\n, so...
653 text.replace( "\\n", "\n" );
656 if ( AmarokConfig::osdCover() ) {
657 //avoid showing the generic cover. we can overwrite this by passing an arg.
658 //get large cover for scaling if big cover needed
660 QString location = QString();
661 if( bundle.podcastBundle() )
662 location = CollectionDB::instance()->podcastImage( bundle, false, 0 );
663 else
664 location = CollectionDB::instance()->albumImage( bundle, false, 0 );
666 if ( location.find( "nocover" ) != -1 )
667 setImage( Amarok::icon() );
668 else
669 setImage( QImage( location ) );
672 text = text.trimmed();
675 if( text.isEmpty() )
676 text = MetaBundle::prettyTitle( bundle.url().fileName() ).trimmed();
678 if( text.startsWith( "- " ) ) //When we only have a title tag, _something_ prepends a fucking hyphen. Remove that.
679 text = text.mid( 2 );
681 if( text.isEmpty() ) //still
682 text = i18n("No information available for this track");
684 OSDWidget::show( text );
687 void
688 Amarok::OSD::applySettings()
690 setAlignment( static_cast<OSDWidget::Alignment>( AmarokConfig::osdAlignment() ) );
691 setDuration( AmarokConfig::osdDuration() );
692 setEnabled( AmarokConfig::osdEnabled() );
693 setOffset( AmarokConfig::osdYOffset() );
694 setScreen( AmarokConfig::osdScreen() );
695 setFont( AmarokConfig::osdFont() );
696 setDrawShadow( AmarokConfig::osdDrawShadow() );
698 if( AmarokConfig::osdUseCustomColors() )
700 setTextColor( AmarokConfig::osdTextColor() );
702 else unsetColors();
705 void
706 Amarok::OSD::forceToggleOSD()
708 if ( !isShown() ) {
709 const bool b = isEnabled();
710 setEnabled( true );
711 //TODO paort 2.0: fix this
712 //show( EngineController::instance()->bundle() );
713 setEnabled( b );
715 else
716 hide();
719 void
720 Amarok::OSD::slotCoverChanged( const QString &artist, const QString &album )
722 #if 0
723 if( AmarokConfig::osdCover() && artist == EngineController::instance()->bundle().artist()
724 && album == EngineController::instance()->bundle().album() )
726 QString location = CollectionDB::instance()->albumImage( artist, album, false, 0 );
728 if( location.find( "nocover" ) != -1 )
729 setImage( Amarok::icon() );
730 else
731 setImage( location );
733 #endif
736 void
737 Amarok::OSD::slotImageChanged( const QString &remoteURL )
739 #if 0
740 QString url = EngineController::instance()->bundle().url().url();
741 PodcastEpisodeBundle peb;
742 if( CollectionDB::instance()->getPodcastEpisodeBundle( url, &peb ) )
744 PodcastChannelBundle pcb;
745 if( CollectionDB::instance()->getPodcastChannelBundle( peb.parent().url(), &pcb ) )
747 if( pcb.imageURL().url() == remoteURL )
749 QString location = CollectionDB::instance()->podcastImage( remoteURL, false, 0 );
750 if( location == CollectionDB::instance()->notAvailCover( false, 0 ) )
751 setImage( Amarok::icon() );
752 else
753 setImage( location );
757 #endif
761 /* Code copied from kshadowengine.cpp
763 * Copyright (C) 2003 Laur Ivan <laurivan@eircom.net>
765 * Many thanks to:
766 * - Bernardo Hung <deciare@gta.igs.net> for the enhanced shadow
767 * algorithm (currently used)
768 * - Tim Jansen <tim@tjansen.de> for the API updates and fixes.
770 * This library is free software; you can redistribute it and/or
771 * modify it under the terms of the GNU Library General Public
772 * License version 2 as published by the Free Software Foundation.
774 * This library is distributed in the hope that it will be useful,
775 * but WITHOUT ANY WARRANTY; without even the implied warranty of
776 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
777 * Library General Public License for more details.
779 * You should have received a copy of the GNU Library General Public License
780 * along with this library; see the file COPYING.LIB. If not, write to
781 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
782 * Boston, MA 02110-1301, USA.
785 namespace ShadowEngine
787 // Not sure, doesn't work above 10
788 static const int MULTIPLICATION_FACTOR = 3;
789 // Multiplication factor for pixels directly above, under, or next to the text
790 static const double AXIS_FACTOR = 2.0;
791 // Multiplication factor for pixels diagonal to the text
792 static const double DIAGONAL_FACTOR = 0.1;
793 // Self explanatory
794 static const int MAX_OPACITY = 200;
796 double decay( QImage&, int, int );
798 QImage makeShadow( const QPixmap& textPixmap, const QColor &bgColor )
800 const int w = textPixmap.width();
801 const int h = textPixmap.height();
802 const int bgr = bgColor.red();
803 const int bgg = bgColor.green();
804 const int bgb = bgColor.blue();
806 int alphaShadow;
808 // This is the source pixmap
809 QImage img = textPixmap.toImage();
811 QImage result( w, h, QImage::Format_ARGB32 );
812 result.fill( 0 ); // fill with black
814 static const int M = 5;
815 for( int i = M; i < w - M; i++) {
816 for( int j = M; j < h - M; j++ )
818 alphaShadow = (int) decay( img, i, j );
820 result.setPixel( i,j, qRgba( bgr, bgg , bgb, qMin( MAX_OPACITY, alphaShadow ) ) );
824 return result;
827 double decay( QImage& source, int i, int j )
829 //if ((i < 1) || (j < 1) || (i > source.width() - 2) || (j > source.height() - 2))
830 // return 0;
832 double alphaShadow;
833 alphaShadow =(qGray(source.pixel(i-1,j-1)) * DIAGONAL_FACTOR +
834 qGray(source.pixel(i-1,j )) * AXIS_FACTOR +
835 qGray(source.pixel(i-1,j+1)) * DIAGONAL_FACTOR +
836 qGray(source.pixel(i ,j-1)) * AXIS_FACTOR +
838 qGray(source.pixel(i ,j+1)) * AXIS_FACTOR +
839 qGray(source.pixel(i+1,j-1)) * DIAGONAL_FACTOR +
840 qGray(source.pixel(i+1,j )) * AXIS_FACTOR +
841 qGray(source.pixel(i+1,j+1)) * DIAGONAL_FACTOR) / MULTIPLICATION_FACTOR;
843 return alphaShadow;
847 #include "osd.moc"