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