1 /*
2 * this file is part of the oxygen gtk engine
3 * Copyright (c) 2010 Hugo Pereira Da Costa <hugo.pereira@free.fr>
4 * Copyright (c) 2010 Ruslan Kabatsayev <b7.10110111@gmail.com>
5 *
6 * This  library is free  software; you can  redistribute it and/or
7 * modify it  under  the terms  of the  GNU Lesser  General  Public
8 * License  as published  by the Free  Software  Foundation; either
9 * version 2 of the License, or(at your option ) any later version.
10 *
11 * This library is distributed  in the hope that it will be useful,
12 * but  WITHOUT ANY WARRANTY; without even  the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License  along  with  this library;  if not,  write to  the Free
18 * Software Foundation, Inc., 51  Franklin St, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 */
21 
22 #include <gtk/gtk.h>
23 #include "oxygeninnershadowdata.h"
24 #include "../oxygengtkutils.h"
25 #include "../config.h"
26 #include "../oxygencairocontext.h"
27 #include "../oxygencairoutils.h"
28 #include "oxygenanimations.h"
29 #include "../oxygenstyle.h"
30 #include "../oxygenmetrics.h"
31 #include <stdlib.h>
32 
33 #include <cassert>
34 #include <iostream>
35 
36 namespace Oxygen
37 {
38 
39     //_____________________________________________
connect(GtkWidget * widget)40     void InnerShadowData::connect( GtkWidget* widget )
41     {
42 
43         assert( GTK_IS_SCROLLED_WINDOW( widget ) );
44         assert( !_target );
45 
46         // store target
47         _target = widget;
48 
49         if( gdk_display_supports_composite( gtk_widget_get_display( widget ) ) )
50         { _exposeId.connect( G_OBJECT(_target), "draw", G_CALLBACK( targetExposeEvent ), this, true ); }
51 
52         // check child
53         GtkWidget* child( gtk_bin_get_child( GTK_BIN( widget ) ) );
54         if( !child ) return;
55 
56         #if OXYGEN_DEBUG
57         std::cerr
58             << "Oxygen::InnerShadowData::connect -"
59             << " widget: " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
60             << " child: " << child << " (" << G_OBJECT_TYPE_NAME( child ) << ")"
61             << std::endl;
62         #endif
63 
64         registerChild( child );
65 
66     }
67 
68     //_____________________________________________
disconnect(GtkWidget *)69     void InnerShadowData::disconnect( GtkWidget* )
70     {
71         _target = 0;
72         for( ChildDataMap::reverse_iterator iter = _childrenData.rbegin(); iter != _childrenData.rend(); ++iter )
73         { iter->second.disconnect( iter->first ); }
74 
75         // disconnect signals
76         _exposeId.disconnect();
77 
78         // clear child data
79         _childrenData.clear();
80     }
81 
82     //_____________________________________________
registerChild(GtkWidget * widget)83     void InnerShadowData::registerChild( GtkWidget* widget )
84     {
85 
86         #if ENABLE_INNER_SHADOWS_HACK
87 
88         // check widget
89         if( !GTK_IS_WIDGET( widget ) ) return;
90 
91         // make sure widget is not already in map
92         if( _childrenData.find( widget ) != _childrenData.end() ) return;
93 
94         if( gtk_scrolled_window_get_shadow_type( GTK_SCROLLED_WINDOW( _target ) ) != GTK_SHADOW_IN )
95         { return; }
96 
97         #if OXYGEN_DEBUG
98         std::cerr
99             << "Oxygen::InnerShadowData::registerChild -"
100             << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
101             << std::endl;
102         #endif
103 
104         GdkWindow* window(gtk_widget_get_window(widget));
105         if(
106             window && gdk_window_get_window_type( window ) == GDK_WINDOW_CHILD &&
107             gdk_display_supports_composite( gtk_widget_get_display( widget ) ) )
108         {
109             ChildData data;
110             data._unrealizeId.connect( G_OBJECT(widget), "unrealize", G_CALLBACK( childUnrealizeNotifyEvent ), this );
111             data._initiallyComposited = gdk_window_get_composited(window);
112             gdk_window_set_composited(window,TRUE);
113             _childrenData.insert( std::make_pair( widget, data ) );
114         }
115 
116         #endif
117 
118     }
119 
120     //________________________________________________________________________________
unregisterChild(GtkWidget * widget)121     void InnerShadowData::unregisterChild( GtkWidget* widget )
122     {
123         #if ENABLE_INNER_SHADOWS_HACK
124 
125         ChildDataMap::iterator iter( _childrenData.find( widget ) );
126         if( iter == _childrenData.end() ) return;
127 
128         #if OXYGEN_DEBUG
129         std::cerr
130             << "Oxygen::InnerShadowData::unregisterChild -"
131             << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
132             << std::endl;
133         #endif
134 
135         iter->second.disconnect( widget );
136         _childrenData.erase( iter );
137 
138         #endif
139     }
140 
141     //________________________________________________________________________________
disconnect(GtkWidget * widget)142     void InnerShadowData::ChildData::disconnect( GtkWidget* widget )
143     {
144         #if ENABLE_INNER_SHADOWS_HACK
145 
146         // disconnect signals
147         _unrealizeId.disconnect();
148 
149         // remove compositing flag
150         GdkWindow* window( gtk_widget_get_window( widget ) );
151 
152         #if OXYGEN_DEBUG
153         std::cerr
154             << "Oxygen::InnerShadowData::ChildData::disconnect -"
155             << " widget: " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
156             << " window: " << window
157             << std::endl;
158         #endif
159 
160         // restore compositing if different from initial state
161         if( GDK_IS_WINDOW( window ) && !gdk_window_is_destroyed(window) && gdk_window_get_composited( window ) != _initiallyComposited )
162         { gdk_window_set_composited( window, _initiallyComposited ); }
163 
164         #endif
165     }
166 
167     //____________________________________________________________________________________________
childUnrealizeNotifyEvent(GtkWidget * widget,gpointer data)168     gboolean InnerShadowData::childUnrealizeNotifyEvent( GtkWidget* widget, gpointer data )
169     {
170         #if OXYGEN_DEBUG
171         std::cerr
172             << "Oxygen::InnerShadowData::childUnrealizeNotifyEvent -"
173             << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
174             << std::endl;
175         #endif
176         static_cast<InnerShadowData*>(data)->unregisterChild( widget );
177         return FALSE;
178     }
179 
180     //_________________________________________________________________________________________
targetExposeEvent(GtkWidget * widget,cairo_t * context,gpointer)181     gboolean InnerShadowData::targetExposeEvent( GtkWidget* widget, cairo_t *context, gpointer )
182     {
183 
184         #if ENABLE_INNER_SHADOWS_HACK
185         GtkWidget* child( gtk_bin_get_child( GTK_BIN( widget ) ) );
186         GdkWindow* childWindow( gtk_widget_get_window( child ) );
187 
188         #if OXYGEN_DEBUG
189         std::cerr << "Oxygen::InnerShadowData::targetExposeEvent -"
190             << " widget: " << widget << " (" << G_OBJECT_TYPE_NAME(widget) << ")"
191             << " child: " << child << " (" << G_OBJECT_TYPE_NAME(child) << ")"
192             << " path: " << Gtk::gtk_widget_path( child )
193             << std::endl;
194         #endif
195 
196         if( !gdk_window_get_composited( childWindow ) )
197         {
198             #if OXYGEN_DEBUG
199             std::cerr << "Oxygen::InnerShadowData::targetExposeEvent - Window isn't composite. Doing nohing\n";
200             #endif
201             return FALSE;
202         }
203 
204         // make sure the child window doesn't contain garbage
205         gdk_window_process_updates( childWindow, TRUE );
206 
207         GtkAllocation allocation;
208         gtk_widget_translate_coordinates (child, widget, 0, 0, &allocation.x, &allocation.y);
209         allocation.width = gdk_window_get_width (childWindow);
210         allocation.height = gdk_window_get_height (childWindow);
211 
212         // draw child
213         gdk_cairo_rectangle( context, &allocation );
214         cairo_clip(context);
215         gdk_cairo_set_source_window( context, childWindow, allocation.x, allocation.y );
216         cairo_paint(context);
217 
218         #if OXYGEN_DEBUG_INNERSHADOWS
219         // Show updated parts in random color
220         cairo_rectangle( context, allocation.x, allocation.y, allocation.width, allocation.height );
221         double red= ((double)rand())/RAND_MAX;
222         double green=((double)rand())/RAND_MAX;
223         double blue=((double)rand())/RAND_MAX;
224         cairo_set_source_rgba(context,red,green,blue,0.5);
225         cairo_fill(context);
226         #endif
227 
228         // Render rounded combobox list child
229         if(Gtk::gtk_combobox_is_tree_view( child ))
230         {
231             StyleOptions options( widget, gtk_widget_get_state_flags( widget ) );
232             Corners corners(CornersAll);
233 
234             // check scrollbar visibility to decide corners
235             GtkScrolledWindow* scrolledWindow( GTK_SCROLLED_WINDOW( widget ) );
236             if( gtk_widget_get_mapped( gtk_scrolled_window_get_vscrollbar( scrolledWindow ) ) )
237             {
238                 if(Gtk::gtk_widget_layout_is_reversed( widget )) corners &= ~CornersLeft;
239                 else corners &= ~CornersRight;
240             }
241 
242             if( gtk_widget_get_mapped( gtk_scrolled_window_get_hscrollbar( scrolledWindow ) ) )
243             { corners &= ~CornersBottom; }
244 
245             int x(allocation.x),y(allocation.y),w(allocation.width),h(allocation.height);
246             cairo_rectangle(context,x,y,w,h);
247             if(!Gtk::gdk_default_screen_is_composited())
248             {
249                 // Take ugly shadow into account
250                 x+=1;
251                 y+=1;
252                 w-=2;
253                 h-=2;
254             }
255             cairo_rounded_rectangle_negative(context,x,y,w,h,2,corners);
256             cairo_clip(context);
257 
258             Style::instance().renderMenuBackground( context, allocation.x,allocation.y,allocation.width,allocation.height, options );
259 
260             // Event handling finished, now let the event propagate
261             return FALSE;
262         }
263 
264         // we only draw SHADOW_IN here
265         if( gtk_scrolled_window_get_shadow_type( GTK_SCROLLED_WINDOW( widget ) ) != GTK_SHADOW_IN )
266         {
267             #if OXYGEN_DEBUG
268             std::cerr << "Oxygen::InnerShadowData::targetExposeEvent - Shadow type isn't GTK_SHADOW_IN, so not drawing the shadow in expose-event handler\n";
269             #endif
270             return FALSE;
271 
272         }
273 
274         // draw the shadow
275         int basicOffset=2;
276 
277         StyleOptions options( widget, gtk_widget_get_state_flags( widget ) );
278         options|=NoFill;
279         options &= ~(Hover|Focus);
280         if( Style::instance().animations().scrolledWindowEngine().contains( widget ) )
281         {
282             if( Style::instance().animations().scrolledWindowEngine().focused( widget ) ) options |= Focus;
283             if( Style::instance().animations().scrolledWindowEngine().hovered( widget ) ) options |= Hover;
284         }
285 
286         const AnimationData data( Style::instance().animations().widgetStateEngine().get( widget, options, AnimationHover|AnimationFocus, AnimationFocus ) );
287 
288         int offsetX = basicOffset+Entry_SideMargin;
289         int offsetY = basicOffset;
290 
291         // also need to correct from widget's margins
292         #if GTK_CHECK_VERSION( 3, 11, 0 )
293         const int marginX = gtk_widget_get_margin_start( child );
294         const int marginW = marginX + gtk_widget_get_margin_end( child );
295         #else
296         const int marginX = gtk_widget_get_margin_left( child );
297         const int marginW = marginX + gtk_widget_get_margin_right( child );
298         #endif
299 
300         const int marginY = gtk_widget_get_margin_top( child );
301         const int marginH = marginY + gtk_widget_get_margin_bottom( child );
302 
303         // hole background
304         Style::instance().renderHoleBackground( context,
305             gtk_widget_get_window(widget), widget,
306             allocation.x - offsetX - marginX,
307             allocation.y - offsetY - marginY,
308             allocation.width + offsetX*2 + marginW,
309             allocation.height + offsetY*2 + marginH );
310 
311         // adjust offset and render hole
312         offsetX -= Entry_SideMargin;
313         Style::instance().renderHole( context,
314             allocation.x - offsetX - marginX,
315             allocation.y - offsetY - marginY,
316             allocation.width + offsetX*2 + marginW,
317             allocation.height + offsetY*2 + marginH,
318             options, data );
319 
320         #endif // enable inner shadows hack
321 
322         // let the event propagate
323         return FALSE;
324     }
325 
326 }
327