File indexing completed on 2024-04-28 05:32:14

0001 /*
0002     this file is part of the oxygen gtk engine
0003     SPDX-FileCopyrightText: 2011 Hugo Pereira Da Costa <hugo.pereira@free.fr>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "oxygencairocontext.h"
0009 #include "oxygencairoutils.h"
0010 #include "config.h"
0011 #include "oxygengtkutils.h"
0012 #include "oxygenmetrics.h"
0013 #include "oxygenrgba.h"
0014 #include "oxygenshadowhelper.h"
0015 
0016 #include "config.h"
0017 
0018 #include <cstring>
0019 #include <iostream>
0020 #include <cairo/cairo.h>
0021 
0022 #ifdef GDK_WINDOWING_X11
0023 #include <cairo/cairo-xlib.h>
0024 #include <gdk/gdkx.h>
0025 #include <X11/Xatom.h>
0026 #endif
0027 
0028 namespace Oxygen
0029 {
0030 
0031     const char* const ShadowHelper::netWMShadowAtomName( "_KDE_NET_WM_SHADOW" );
0032 
0033     //______________________________________________
0034     ShadowHelper::ShadowHelper( void ):
0035         _supported( false ),
0036         _size(0),
0037         _hooksInitialized( false )
0038     {
0039 
0040         #ifdef GDK_WINDOWING_X11
0041         _atom = None;
0042         #endif
0043 
0044         #if OXYGEN_DEBUG
0045         std::cerr << "Oxygen::ShadowHelper::ShadowHelper" << std::endl;
0046         #endif
0047     }
0048 
0049     //______________________________________________
0050     ShadowHelper::~ShadowHelper( void )
0051     {
0052         #if OXYGEN_DEBUG
0053         std::cerr << "Oxygen::ShadowHelper::~ShadowHelper" << std::endl;
0054         #endif
0055 
0056         for( WidgetMap::iterator iter = _widgets.begin(); iter != _widgets.end(); ++iter )
0057         { iter->second._destroyId.disconnect(); }
0058 
0059         reset();
0060         _realizeHook.disconnect();
0061     }
0062 
0063     //______________________________________________
0064     void ShadowHelper::reset( void )
0065     {
0066 
0067         #if OXYGEN_DEBUG
0068         std::cerr << "Oxygen::ShadowHelper::reset" << std::endl;
0069         #endif
0070 
0071         #ifdef GDK_WINDOWING_X11
0072         GdkScreen* screen = gdk_screen_get_default();
0073         if( !screen ) return;
0074 
0075         Display* display( GDK_DISPLAY_XDISPLAY( gdk_screen_get_display( screen ) ) );
0076 
0077         // round pixmaps
0078         for( PixmapList::const_iterator iter = _roundPixmaps.begin(); iter != _roundPixmaps.end(); ++iter )
0079         { XFreePixmap(display, *iter); }
0080 
0081         // square pixmaps
0082         for( PixmapList::const_iterator iter = _squarePixmaps.begin(); iter != _squarePixmaps.end(); ++iter )
0083         { XFreePixmap(display, *iter); }
0084         #endif
0085 
0086         // clear arrays
0087         _roundPixmaps.clear();
0088         _squarePixmaps.clear();
0089 
0090         // reset size
0091         _size = 0;
0092 
0093     }
0094 
0095     //______________________________________________
0096     void ShadowHelper::initializeHooks( void )
0097     {
0098        if( _hooksInitialized ) return;
0099 
0100         #if OXYGEN_DEBUG
0101         std::cerr << "Oxygen::ShadowHelper::initializeHooks" << std::endl;
0102         #endif
0103 
0104         // install hooks
0105         _realizeHook.connect( "realize", (GSignalEmissionHook)realizeHook, this );
0106         _hooksInitialized = true;
0107 
0108     }
0109 
0110     //______________________________________________
0111     void ShadowHelper::initialize( const ColorUtils::Rgba& color, const WindowShadow& shadow )
0112     {
0113 
0114         #if OXYGEN_DEBUG
0115         std::cerr << "Oxygen::ShadowHelper::initialize" << std::endl;
0116         #endif
0117 
0118         reset();
0119         _size = int(shadow.shadowSize()) - WindowShadow::Overlap;
0120 
0121         // round tiles
0122         WindowShadowKey key;
0123         key.hasTopBorder = true;
0124         key.hasBottomBorder = true;
0125         _roundTiles = shadow.tileSet( color, key );
0126 
0127         // square tiles
0128         key.hasTopBorder = false;
0129         key.hasBottomBorder = false;
0130         _squareTiles = shadow.tileSet( color, key );
0131 
0132         // re-install shadows for all windowId
0133         for( WidgetMap::const_iterator iter = _widgets.begin(); iter != _widgets.end(); ++iter )
0134         { installX11Shadows( iter->first ); }
0135 
0136     }
0137 
0138     //______________________________________________
0139     bool ShadowHelper::registerWidget( GtkWidget* widget )
0140     {
0141 
0142         // do nothing if not supported
0143         if( !_supported ) return false;
0144 
0145         // check widget
0146         if( !( widget && GTK_IS_WINDOW( widget ) ) ) return false;
0147 
0148         // make sure that widget is not already registered
0149         if( _widgets.find( widget ) != _widgets.end() ) return false;
0150 
0151         // check if window is accepted
0152         if( !acceptWidget( widget ) ) return false;
0153 
0154         // try install shadows
0155         installX11Shadows( widget );
0156 
0157         // register in map and returns success
0158         WidgetData data;
0159         data._destroyId.connect( G_OBJECT( widget ), "destroy", G_CALLBACK( destroyNotifyEvent ), this );
0160         _widgets.insert( std::make_pair( widget, data ) );
0161 
0162         return true;
0163 
0164     }
0165 
0166     //______________________________________________
0167     void ShadowHelper::unregisterWidget( GtkWidget* widget )
0168     {
0169         // find matching data in map
0170         WidgetMap::iterator iter( _widgets.find( widget ) );
0171         if( iter == _widgets.end() ) return;
0172 
0173         // disconnect
0174         iter->second._destroyId.disconnect();
0175 
0176         // remove from map
0177         _widgets.erase( iter );
0178     }
0179 
0180     //______________________________________________
0181     bool ShadowHelper::isMenu( GtkWidget* widget ) const
0182     {
0183         if( !( widget && GTK_IS_WINDOW( widget ) ) ) return false;
0184         const GdkWindowTypeHint hint( gtk_window_get_type_hint( GTK_WINDOW( widget ) ) );
0185         return
0186             hint == GDK_WINDOW_TYPE_HINT_MENU ||
0187             hint == GDK_WINDOW_TYPE_HINT_DROPDOWN_MENU ||
0188             hint == GDK_WINDOW_TYPE_HINT_POPUP_MENU;
0189     }
0190 
0191     //______________________________________________
0192     bool ShadowHelper::isToolTip( GtkWidget* widget ) const
0193     {
0194         if( !( widget && GTK_IS_WINDOW( widget ) ) ) return false;
0195         const GdkWindowTypeHint hint( gtk_window_get_type_hint( GTK_WINDOW( widget ) ) );
0196         return hint == GDK_WINDOW_TYPE_HINT_TOOLTIP;
0197     }
0198 
0199     //______________________________________________
0200     bool ShadowHelper::acceptWidget( GtkWidget* widget ) const
0201     {
0202 
0203         // check widget and type
0204         if( !( widget && GTK_IS_WINDOW( widget ) ) ) return false;
0205 
0206         // for openoffice, accept all non decorated windows
0207         if( _applicationName.isOpenOffice() ) return true;
0208 
0209         // otherwise check window hint
0210         const GdkWindowTypeHint hint( gtk_window_get_type_hint( GTK_WINDOW( widget ) ) );
0211         return
0212             hint == GDK_WINDOW_TYPE_HINT_MENU ||
0213             hint == GDK_WINDOW_TYPE_HINT_DROPDOWN_MENU ||
0214             hint == GDK_WINDOW_TYPE_HINT_POPUP_MENU ||
0215             hint == GDK_WINDOW_TYPE_HINT_COMBO ||
0216             hint == GDK_WINDOW_TYPE_HINT_TOOLTIP;
0217     }
0218 
0219     //______________________________________________
0220     void ShadowHelper::createPixmapHandles( void )
0221     {
0222 
0223         #if OXYGEN_DEBUG
0224         std::cerr << "Oxygen::ShadowHelper::createPixmapHandles" << std::endl;
0225         #endif
0226 
0227         #ifdef GDK_WINDOWING_X11
0228         // create atom
0229         if( !_atom )
0230         {
0231 
0232             // get screen and check
0233             GdkScreen* screen = gdk_screen_get_default();
0234             if( !screen )
0235             {
0236 
0237                 #if OXYGEN_DEBUG
0238                 std::cerr << "ShadowHelper::createPixmapHandles - screen is NULL" << std::endl;
0239                 #endif
0240 
0241                 return;
0242             }
0243 
0244             // get display and check
0245             Display* display( GDK_DISPLAY_XDISPLAY( gdk_screen_get_display( screen ) ) );
0246             if( !display )
0247             {
0248 
0249                 #if OXYGEN_DEBUG
0250                 std::cerr << "ShadowHelper::createPixmapHandles - display is NULL" << std::endl;
0251                 #endif
0252 
0253                 return;
0254             }
0255 
0256            _atom = XInternAtom( display, netWMShadowAtomName, False);
0257         }
0258 
0259         // make sure size is valid
0260         if( _size <= 0 ) return;
0261 
0262         // opacity
0263         const int shadowOpacity = 150;
0264 
0265         if( _roundPixmaps.empty() || _squarePixmaps.empty() )
0266         {
0267             // get screen, display, visual and check
0268             // no need to check screen and display, since was already done for ATOM
0269             GdkScreen* screen = gdk_screen_get_default();
0270             if( !gdk_screen_get_rgba_visual( screen ) )
0271             {
0272 
0273                 #if OXYGEN_DEBUG
0274                 std::cerr << "ShadowHelper::createPixmapHandles - no valid RGBA visual found." << std::endl;
0275                 #endif
0276 
0277                 return;
0278 
0279             }
0280         }
0281 
0282         // make sure pixmaps are not already initialized
0283         if( _roundPixmaps.empty() )
0284         {
0285 
0286             _roundPixmaps.push_back( createPixmap( _roundTiles.surface( 1 ), shadowOpacity ) );
0287             _roundPixmaps.push_back( createPixmap( _roundTiles.surface( 2 ), shadowOpacity ) );
0288             _roundPixmaps.push_back( createPixmap( _roundTiles.surface( 5 ), shadowOpacity ) );
0289             _roundPixmaps.push_back( createPixmap( _roundTiles.surface( 8 ), shadowOpacity ) );
0290             _roundPixmaps.push_back( createPixmap( _roundTiles.surface( 7 ), shadowOpacity ) );
0291             _roundPixmaps.push_back( createPixmap( _roundTiles.surface( 6 ), shadowOpacity ) );
0292             _roundPixmaps.push_back( createPixmap( _roundTiles.surface( 3 ), shadowOpacity ) );
0293             _roundPixmaps.push_back( createPixmap( _roundTiles.surface( 0 ), shadowOpacity ) );
0294 
0295         }
0296 
0297         if( _squarePixmaps.empty() )
0298         {
0299 
0300             _squarePixmaps.push_back( createPixmap( _squareTiles.surface( 1 ), shadowOpacity ) );
0301             _squarePixmaps.push_back( createPixmap( _squareTiles.surface( 2 ), shadowOpacity ) );
0302             _squarePixmaps.push_back( createPixmap( _squareTiles.surface( 5 ), shadowOpacity ) );
0303             _squarePixmaps.push_back( createPixmap( _squareTiles.surface( 8 ), shadowOpacity ) );
0304             _squarePixmaps.push_back( createPixmap( _squareTiles.surface( 7 ), shadowOpacity ) );
0305             _squarePixmaps.push_back( createPixmap( _squareTiles.surface( 6 ), shadowOpacity ) );
0306             _squarePixmaps.push_back( createPixmap( _squareTiles.surface( 3 ), shadowOpacity ) );
0307             _squarePixmaps.push_back( createPixmap( _squareTiles.surface( 0 ), shadowOpacity ) );
0308 
0309         }
0310 
0311         #endif
0312 
0313     }
0314 
0315     //______________________________________________
0316     #ifdef GDK_WINDOWING_X11
0317     Pixmap ShadowHelper::createPixmap( const Cairo::Surface& surface, int opacity ) const
0318     {
0319         assert( surface.isValid() );
0320         int width(0);
0321         int height(0);
0322         cairo_surface_get_size( surface, width, height );
0323 
0324         GdkScreen* screen = gdk_screen_get_default();
0325         Display* display( GDK_DISPLAY_XDISPLAY( gdk_screen_get_display( screen ) ) );
0326         Window root( GDK_WINDOW_XID( gdk_screen_get_root_window( screen ) ) );
0327         Pixmap pixmap = XCreatePixmap( display, root, width, height, 32 );
0328 
0329         // create surface for pixmap
0330         {
0331             Cairo::Surface dest( cairo_xlib_surface_create( display, pixmap, GDK_VISUAL_XVISUAL( gdk_screen_get_rgba_visual( screen ) ), width, height ) );
0332             Cairo::Context context( dest );
0333             cairo_set_operator( context, CAIRO_OPERATOR_SOURCE );
0334 
0335             cairo_rectangle( context, 0, 0, width, height );
0336             cairo_set_source_surface( context, surface, 0, 0 );
0337             cairo_fill( context );
0338 
0339             if( opacity < 255 )
0340             {
0341 
0342                 cairo_set_operator( context, CAIRO_OPERATOR_DEST_IN );
0343                 cairo_set_source( context, ColorUtils::Rgba( 0, 0, 0, double(opacity)/255 ) );
0344                 cairo_rectangle( context, 0, 0, width, height );
0345                 cairo_fill( context );
0346 
0347             }
0348 
0349         }
0350 
0351         return pixmap;
0352 
0353     }
0354     #endif
0355 
0356     //______________________________________________
0357     void ShadowHelper::installX11Shadows( GtkWidget* widget )
0358     {
0359 
0360         #ifdef GDK_WINDOWING_X11
0361 
0362         #if OXYGEN_DEBUG
0363         std::cerr
0364             << "Oxygen::ShadowHelper::installX11Shadows - "
0365             << " widget: " << widget
0366             << " wid: " << GDK_WINDOW_XID( gtk_widget_get_window( widget ) )
0367             << std::endl;
0368         #endif
0369 
0370         // do nothing if not supported
0371         if( !_supported ) return;
0372 
0373         // check widget
0374         if( !GTK_IS_WIDGET( widget ) ) return;
0375 
0376         // make sure handles and atom are defined
0377         createPixmapHandles();
0378 
0379         GdkWindow  *window = gtk_widget_get_window( widget );
0380         GdkDisplay *display = gtk_widget_get_display( widget );
0381 
0382         std::vector<unsigned long> data;
0383         const bool isMenu( this->isMenu( widget ) );
0384         const bool isToolTip( this->isToolTip( widget ) );
0385         if( _applicationName.isOpenOffice() || ( (isMenu||isToolTip) && _applicationName.isXul( widget ) ) )
0386         {
0387 
0388             data = _squarePixmaps;
0389             data.push_back( _size );
0390             data.push_back( _size );
0391             data.push_back( _size );
0392             data.push_back( _size );
0393 
0394         } else {
0395 
0396             data = _roundPixmaps;
0397             if( isMenu )
0398             {
0399 
0400                 /*
0401                 for menus, need to shrink top and bottom shadow size, since body is done likely with respect to real size
0402                 in painting method (Oxygen::Style::renderMenuBackground)
0403                 */
0404                 data.push_back( _size - Menu_VerticalOffset );
0405                 data.push_back( _size );
0406                 data.push_back( _size - Menu_VerticalOffset );
0407                 data.push_back( _size );
0408 
0409             } else {
0410 
0411                 // all sides have same sizz
0412                 data.push_back( _size );
0413                 data.push_back( _size );
0414                 data.push_back( _size );
0415                 data.push_back( _size );
0416 
0417             }
0418 
0419         }
0420 
0421         // change property
0422         XChangeProperty(
0423             GDK_DISPLAY_XDISPLAY( display ), GDK_WINDOW_XID(window), _atom, XA_CARDINAL, 32, PropModeReplace,
0424             reinterpret_cast<const unsigned char *>(&data[0]), data.size() );
0425 
0426         #endif
0427 
0428     }
0429 
0430     //_______________________________________________________
0431     void ShadowHelper::uninstallX11Shadows( GtkWidget* widget ) const
0432     {
0433 
0434         #ifdef GDK_WINDOWING_X11
0435 
0436         // do nothing if not supported
0437         if( !_supported ) return;
0438 
0439         if( !GTK_IS_WIDGET( widget ) ) return;
0440         GdkWindow  *window = gtk_widget_get_window( widget );
0441         GdkDisplay *display = gtk_widget_get_display( widget );
0442         XDeleteProperty( GDK_DISPLAY_XDISPLAY( display ), GDK_WINDOW_XID(window), _atom);
0443         #endif
0444 
0445     }
0446 
0447     //_______________________________________________________
0448     gboolean ShadowHelper::realizeHook( GSignalInvocationHint*, guint, const GValue* params, gpointer data )
0449     {
0450 
0451         // get widget from params
0452         GtkWidget* widget( GTK_WIDGET( g_value_get_object( params ) ) );
0453 
0454         // check type
0455         if( !GTK_IS_WIDGET( widget ) ) return FALSE;
0456         static_cast<ShadowHelper*>(data)->registerWidget( widget );
0457         return TRUE;
0458     }
0459 
0460     //____________________________________________________________________________________________
0461     gboolean ShadowHelper::destroyNotifyEvent( GtkWidget* widget, gpointer data )
0462     {
0463         static_cast<ShadowHelper*>(data)->unregisterWidget( widget );
0464         return FALSE;
0465     }
0466 
0467 }