Skip to content
Snippets Groups Projects
qwt_widget_overlay.cpp 8.25 KiB
Newer Older
  • Learn to ignore specific revisions
  • Bryant's avatar
    Bryant committed
    /* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
     * Qwt Widget Library
     * Copyright (C) 1997   Josef Wilgen
     * Copyright (C) 2002   Uwe Rathmann
     *
     * This library is free software; you can redistribute it and/or
     * modify it under the terms of the Qwt License, Version 1.0
     *****************************************************************************/
    
    #include "qwt_widget_overlay.h"
    #include "qwt_painter.h"
    #include <qpainter.h>
    #include <qpaintengine.h>
    #include <qimage.h>
    #include <qevent.h>
    
    static QImage::Format qwtMaskImageFormat()
    {
        if ( QwtPainter::isX11GraphicsSystem() )
            return QImage::Format_ARGB32;
    
        return QImage::Format_ARGB32_Premultiplied;
    }
    
    static QRegion qwtAlphaMask( 
        const QImage& image, const QVector<QRect> rects )
    {
        const int w = image.width();
        const int h = image.height();
    
        QRegion region;
        QRect rect;
    
        for ( int i = 0; i < rects.size(); i++ )
        {
            int x1, x2, y1, y2;
            rects[i].getCoords( &x1, &y1, &x2, &y2 );
    
            x1 = qMax( x1, 0 );
            x2 = qMin( x2, w - 1 );
            y1 = qMax( y1, 0 );
            y2 = qMin( y2, h - 1 );
    
            for ( int y = y1; y <= y2; ++y ) 
            {
                bool inRect = false;
                int rx0 = -1;
    
                const uint *line = 
                    reinterpret_cast<const uint *> ( image.scanLine( y ) ) + x1;
                for ( int x = x1; x <= x2; x++ ) 
                {
                    const bool on = ( ( *line++ >> 24 ) != 0 );
                    if ( on != inRect ) 
                    {
                        if ( inRect  ) 
                        {
                            rect.setCoords( rx0, y, x - 1, y );
                            region += rect;
                        } 
                        else 
                        {
                            rx0 = x;
                        }
    
                        inRect = on;
                    } 
                }
    
                if ( inRect ) 
                {
                    rect.setCoords( rx0, y, x2, y );
                    region = region.united( rect );
                }
            }
        }
    
        return region;
    }
    
    class QwtWidgetOverlay::PrivateData
    {
    public:
        PrivateData():
            maskMode( QwtWidgetOverlay::MaskHint ),
            renderMode( QwtWidgetOverlay::AutoRenderMode ),
            rgbaBuffer( NULL )
        {
        }
    
        ~PrivateData()
        {
            resetRgbaBuffer();
        }
    
        void resetRgbaBuffer()
        {
            if ( rgbaBuffer )
            {
                ::free( rgbaBuffer );
                rgbaBuffer = NULL;
            }
        }
    
        MaskMode maskMode;
        RenderMode renderMode;
        uchar *rgbaBuffer;
    };
    
    /*!
       \brief Constructor
       \param widget Parent widget, where the overlay is aligned to
    */
    QwtWidgetOverlay::QwtWidgetOverlay( QWidget* widget ):
        QWidget( widget )
    {
        d_data = new PrivateData;
    
        setAttribute( Qt::WA_TransparentForMouseEvents );
        setAttribute( Qt::WA_NoSystemBackground );
        setFocusPolicy( Qt::NoFocus );
    
        if ( widget )
        {
            resize( widget->size() );
            widget->installEventFilter( this );
        }
    }
    
    //! Destructor
    QwtWidgetOverlay::~QwtWidgetOverlay()
    {
        delete d_data;
    }
    
    /*!
       \brief Specify how to find the mask for the overlay
    
       \param mode New mode
       \sa maskMode()
     */
    void QwtWidgetOverlay::setMaskMode( MaskMode mode )
    {
        if ( mode != d_data->maskMode )
        {
            d_data->maskMode = mode;
            d_data->resetRgbaBuffer();
        }
    }
    
    /*!
       \return Mode how to find the mask for the overlay
       \sa setMaskMode()
     */
    QwtWidgetOverlay::MaskMode QwtWidgetOverlay::maskMode() const
    {
        return d_data->maskMode;
    }
    
    /*!
       Set the render mode
       \param mode Render mode
    
       \sa RenderMode, renderMode()
    */
    void QwtWidgetOverlay::setRenderMode( RenderMode mode )
    {
        d_data->renderMode = mode;
    }
    
    /*!
       \return Render mode
       \sa RenderMode, setRenderMode()
     */
    QwtWidgetOverlay::RenderMode QwtWidgetOverlay::renderMode() const
    {
        return d_data->renderMode;
    }
    
    /*!
       Recalculate the mask and repaint the overlay
     */
    void QwtWidgetOverlay::updateOverlay()
    {
        updateMask();
        update();
    }
    
    void QwtWidgetOverlay::updateMask()
    {
        d_data->resetRgbaBuffer();
    
        QRegion mask;
    
        if ( d_data->maskMode == QwtWidgetOverlay::MaskHint )
        {
            mask = maskHint();
        }
        else if ( d_data->maskMode == QwtWidgetOverlay::AlphaMask )
        {
            // TODO: the image doesn't need to be larger than
            //       the bounding rectangle of the hint !!
    
            QRegion hint = maskHint();
            if ( hint.isEmpty() )
                hint += QRect( 0, 0, width(), height() );
    
            // A fresh buffer from calloc() is usually faster
            // than reinitializing an existing one with
            // QImage::fill( 0 ) or memset()
    
            d_data->rgbaBuffer = ( uchar* )::calloc( width() * height(), 4 );
    
            QImage image( d_data->rgbaBuffer, 
                width(), height(), qwtMaskImageFormat() );
    
            QPainter painter( &image );
            draw( &painter );
            painter.end();
    
            mask = qwtAlphaMask( image, hint.rects() );
    
            if ( d_data->renderMode == QwtWidgetOverlay::DrawOverlay )
            {
                // we don't need the buffer later
                d_data->resetRgbaBuffer();
            }
        }
    
        // A bug in Qt initiates a full repaint of the widget
        // when we change the mask, while we are visible !
    
        setVisible( false );
    
        if ( mask.isEmpty() )
            clearMask();
        else
            setMask( mask );
    
        setVisible( true );
    }
    
    /*!
      Paint event
      \param event Paint event
    
      \sa drawOverlay()
    */
    void QwtWidgetOverlay::paintEvent( QPaintEvent* event )
    {
        const QRegion clipRegion = event->region();
    
        QPainter painter( this );
    
        bool useRgbaBuffer = false;
        if ( d_data->renderMode == QwtWidgetOverlay::CopyAlphaMask )
        {
            useRgbaBuffer = true;
        }
        else if ( d_data->renderMode == QwtWidgetOverlay::AutoRenderMode )
        {
            if ( painter.paintEngine()->type() == QPaintEngine::Raster )
                useRgbaBuffer = true;
        }
    
        if ( d_data->rgbaBuffer && useRgbaBuffer )
        {
            const QImage image( d_data->rgbaBuffer, 
                width(), height(), qwtMaskImageFormat() );
    
            QVector<QRect> rects;
            if ( clipRegion.rects().size() > 2000 )
            {
                // the region is to complex
                painter.setClipRegion( clipRegion );
                rects += clipRegion.boundingRect();
            }
            else
            {
                rects = clipRegion.rects();
            }
    
            for ( int i = 0; i < rects.size(); i++ )
            {
                const QRect r = rects[i];
                painter.drawImage( r.topLeft(), image, r );
            }
        }
        else
        {
            painter.setClipRegion( clipRegion );
            draw( &painter );
        }
    }
    
    /*!
      Resize event
      \param event Resize event
    */
    void QwtWidgetOverlay::resizeEvent( QResizeEvent* event )
    {
        Q_UNUSED( event );
    
        d_data->resetRgbaBuffer();
    }
    
    void QwtWidgetOverlay::draw( QPainter *painter ) const
    {
        QWidget *widget = const_cast< QWidget *>( parentWidget() );
        if ( widget )
        {
            painter->setClipRect( parentWidget()->contentsRect() );
    
            // something special for the plot canvas
            QPainterPath clipPath;
    
            ( void )QMetaObject::invokeMethod(
                widget, "borderPath", Qt::DirectConnection,
                Q_RETURN_ARG( QPainterPath, clipPath ), Q_ARG( QRect, rect() ) );
    
            if (!clipPath.isEmpty())
            {
                painter->setClipPath( clipPath, Qt::IntersectClip );
            }
        }
    
        drawOverlay( painter );
    }
    
    /*!
       \brief Calculate an approximation for the mask
    
       - MaskHint
         The hint is used as mask.
    
       - AlphaMask
         The hint is used to speed up the algorithm
         for calculating a mask from non transparent pixels
    
       - NoMask
         The hint is unused.
    
       The default implementation returns an invalid region
       indicating no hint.
    
       \return Hint for the mask
     */
    QRegion QwtWidgetOverlay::maskHint() const
    {
        return QRegion();
    }
    
    /*!
      \brief Event filter
    
      Resize the overlay according to the size of the parent widget.
    
      \param object Object to be filtered
      \param event Event
    
      \return See QObject::eventFilter()
    */
    
    bool QwtWidgetOverlay::eventFilter( QObject* object, QEvent* event )
    {
        if ( object == parent() && event->type() == QEvent::Resize )
        {
            QResizeEvent *resizeEvent = static_cast<QResizeEvent *>( event );
            resize( resizeEvent->size() );
        }
    
        return QObject::eventFilter( object, event );
    }