File indexing completed on 2024-05-12 05:34:34

0001 /*
0002     this file is part of the oxygen gtk engine
0003     SPDX-FileCopyrightText: 2010 Hugo Pereira Da Costa <hugo.pereira@free.fr>
0004     SPDX-FileCopyrightText: 2010 Ruslan Kabatsayev <b7.10110111@gmail.com>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "oxygeninnershadowdata.h"
0010 #include "../oxygengtkutils.h"
0011 #include "../config.h"
0012 #include "../oxygencairocontext.h"
0013 #include "../oxygencairoutils.h"
0014 #include "oxygenanimations.h"
0015 #include "../oxygenstyle.h"
0016 #include "../oxygenmetrics.h"
0017 
0018 #include <gtk/gtk.h>
0019 #include <cstdlib>
0020 
0021 #include <cassert>
0022 #include <iostream>
0023 
0024 namespace Oxygen
0025 {
0026 
0027     //_____________________________________________
0028     void InnerShadowData::connect( GtkWidget* widget )
0029     {
0030 
0031         assert( GTK_IS_SCROLLED_WINDOW( widget ) );
0032         assert( !_target );
0033 
0034         // store target
0035         _target = widget;
0036 
0037         if( gdk_display_supports_composite( gtk_widget_get_display( widget ) ) )
0038         { _exposeId.connect( G_OBJECT(_target), "expose-event", G_CALLBACK( targetExposeEvent ), this, true ); }
0039 
0040         // check child
0041         GtkWidget* child( gtk_bin_get_child( GTK_BIN( widget ) ) );
0042         if( !child ) return;
0043 
0044         #if OXYGEN_DEBUG
0045         std::cerr
0046             << "Oxygen::InnerShadowData::connect -"
0047             << " widget: " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
0048             << " child: " << child << " (" << G_OBJECT_TYPE_NAME( child ) << ")"
0049             << std::endl;
0050         #endif
0051 
0052         registerChild( child );
0053 
0054     }
0055 
0056     //_____________________________________________
0057     void InnerShadowData::disconnect( GtkWidget* )
0058     {
0059         _target = 0;
0060         for( ChildDataMap::reverse_iterator iter = _childrenData.rbegin(); iter != _childrenData.rend(); ++iter )
0061         { iter->second.disconnect( iter->first ); }
0062 
0063         // disconnect signals
0064         _exposeId.disconnect();
0065 
0066         // clear child data
0067         _childrenData.clear();
0068     }
0069 
0070     //_____________________________________________
0071     void InnerShadowData::registerChild( GtkWidget* widget )
0072     {
0073 
0074         #if ENABLE_INNER_SHADOWS_HACK
0075 
0076         // make sure widget is not already in map
0077         if( _childrenData.find( widget ) != _childrenData.end() ) return;
0078 
0079         if( gtk_scrolled_window_get_shadow_type( GTK_SCROLLED_WINDOW( _target ) ) != GTK_SHADOW_IN )
0080         { return; }
0081 
0082         #if OXYGEN_DEBUG
0083         std::cerr
0084             << "Oxygen::InnerShadowData::registerChild -"
0085             << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
0086             << std::endl;
0087         #endif
0088 
0089         GdkWindow* window(gtk_widget_get_window(widget));
0090         if(
0091 
0092             // check window
0093             window &&
0094             gdk_window_get_window_type( window ) == GDK_WINDOW_CHILD &&
0095 
0096             // check compositing
0097             gdk_display_supports_composite( gtk_widget_get_display( widget ) ) &&
0098 
0099             // make sure widget is scrollable
0100             GTK_WIDGET_GET_CLASS( widget )->set_scroll_adjustments_signal )
0101         {
0102             ChildData data;
0103             data._unrealizeId.connect( G_OBJECT(widget), "unrealize", G_CALLBACK( childUnrealizeNotifyEvent ), this );
0104             data._initiallyComposited = gdk_window_get_composited(window);
0105             gdk_window_set_composited(window,TRUE);
0106             _childrenData.insert( std::make_pair( widget, data ) );
0107         }
0108 
0109         #endif
0110 
0111     }
0112 
0113     //________________________________________________________________________________
0114     void InnerShadowData::unregisterChild( GtkWidget* widget )
0115     {
0116         #if ENABLE_INNER_SHADOWS_HACK
0117 
0118         ChildDataMap::iterator iter( _childrenData.find( widget ) );
0119         if( iter == _childrenData.end() ) return;
0120 
0121         #if OXYGEN_DEBUG
0122         std::cerr
0123             << "Oxygen::InnerShadowData::unregisterChild -"
0124             << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
0125             << std::endl;
0126         #endif
0127 
0128         iter->second.disconnect( widget );
0129         _childrenData.erase( iter );
0130 
0131         #endif
0132     }
0133 
0134     //________________________________________________________________________________
0135     void InnerShadowData::ChildData::disconnect( GtkWidget* widget )
0136     {
0137         #if ENABLE_INNER_SHADOWS_HACK
0138 
0139         // disconnect signals
0140         _unrealizeId.disconnect();
0141 
0142         // remove compositing flag
0143         GdkWindow* window( gtk_widget_get_window( widget ) );
0144 
0145         #if OXYGEN_DEBUG
0146         std::cerr
0147             << "Oxygen::InnerShadowData::ChildData::disconnect -"
0148             << " widget: " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
0149             << " window: " << window
0150             << std::endl;
0151         #endif
0152 
0153         // restore compositing if different from initial state
0154         if( GDK_IS_WINDOW( window ) && !gdk_window_is_destroyed(window) && gdk_window_get_composited( window ) != _initiallyComposited )
0155         { gdk_window_set_composited( window, _initiallyComposited ); }
0156 
0157         #endif
0158     }
0159 
0160     //____________________________________________________________________________________________
0161     gboolean InnerShadowData::childUnrealizeNotifyEvent( GtkWidget* widget, gpointer data )
0162     {
0163         #if OXYGEN_DEBUG
0164         std::cerr
0165             << "Oxygen::InnerShadowData::childUnrealizeNotifyEvent -"
0166             << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
0167             << std::endl;
0168         #endif
0169         static_cast<InnerShadowData*>(data)->unregisterChild( widget );
0170         return FALSE;
0171     }
0172 
0173     //_________________________________________________________________________________________
0174     gboolean InnerShadowData::targetExposeEvent( GtkWidget* widget, GdkEventExpose* event, gpointer )
0175     {
0176 
0177         #if ENABLE_INNER_SHADOWS_HACK
0178         GtkWidget* child=gtk_bin_get_child(GTK_BIN(widget));
0179         GdkWindow* window=gtk_widget_get_window(child);
0180 
0181         #if OXYGEN_DEBUG
0182         std::cerr << "Oxygen::InnerShadowData::targetExposeEvent -"
0183             << " widget: " << widget << " (" << G_OBJECT_TYPE_NAME(widget) << ")"
0184             << " child: " << child << " (" << G_OBJECT_TYPE_NAME(child) << ")"
0185             << " path: " << Gtk::gtk_widget_path( child )
0186             << " area: " << event->area
0187             << std::endl;
0188         #endif
0189 
0190         if(!gdk_window_get_composited(window))
0191         {
0192             #if OXYGEN_DEBUG
0193             std::cerr << "Oxygen::InnerShadowData::targetExposeEvent - Window isn't composite. Doing nohing\n";
0194             #endif
0195             return FALSE;
0196         }
0197 
0198         // make sure the child window doesn't contain garbage
0199         gdk_window_process_updates(window,TRUE);
0200 
0201         // get window geometry
0202         GtkAllocation allocation( Gtk::gdk_rectangle() );
0203         gdk_window_get_geometry( window, &allocation.x, &allocation.y, &allocation.width, &allocation.height, 0L );
0204 
0205         // create context with clipping
0206         Cairo::Context context(gtk_widget_get_window(widget), &allocation );
0207 
0208         // add event region
0209         gdk_cairo_region(context,event->region);
0210         cairo_clip(context);
0211 
0212         // draw child
0213         gdk_cairo_set_source_window( context, window, allocation.x, allocation.y );
0214         cairo_paint(context);
0215 
0216         #if OXYGEN_DEBUG_INNERSHADOWS
0217         // Show updated parts in random color
0218         cairo_rectangle(context,allocation.x,allocation.y,allocation.width,allocation.height);
0219         double red=((double)rand())/RAND_MAX;
0220         double green=((double)rand())/RAND_MAX;
0221         double blue=((double)rand())/RAND_MAX;
0222         cairo_set_source_rgba(context,red,green,blue,0.5);
0223         cairo_fill(context);
0224         #endif
0225 
0226         // Render rounded combobox list child
0227         if(Gtk::gtk_combobox_is_tree_view( child ))
0228         {
0229             StyleOptions options(widget,gtk_widget_get_state(widget));
0230             Corners corners(CornersAll);
0231             if(gtk_widget_get_visible(gtk_scrolled_window_get_vscrollbar(GTK_SCROLLED_WINDOW(widget))))
0232             {
0233                 if(Gtk::gtk_widget_layout_is_reversed( widget ))
0234                     corners &= ~CornersLeft;
0235                 else
0236                     corners &= ~CornersRight;
0237             }
0238             if(gtk_widget_get_visible(gtk_scrolled_window_get_hscrollbar(GTK_SCROLLED_WINDOW(widget))))
0239                 corners &= ~CornersBottom;
0240 
0241             int x(allocation.x),y(allocation.y),w(allocation.width),h(allocation.height);
0242             cairo_rectangle(context,x,y,w,h);
0243             if(!Gtk::gdk_default_screen_is_composited())
0244             {
0245                 // Take ugly shadow into account
0246                 x+=1;
0247                 y+=1;
0248                 w-=2;
0249                 h-=2;
0250             }
0251             cairo_rounded_rectangle_negative(context,x,y,w,h,2,corners);
0252             cairo_clip(context);
0253 
0254             Style::instance().renderMenuBackground( gtk_widget_get_window(widget), context, allocation.x,allocation.y,allocation.width,allocation.height, options );
0255 
0256             // Event handling finished, now let the event propagate
0257             return FALSE;
0258         }
0259 
0260         // draw the shadow
0261         /*
0262         TODO: here child widget's allocation is used instead of window geometry.
0263         I think this is the correct thing to do (unlike above), but this is to be double check
0264         */
0265         allocation = Gtk::gtk_widget_get_allocation( child );
0266         int basicOffset=2;
0267 
0268         // we only draw SHADOW_IN here
0269         if( gtk_scrolled_window_get_shadow_type( GTK_SCROLLED_WINDOW( widget ) ) != GTK_SHADOW_IN )
0270         {
0271             if( GTK_IS_VIEWPORT(child) && gtk_viewport_get_shadow_type(GTK_VIEWPORT(child)) == GTK_SHADOW_IN )
0272             {
0273 
0274                 basicOffset=0;
0275 
0276             } else {
0277 
0278                 // FIXME: do we need this special case?
0279                 // special_case {
0280                 // we still want to draw shadow on GtkFrames with shadow containing GtkScrolledWindow without shadow
0281                 GtkWidget* box=gtk_widget_get_parent(widget);
0282                 GtkWidget* frame=0;
0283                 if(GTK_IS_BOX(box) && GTK_IS_FRAME(frame=gtk_widget_get_parent(box)) &&
0284                        gtk_frame_get_shadow_type(GTK_FRAME(frame))==GTK_SHADOW_IN)
0285                 {
0286                     #if OXYGEN_DEBUG
0287                     std::cerr << "Oxygen::InnerShadowData::targetExposeEvent: Box children: " << GTK_CONTAINER(box) << std::endl;
0288                     #endif
0289                     // make sure GtkScrolledWindow is the only visible child
0290                     GList* children=gtk_container_get_children(GTK_CONTAINER(box));
0291                     for(GList* child=g_list_first(children); child; child=g_list_next(child))
0292                     {
0293                         GtkWidget* childWidget(GTK_WIDGET(child->data));
0294                         if(gtk_widget_get_visible(childWidget) && !GTK_IS_SCROLLED_WINDOW(childWidget))
0295                         {
0296                             g_list_free(children);
0297                             return FALSE;
0298                         }
0299                     }
0300                     int frameX, frameY;
0301                     GtkAllocation frameAlloc;
0302                     if(gtk_widget_translate_coordinates(frame,widget,0,0,&frameX,&frameY))
0303                     {
0304                         #if OXYGEN_DEBUG
0305                         std::cerr << "coords translation: x=" << frameX << "; y=" << frameY << std::endl;
0306                         #endif
0307                         gtk_widget_get_allocation(frame,&frameAlloc);
0308                         allocation.x+=frameX;
0309                         allocation.y+=frameY;
0310                         allocation.width=frameAlloc.width;
0311                         allocation.height=frameAlloc.height;
0312                         basicOffset=0;
0313                     }
0314 
0315                 } else {
0316 
0317                     #if OXYGEN_DEBUG
0318                     std::cerr << "Oxygen::InnerShadowData::targetExposeEvent - Shadow type isn't GTK_SHADOW_IN, so not drawing the shadow in expose-event handler\n";
0319                     #endif
0320                     return FALSE;
0321 
0322                 }
0323 
0324             }
0325         }
0326 
0327         StyleOptions options(widget,gtk_widget_get_state(widget));
0328         options|=NoFill;
0329         options &= ~(Hover|Focus);
0330         if( Style::instance().animations().scrolledWindowEngine().contains( widget ) )
0331         {
0332             if( Style::instance().animations().scrolledWindowEngine().focused( widget ) ) options |= Focus;
0333             if( Style::instance().animations().scrolledWindowEngine().hovered( widget ) ) options |= Hover;
0334         }
0335 
0336         const AnimationData data( Style::instance().animations().widgetStateEngine().get( widget, options, AnimationHover|AnimationFocus, AnimationFocus ) );
0337 
0338         int offsetX=basicOffset+Entry_SideMargin;
0339         int offsetY=basicOffset;
0340 
0341         // clipRect
0342         GdkRectangle clipRect( allocation );
0343 
0344         // hole background
0345         Style::instance().renderHoleBackground(
0346             gtk_widget_get_window(widget), widget, &clipRect,
0347             allocation.x-offsetX, allocation.y-offsetY, allocation.width+offsetX*2, allocation.height+offsetY*2 );
0348 
0349         // adjust offset and render hole
0350         offsetX -= Entry_SideMargin;
0351         Style::instance().renderHole(
0352             gtk_widget_get_window(widget), &clipRect,
0353             allocation.x-offsetX, allocation.y-offsetY, allocation.width+offsetX*2, allocation.height+offsetY*2,
0354             options, data );
0355 
0356         #endif // enable inner shadows hack
0357 
0358         // let the event propagate
0359         return FALSE;
0360     }
0361 
0362 }