File indexing completed on 2024-05-05 04:49:30

0001 /****************************************************************************************
0002 * Copyright (c) 2009 Thomas Luebking <thomas.luebking@web.de>                          *
0003 *                                                                                      *
0004 * This program is free software; you can redistribute it and/or modify it under        *
0005 * the terms of the GNU General Public License as published by the Free Software        *
0006 * Foundation; either version 2 of the License, or (at your option) any later           *
0007 * version.                                                                             *
0008 *                                                                                      *
0009 * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0010 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0011 * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0012 *                                                                                      *
0013 * You should have received a copy of the GNU General Public License along with         *
0014 * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0015 ****************************************************************************************/
0016 
0017 #include "VolumeDial.h"
0018 
0019 #include "PaletteHandler.h"
0020 #include "SvgHandler.h"
0021 
0022 #include <QConicalGradient>
0023 #include <QCoreApplication>
0024 #include <QMouseEvent>
0025 #include <QPainter>
0026 #include <QToolBar>
0027 #include <QToolTip>
0028 
0029 #include <KColorUtils>
0030 #include <KLocalizedString>
0031 
0032 #include <cmath>
0033 
0034 VolumeDial::VolumeDial( QWidget *parent ) : QDial( parent )
0035     , m_isClick( false )
0036     , m_isDown( false )
0037     , m_muted( false )
0038 {
0039     m_anim.step = 0;
0040     m_anim.timer = 0;
0041     setMouseTracking( true );
0042 
0043     connect( this, &VolumeDial::valueChanged, this, &VolumeDial::valueChangedSlot );
0044     connect( The::paletteHandler(), &PaletteHandler::newPalette, this, &VolumeDial::paletteChanged );
0045 }
0046 
0047 void VolumeDial::addWheelProxies(const QList<QWidget *> &proxies )
0048 {
0049     foreach ( QWidget *proxy, proxies )
0050     {
0051         if ( !m_wheelProxies.contains( proxy ) )
0052         {
0053             proxy->installEventFilter( this );
0054             connect ( proxy, &QWidget::destroyed, this, &VolumeDial::removeWheelProxy );
0055             m_wheelProxies << proxy;
0056         }
0057     }
0058 }
0059 
0060 void VolumeDial::paletteChanged( const QPalette &palette )
0061 {
0062     const QColor &fg = palette.color( foregroundRole() );
0063     const QColor &hg = palette.color( QPalette::Highlight );
0064     const qreal contrast = KColorUtils::contrastRatio( hg, palette.color( backgroundRole() ) );
0065     m_highlightColor = KColorUtils::mix( hg, fg, 1.0 - contrast/3.0 );
0066     renderIcons();
0067 }
0068 
0069 void VolumeDial::enterEvent( QEvent * )
0070 {
0071     startFade();
0072 }
0073 
0074 // NOTICE: we intercept wheelEvents for ourself to prevent the tooltip hiding on them,
0075 // see ::wheelEvent()
0076 // this is _NOT_ redundant to the code in MainToolbar.cpp
0077 bool VolumeDial::eventFilter( QObject *o, QEvent *e )
0078 {
0079     if ( e->type() == QEvent::Wheel && !static_cast<QWheelEvent*>(e)->modifiers() )
0080     {
0081         if ( o == this || m_wheelProxies.contains( static_cast<QWidget*>( o ) ) )
0082         {
0083             QWheelEvent *wev = static_cast<QWheelEvent*>(e);
0084             if ( o != this )
0085             {
0086                 QPoint pos( 0, 0 ); // the event needs to be on us or nothing will happen
0087                 QWheelEvent nwev( pos, mapToGlobal( pos ), wev->pixelDelta(), wev->angleDelta(), wev->buttons(), wev->modifiers(), wev->phase(), wev->inverted(), wev->source() );
0088                 wheelEvent( &nwev );
0089             }
0090             else
0091                 wheelEvent( wev );
0092             return true;
0093         }
0094         else // we're not needed globally anymore
0095             qApp->removeEventFilter( this );
0096     }
0097     return false;
0098 }
0099 
0100 void VolumeDial::leaveEvent( QEvent * )
0101 {
0102     startFade();
0103 }
0104 
0105 static bool onRing( const QRect &r, const QPoint &p )
0106 {
0107     const QPoint c = r.center();
0108     const int dx = p.x() - c.x();
0109     const int dy = p.y() - c.y();
0110     return sqrt(dx*dx + dy*dy) > r.width()/4;
0111 }
0112 
0113 void VolumeDial::mouseMoveEvent( QMouseEvent *me )
0114 {
0115     if ( me->buttons() == Qt::NoButton )
0116         setCursor( onRing( rect(), me->pos() ) ? Qt::PointingHandCursor : Qt::ArrowCursor );
0117     else if ( m_isClick )
0118         me->accept();
0119     else
0120         QDial::mouseMoveEvent( me );
0121 }
0122 
0123 void VolumeDial::mousePressEvent( QMouseEvent *me )
0124 {
0125     if ( me->button() != Qt::LeftButton )
0126     {
0127         QDial::mousePressEvent( me );
0128         return;
0129     }
0130 
0131     m_isClick = !onRing( rect(), me->pos() );
0132 
0133     if ( m_isClick )
0134         update(); // hide the ring
0135     else
0136     {
0137         setCursor( Qt::PointingHandCursor ); // hint dragging
0138         QDial::mousePressEvent( me ); // this will directly jump to the proper position
0139     }
0140 
0141     // for value changes caused by mouseevent we'll only let our adjusted value changes be emitted
0142     // see ::sliderChange()
0143     m_formerValue = value();
0144     blockSignals( true );
0145 }
0146 
0147 void VolumeDial::mouseReleaseEvent( QMouseEvent *me )
0148 {
0149     if ( me->button() != Qt::LeftButton )
0150         return;
0151 
0152     blockSignals( false ); // free signals
0153     setCursor( Qt::ArrowCursor );
0154     setSliderDown( false );
0155 
0156     if ( m_isClick )
0157     {
0158         m_isClick = !onRing( rect(), me->pos() );
0159         if ( m_isClick )
0160             Q_EMIT muteToggled( !m_muted );
0161     }
0162 
0163     m_isClick = false;
0164 }
0165 
0166 void VolumeDial::paintEvent( QPaintEvent * )
0167 {
0168     QPainter p( this );
0169     int icon = m_muted ? 0 : 3;
0170     if ( icon && value() < 66 )
0171         icon = value() < 33 ? 1 : 2;
0172     p.setRenderHint( QPainter::SmoothPixmapTransform );
0173     p.drawPixmap( 0,0, m_icon[ icon ].width()/2,  m_icon[ icon ].height()/2,  m_icon[ icon ] );
0174     if ( !m_isClick )
0175     {
0176         p.setPen( QPen( m_sliderGradient, 3, Qt::SolidLine, Qt::RoundCap ) );
0177         p.setRenderHint( QPainter::Antialiasing );
0178         p.drawArc( rect().adjusted(4,4,-4,-4), -110*16, - value()*320*16 / (maximum() - minimum()) );
0179     }
0180     p.end();
0181 }
0182 
0183 void VolumeDial::removeWheelProxy( QObject *w )
0184 {
0185     m_wheelProxies.removeOne( static_cast<QWidget*>(w) );
0186 }
0187 
0188 void VolumeDial::resizeEvent( QResizeEvent *re )
0189 {
0190     if( width() != height() )
0191         resize( height(), height() );
0192     else
0193         QDial::resizeEvent( re );
0194 
0195     if( re->size() != re->oldSize() )
0196     {
0197         renderIcons();
0198         m_sliderGradient = QPixmap( size() );
0199         updateSliderGradient();
0200         update();
0201     }
0202 }
0203 
0204 void VolumeDial::renderIcons()
0205 {
0206     //double size svg render to have better looking high-dpi toolbar
0207     m_icon[0] = The::svgHandler()->renderSvg( "Muted",      width()*2, height()*2, "Muted",      true );
0208     m_icon[1] = The::svgHandler()->renderSvg( "Volume_low", width()*2, height()*2, "Volume_low", true );
0209     m_icon[2] = The::svgHandler()->renderSvg( "Volume_mid", width()*2, height()*2, "Volume_mid", true );
0210     m_icon[3] = The::svgHandler()->renderSvg( "Volume",     width()*2, height()*2, "Volume",     true );
0211     if( layoutDirection() == Qt::RightToLeft )
0212     {
0213         for ( int i = 0; i < 4; ++i )
0214             m_icon[i] = QPixmap::fromImage( m_icon[i].toImage().mirrored( true, false ) );
0215     }
0216 }
0217 
0218 void VolumeDial::startFade()
0219 {
0220     if ( m_anim.timer )
0221         killTimer( m_anim.timer );
0222     m_anim.timer = startTimer( 40 );
0223 }
0224 
0225 void VolumeDial::stopFade()
0226 {
0227     killTimer( m_anim.timer );
0228     m_anim.timer = 0;
0229     if ( m_anim.step < 0 )
0230         m_anim.step = 0;
0231     else if ( m_anim.step > 6 )
0232         m_anim.step = 6;
0233 }
0234 
0235 void VolumeDial::timerEvent( QTimerEvent *te )
0236 {
0237     if ( te->timerId() != m_anim.timer )
0238         return;
0239     if ( underMouse() ) // fade in
0240     {
0241         m_anim.step += 2;
0242         if ( m_anim.step > 5 )
0243             stopFade();
0244     }
0245     else // fade out
0246     {
0247         --m_anim.step;
0248         if ( m_anim.step < 1 )
0249             stopFade();
0250     }
0251     updateSliderGradient();
0252     repaint();
0253 }
0254 
0255 void VolumeDial::updateSliderGradient()
0256 {
0257     m_sliderGradient.fill( Qt::transparent );
0258     QColor c = m_highlightColor;
0259     if ( !m_anim.step )
0260     {
0261         c.setAlpha( 99 );
0262         m_sliderGradient.fill( c );
0263         return;
0264     }
0265 
0266     QConicalGradient cg( m_sliderGradient.rect().center(), -90 );
0267 
0268     c.setAlpha( 99 + m_anim.step*156/6 );
0269     cg.setColorAt( 0, c );
0270     c.setAlpha( 99 + m_anim.step*42/6 );
0271     cg.setColorAt( 1, c );
0272 
0273     QPainter p( &m_sliderGradient );
0274     p.fillRect( m_sliderGradient.rect(), cg );
0275     p.end();
0276 }
0277 
0278 void VolumeDial::wheelEvent( QWheelEvent *wev )
0279 {
0280     QDial::wheelEvent( wev );
0281     wev->accept();
0282 
0283     const QPoint tooltipPosition = mapToGlobal( rect().translated( 7, -22 ).bottomLeft() );
0284     QToolTip::showText( tooltipPosition, toolTip() );
0285 
0286     // NOTICE: this is a bit tricky.
0287     // the ToolTip "QTipLabel" just installed a global eventfilter that intercepts various
0288     // events and hides itself on them. Therefore every even wheelevent will close the tip
0289     // ("works - works not - works - works not - ...")
0290     // so we post-install our own global eventfilter to handle wheel events meant for us bypassing
0291     // the ToolTip eventfilter
0292 
0293     // first remove to prevent multiple installations but ensure we're on top of the ToolTip filter
0294     qApp->removeEventFilter( this );
0295     // it's ultimately removed in the timer triggered ::hideToolTip() slot
0296     qApp->installEventFilter( this );
0297 }
0298 
0299 void VolumeDial::setMuted( bool mute )
0300 {
0301     m_muted = mute;
0302 
0303     setToolTip( m_muted ? i18n( "Muted" ) : i18n( "Volume: %1%", value() ) );
0304     update();
0305 }
0306 
0307 QSize VolumeDial::sizeHint() const
0308 {
0309     if ( QToolBar *toolBar = qobject_cast<QToolBar*>( parentWidget() ) )
0310         return toolBar->iconSize();
0311 
0312     return QDial::sizeHint();
0313 }
0314 
0315 void VolumeDial::sliderChange( SliderChange change )
0316 {
0317     if ( change == SliderValueChange && isSliderDown() && signalsBlocked() )
0318     {
0319         int d = value() - m_formerValue;
0320         if ( d && d < 33 && d > -33 ) // don't allow real "jumps" > 1/3
0321         {
0322             if ( d > 5 ) // ease movement
0323                 d = 5;
0324             else if ( d < -5 )
0325                 d = -5;
0326             m_formerValue += d;
0327             blockSignals( false );
0328             Q_EMIT sliderMoved( m_formerValue );
0329             Q_EMIT valueChanged( m_formerValue );
0330             blockSignals( true );
0331         }
0332         if ( d )
0333             setValue( m_formerValue );
0334     }
0335     QDial::sliderChange(change);
0336 }
0337 
0338 void VolumeDial::valueChangedSlot( int v )
0339 {
0340     m_isClick = false;
0341 
0342     setToolTip( m_muted ? i18n( "Muted" ) : i18n( "Volume: %1%", v ) );
0343     update();
0344 }
0345