1 /*
2 * this file is part of the oxygen gtk engine
3 * Copyright (c) 2011 Hugo Pereira Da Costa <hugo.pereira@free.fr>
4 *
5 * This  library is free  software; you can  redistribute it and/or
6 * modify it  under  the terms  of the  GNU Lesser  General  Public
7 * License  as published  by the Free  Software  Foundation; either
8 * version 2 of the License, or( at your option ) any later version.
9 *
10 * This library is distributed  in the hope that it will be useful,
11 * but  WITHOUT ANY WARRANTY; without even  the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License  along  with  this library;  if not,  write to  the Free
17 * Software Foundation, Inc., 51  Franklin St, Fifth Floor, Boston,
18 * MA 02110-1301, USA.
19 */
20 
21 #include "oxygencairocontext.h"
22 #include "oxygencairoutils.h"
23 #include "config.h"
24 #include "oxygengtkutils.h"
25 #include "oxygenmetrics.h"
26 #include "oxygenrgba.h"
27 #include "oxygenshadowhelper.h"
28 
29 #include "config.h"
30 
31 #include <iostream>
32 #include <cairo/cairo.h>
33 
34 #ifdef GDK_WINDOWING_X11
35 #include <cairo/cairo-xlib.h>
36 #include <gdk/gdkx.h>
37 #include <X11/Xatom.h>
38 #endif
39 
40 namespace Oxygen
41 {
42 
43     //______________________________________________
ShadowHelper(void)44     ShadowHelper::ShadowHelper( void ):
45         _size(0),
46         _hooksInitialized( false )
47     {
48 
49         #ifdef GDK_WINDOWING_X11
50         _atom = None;
51         #endif
52 
53         #if OXYGEN_DEBUG
54         std::cerr << "Oxygen::ShadowHelper::ShadowHelper" << std::endl;
55         #endif
56     }
57 
58     //______________________________________________
~ShadowHelper(void)59     ShadowHelper::~ShadowHelper( void )
60     {
61         #if OXYGEN_DEBUG
62         std::cerr << "Oxygen::ShadowHelper::~ShadowHelper" << std::endl;
63         #endif
64 
65         for( WidgetMap::iterator iter = _widgets.begin(); iter != _widgets.end(); ++iter )
66         { iter->second._destroyId.disconnect(); }
67 
68         reset();
69         _realizeHook.disconnect();
70     }
71 
72     //______________________________________________
reset(void)73     void ShadowHelper::reset( void )
74     {
75 
76         #if OXYGEN_DEBUG
77         std::cerr << "Oxygen::ShadowHelper::reset" << std::endl;
78         #endif
79 
80         #ifdef GDK_WINDOWING_X11
81         GdkScreen* screen = gdk_screen_get_default();
82         if( !screen ) return;
83 
84         Display* display( GDK_DISPLAY_XDISPLAY( gdk_screen_get_display( screen ) ) );
85 
86         // round pixmaps
87         for( PixmapList::const_iterator iter = _roundPixmaps.begin(); iter != _roundPixmaps.end(); ++iter )
88         { XFreePixmap(display, *iter); }
89 
90         // square pixmaps
91         for( PixmapList::const_iterator iter = _squarePixmaps.begin(); iter != _squarePixmaps.end(); ++iter )
92         { XFreePixmap(display, *iter); }
93         #endif
94 
95         // clear arrays
96         _roundPixmaps.clear();
97         _squarePixmaps.clear();
98 
99         // reset size
100         _size = 0;
101 
102     }
103 
104     //______________________________________________
initializeHooks(void)105     void ShadowHelper::initializeHooks( void )
106     {
107        if( _hooksInitialized ) return;
108 
109         #if OXYGEN_DEBUG
110         std::cerr << "Oxygen::ShadowHelper::initializeHooks" << std::endl;
111         #endif
112 
113         // install hooks
114         _realizeHook.connect( "realize", (GSignalEmissionHook)realizeHook, this );
115         _hooksInitialized = true;
116 
117     }
118 
119     //______________________________________________
initialize(const ColorUtils::Rgba & color,const WindowShadow & shadow)120     void ShadowHelper::initialize( const ColorUtils::Rgba& color, const WindowShadow& shadow )
121     {
122 
123         #if OXYGEN_DEBUG
124         std::cerr << "Oxygen::ShadowHelper::initialize" << std::endl;
125         #endif
126 
127         reset();
128         _size = int(shadow.shadowSize()) - WindowShadow::Overlap;
129 
130         // round tiles
131         WindowShadowKey key;
132         key.hasTopBorder = true;
133         key.hasBottomBorder = true;
134         _roundTiles = shadow.tileSet( color, key );
135 
136         // square tiles
137         key.hasTopBorder = false;
138         key.hasBottomBorder = false;
139         _squareTiles = shadow.tileSet( color, key );
140 
141         // re-install shadows for all windowId
142         for( WidgetMap::const_iterator iter = _widgets.begin(); iter != _widgets.end(); ++iter )
143         { installX11Shadows( iter->first ); }
144 
145     }
146 
147     //______________________________________________
registerWidget(GtkWidget * widget)148     bool ShadowHelper::registerWidget( GtkWidget* widget )
149     {
150 
151         // check widget
152         if( !( widget && GTK_IS_WINDOW( widget ) ) ) return false;
153 
154         // make sure that widget is not already registered
155         if( _widgets.find( widget ) != _widgets.end() ) return false;
156 
157         // check if window is accepted
158         if( !acceptWidget( widget ) ) return false;
159 
160         // try install shadows
161         installX11Shadows( widget );
162 
163         // register in map and returns success
164         WidgetData data;
165         data._destroyId.connect( G_OBJECT( widget ), "destroy", G_CALLBACK( destroyNotifyEvent ), this );
166         _widgets.insert( std::make_pair( widget, data ) );
167 
168         return true;
169 
170     }
171 
172     //______________________________________________
unregisterWidget(GtkWidget * widget)173     void ShadowHelper::unregisterWidget( GtkWidget* widget )
174     {
175         // find matching data in map
176         WidgetMap::iterator iter( _widgets.find( widget ) );
177         if( iter == _widgets.end() ) return;
178 
179         // disconnect
180         iter->second._destroyId.disconnect();
181 
182         // remove from map
183         _widgets.erase( iter );
184     }
185 
186     //______________________________________________
isMenu(GtkWidget * widget) const187     bool ShadowHelper::isMenu( GtkWidget* widget ) const
188     {
189         if( !( widget && GTK_IS_WINDOW( widget ) ) ) return false;
190         const GdkWindowTypeHint hint( gtk_window_get_type_hint( GTK_WINDOW( widget ) ) );
191         return
192             hint == GDK_WINDOW_TYPE_HINT_MENU ||
193             hint == GDK_WINDOW_TYPE_HINT_DROPDOWN_MENU ||
194             hint == GDK_WINDOW_TYPE_HINT_POPUP_MENU;
195     }
196 
197     //______________________________________________
isToolTip(GtkWidget * widget) const198     bool ShadowHelper::isToolTip( GtkWidget* widget ) const
199     {
200         if( !( widget && GTK_IS_WINDOW( widget ) ) ) return false;
201         const GdkWindowTypeHint hint( gtk_window_get_type_hint( GTK_WINDOW( widget ) ) );
202         return hint == GDK_WINDOW_TYPE_HINT_TOOLTIP;
203     }
204 
205     //______________________________________________
acceptWidget(GtkWidget * widget) const206     bool ShadowHelper::acceptWidget( GtkWidget* widget ) const
207     {
208 
209         // check widget and type
210         if( !( widget && GTK_IS_WINDOW( widget ) ) ) return false;
211 
212         // for openoffice, accept all non decorated windows
213         if( _applicationName.isOpenOffice() ) return true;
214 
215         // otherwise check window hint
216         const GdkWindowTypeHint hint( gtk_window_get_type_hint( GTK_WINDOW( widget ) ) );
217         return
218             hint == GDK_WINDOW_TYPE_HINT_MENU ||
219             hint == GDK_WINDOW_TYPE_HINT_DROPDOWN_MENU ||
220             hint == GDK_WINDOW_TYPE_HINT_POPUP_MENU ||
221             hint == GDK_WINDOW_TYPE_HINT_COMBO ||
222             hint == GDK_WINDOW_TYPE_HINT_TOOLTIP;
223     }
224 
225     //______________________________________________
createPixmapHandles(void)226     void ShadowHelper::createPixmapHandles( void )
227     {
228 
229         #if OXYGEN_DEBUG
230         std::cerr << "Oxygen::ShadowHelper::createPixmapHandles" << std::endl;
231         #endif
232 
233         #ifdef GDK_WINDOWING_X11
234         // create atom
235         if( !_atom )
236         {
237 
238             // get screen and check
239             GdkScreen* screen = gdk_screen_get_default();
240             if( !screen )
241             {
242 
243                 #if OXYGEN_DEBUG
244                 std::cerr << "ShadowHelper::createPixmapHandles - screen is NULL" << std::endl;
245                 #endif
246 
247                 return;
248             }
249 
250             // get display and check
251             Display* display( GDK_DISPLAY_XDISPLAY( gdk_screen_get_display( screen ) ) );
252             if( !display )
253             {
254 
255                 #if OXYGEN_DEBUG
256                 std::cerr << "ShadowHelper::createPixmapHandles - display is NULL" << std::endl;
257                 #endif
258 
259                 return;
260             }
261 
262            _atom = XInternAtom( display, "_KDE_NET_WM_SHADOW", False);
263         }
264 
265         // make sure size is valid
266         if( _size <= 0 ) return;
267 
268         // opacity
269         const int shadowOpacity = 150;
270 
271         if( _roundPixmaps.empty() || _squarePixmaps.empty() )
272         {
273             // get screen, display, visual and check
274             // no need to check screen and display, since was already done for ATOM
275             GdkScreen* screen = gdk_screen_get_default();
276             if( !gdk_screen_get_rgba_visual( screen ) )
277             {
278 
279                 #if OXYGEN_DEBUG
280                 std::cerr << "ShadowHelper::createPixmapHandles - no valid RGBA visual found." << std::endl;
281                 #endif
282 
283                 return;
284 
285             }
286         }
287 
288         // make sure pixmaps are not already initialized
289         if( _roundPixmaps.empty() )
290         {
291 
292             _roundPixmaps.push_back( createPixmap( _roundTiles.surface( 1 ), shadowOpacity ) );
293             _roundPixmaps.push_back( createPixmap( _roundTiles.surface( 2 ), shadowOpacity ) );
294             _roundPixmaps.push_back( createPixmap( _roundTiles.surface( 5 ), shadowOpacity ) );
295             _roundPixmaps.push_back( createPixmap( _roundTiles.surface( 8 ), shadowOpacity ) );
296             _roundPixmaps.push_back( createPixmap( _roundTiles.surface( 7 ), shadowOpacity ) );
297             _roundPixmaps.push_back( createPixmap( _roundTiles.surface( 6 ), shadowOpacity ) );
298             _roundPixmaps.push_back( createPixmap( _roundTiles.surface( 3 ), shadowOpacity ) );
299             _roundPixmaps.push_back( createPixmap( _roundTiles.surface( 0 ), shadowOpacity ) );
300 
301         }
302 
303         if( _squarePixmaps.empty() )
304         {
305 
306             _squarePixmaps.push_back( createPixmap( _squareTiles.surface( 1 ), shadowOpacity ) );
307             _squarePixmaps.push_back( createPixmap( _squareTiles.surface( 2 ), shadowOpacity ) );
308             _squarePixmaps.push_back( createPixmap( _squareTiles.surface( 5 ), shadowOpacity ) );
309             _squarePixmaps.push_back( createPixmap( _squareTiles.surface( 8 ), shadowOpacity ) );
310             _squarePixmaps.push_back( createPixmap( _squareTiles.surface( 7 ), shadowOpacity ) );
311             _squarePixmaps.push_back( createPixmap( _squareTiles.surface( 6 ), shadowOpacity ) );
312             _squarePixmaps.push_back( createPixmap( _squareTiles.surface( 3 ), shadowOpacity ) );
313             _squarePixmaps.push_back( createPixmap( _squareTiles.surface( 0 ), shadowOpacity ) );
314 
315         }
316 
317         #endif
318 
319     }
320 
321     //______________________________________________
322     #ifdef GDK_WINDOWING_X11
createPixmap(const Cairo::Surface & surface,int opacity) const323     Pixmap ShadowHelper::createPixmap( const Cairo::Surface& surface, int opacity ) const
324     {
325         assert( surface.isValid() );
326         int width(0);
327         int height(0);
328         cairo_surface_get_size( surface, width, height );
329 
330         GdkScreen* screen = gdk_screen_get_default();
331         Display* display( GDK_DISPLAY_XDISPLAY( gdk_screen_get_display( screen ) ) );
332         Window root( GDK_WINDOW_XID( gdk_screen_get_root_window( screen ) ) );
333         Pixmap pixmap = XCreatePixmap( display, root, width, height, 32 );
334 
335         // create surface for pixmap
336         {
337             Cairo::Surface dest( cairo_xlib_surface_create( display, pixmap, GDK_VISUAL_XVISUAL( gdk_screen_get_rgba_visual( screen ) ), width, height ) );
338             Cairo::Context context( dest );
339             cairo_set_operator( context, CAIRO_OPERATOR_SOURCE );
340 
341             cairo_rectangle( context, 0, 0, width, height );
342             cairo_set_source_surface( context, surface, 0, 0 );
343             cairo_fill( context );
344 
345             if( opacity < 255 )
346             {
347 
348                 cairo_set_operator( context, CAIRO_OPERATOR_DEST_IN );
349                 cairo_set_source( context, ColorUtils::Rgba( 0, 0, 0, double(opacity)/255 ) );
350                 cairo_rectangle( context, 0, 0, width, height );
351                 cairo_fill( context );
352 
353             }
354 
355         }
356 
357         return pixmap;
358 
359     }
360     #endif
361 
362     //______________________________________________
installX11Shadows(GtkWidget * widget)363     void ShadowHelper::installX11Shadows( GtkWidget* widget )
364     {
365 
366         #ifdef GDK_WINDOWING_X11
367 
368         #if OXYGEN_DEBUG
369         std::cerr
370             << "Oxygen::ShadowHelper::installX11Shadows - "
371             << " widget: " << widget
372             << " wid: " << GDK_WINDOW_XID( gtk_widget_get_window( widget ) )
373             << std::endl;
374         #endif
375 
376         // check widget
377         if( !GTK_IS_WIDGET( widget ) ) return;
378 
379         // make sure handles and atom are defined
380         createPixmapHandles();
381 
382         GdkWindow  *window = gtk_widget_get_window( widget );
383         GdkDisplay *display = gtk_widget_get_display( widget );
384 
385         std::vector<unsigned long> data;
386         const bool isMenu( this->isMenu( widget ) );
387         const bool isToolTip( this->isToolTip( widget ) );
388         if( _applicationName.isOpenOffice() || ( (isMenu||isToolTip) && _applicationName.isXul( widget ) ) )
389         {
390 
391             data = _squarePixmaps;
392             data.push_back( _size );
393             data.push_back( _size );
394             data.push_back( _size );
395             data.push_back( _size );
396 
397         } else {
398 
399             data = _roundPixmaps;
400             if( isMenu )
401             {
402 
403                 /*
404                 for menus, need to shrink top and bottom shadow size, since body is done likely with respect to real size
405                 in painting method (Oxygen::Style::renderMenuBackground)
406                 */
407                 data.push_back( _size - Menu_VerticalOffset );
408                 data.push_back( _size );
409                 data.push_back( _size - Menu_VerticalOffset );
410                 data.push_back( _size );
411 
412             } else {
413 
414                 // all sides have same sizz
415                 data.push_back( _size );
416                 data.push_back( _size );
417                 data.push_back( _size );
418                 data.push_back( _size );
419 
420             }
421 
422         }
423 
424         // change property
425         XChangeProperty(
426             GDK_DISPLAY_XDISPLAY( display ), GDK_WINDOW_XID(window), _atom, XA_CARDINAL, 32, PropModeReplace,
427             reinterpret_cast<const unsigned char *>(&data[0]), data.size() );
428 
429         #endif
430 
431     }
432 
433     //_______________________________________________________
uninstallX11Shadows(GtkWidget * widget) const434     void ShadowHelper::uninstallX11Shadows( GtkWidget* widget ) const
435     {
436 
437         if( !GTK_IS_WIDGET( widget ) ) return;
438 
439         #ifdef GDK_WINDOWING_X11
440         GdkWindow  *window = gtk_widget_get_window( widget );
441         GdkDisplay *display = gtk_widget_get_display( widget );
442         XDeleteProperty( GDK_DISPLAY_XDISPLAY( display ), GDK_WINDOW_XID(window), _atom);
443         #endif
444 
445     }
446 
447     //_______________________________________________________
realizeHook(GSignalInvocationHint *,guint,const GValue * params,gpointer data)448     gboolean ShadowHelper::realizeHook( GSignalInvocationHint*, guint, const GValue* params, gpointer data )
449     {
450 
451         // get widget from params
452         GtkWidget* widget( GTK_WIDGET( g_value_get_object( params ) ) );
453 
454         // check type
455         if( !GTK_IS_WIDGET( widget ) ) return FALSE;
456         static_cast<ShadowHelper*>(data)->registerWidget( widget );
457         return TRUE;
458     }
459 
460     //____________________________________________________________________________________________
destroyNotifyEvent(GtkWidget * widget,gpointer data)461     gboolean ShadowHelper::destroyNotifyEvent( GtkWidget* widget, gpointer data )
462     {
463         static_cast<ShadowHelper*>(data)->unregisterWidget( widget );
464         return FALSE;
465     }
466 
467 }
468