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 * MenuBarState prelight effect is based on 7 * Redmond95 - a cairo based GTK+ engine 8 * Copyright (C) 2001 Red Hat, Inc. <@redhat.com> 9 * Copyright (C) 2006 Andrew Johnson <acjgenius@earthlink.net> 10 * Copyright (C) 2006-2007 Benjamin Berg <benjamin@sipsolutions.net> 11 * 12 * the menushell data code is largely inspired from the gtk redmond engine 13 * 14 * This library is free software; you can redistribute it and/or 15 * modify it under the terms of the GNU Lesser General Public 16 * License as published by the Free Software Foundation; either 17 * version 2 of the License, or(at your option ) any later version. 18 * 19 * This library is distributed in the hope that it will be useful, 20 * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 22 * Lesser General Public License for more details. 23 * 24 * You should have received a copy of the GNU Lesser General Public 25 * License along with this library; if not, write to the Free 26 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, 27 * MA 02110-1301, USA. 28 */ 29 30 #include "oxygenmenubarstatedata.h" 31 #include "../oxygengtkutils.h" 32 #include "../config.h" 33 34 #include <gtk/gtk.h> 35 36 namespace Oxygen 37 { 38 39 //________________________________________________________________________________ connect(GtkWidget * widget)40 void MenuBarStateData::connect( GtkWidget* widget ) 41 { 42 43 #if OXYGEN_DEBUG 44 std::cerr 45 << "Oxygen::MenuBarStateData::connect - " 46 << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")" 47 << std::endl; 48 #endif 49 50 _target = widget; 51 _motionId.connect( G_OBJECT(widget), "motion-notify-event", G_CALLBACK( motionNotifyEvent ), this ); 52 _leaveId.connect( G_OBJECT(widget), "leave-notify-event", G_CALLBACK( leaveNotifyEvent ), this ); 53 54 // connect timeLines 55 _current._timeLine.connect( (GSourceFunc)delayedUpdate, this ); 56 _previous._timeLine.connect( (GSourceFunc)delayedUpdate, this ); 57 58 // set directions 59 _current._timeLine.setDirection( TimeLine::Forward ); 60 _previous._timeLine.setDirection( TimeLine::Backward ); 61 62 // follow mouse animation 63 FollowMouseData::connect( (GSourceFunc)followMouseUpdate, this ); 64 65 } 66 67 //________________________________________________________________________________ disconnect(GtkWidget *)68 void MenuBarStateData::disconnect( GtkWidget* ) 69 { 70 71 #if OXYGEN_DEBUG 72 std::cerr 73 << "Oxygen::MenuBarStateData::disconnect - " 74 << " " << _target << " (" << (_target ? G_OBJECT_TYPE_NAME( _target ) : "0x0") << ")" 75 << std::endl; 76 #endif 77 78 _target = 0L; 79 80 // disconnect signal 81 _motionId.disconnect(); 82 _leaveId.disconnect(); 83 84 // disconnect timelines 85 _current._timeLine.disconnect(); 86 _previous._timeLine.disconnect(); 87 88 // disconnect all children 89 for( ChildrenMap::iterator iter = _children.begin(); iter != _children.end(); ++iter ) 90 { iter->second.disconnect(); } 91 92 _children.clear(); 93 94 FollowMouseData::disconnect(); 95 96 } 97 98 99 //________________________________________________________________________________ registerChild(GtkWidget * widget)100 void MenuBarStateData::registerChild( GtkWidget* widget ) 101 { 102 if( widget && _children.find( widget ) == _children.end() ) 103 { 104 105 #if OXYGEN_DEBUG 106 std::cerr 107 << "Oxygen::MenuBarStateData::registerChild -" 108 << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")" 109 << std::endl; 110 #endif 111 112 Signal destroyId; 113 destroyId.connect( G_OBJECT( widget ), "destroy", G_CALLBACK( childDestroyNotifyEvent ), this ); 114 _children.insert( std::make_pair( widget, destroyId ) ); 115 } 116 117 } 118 119 //________________________________________________________________________________ unregisterChild(GtkWidget * widget)120 void MenuBarStateData::unregisterChild( GtkWidget* widget ) 121 { 122 123 #if OXYGEN_DEBUG 124 std::cerr 125 << "Oxygen::MenuBarStateData::unregisterChild -" 126 << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")" 127 << std::endl; 128 #endif 129 130 ChildrenMap::iterator iter( _children.find( widget ) ); 131 132 // erase from children map 133 if( iter != _children.end() ) 134 { 135 iter->second.disconnect(); 136 _children.erase( iter ); 137 } 138 139 // reset corresponding data, if matches 140 if( widget == _previous._widget ) 141 { 142 _previous._widget = 0L; 143 _previous._timeLine.disconnect(); 144 } 145 146 if( widget == _current._widget ) 147 { 148 _current._widget = 0L; 149 _current._timeLine.disconnect(); 150 } 151 152 } 153 154 //________________________________________________________________________________ updateItems(GdkEventType type)155 void MenuBarStateData::updateItems( GdkEventType type ) 156 { 157 158 if( !_target ) return; 159 160 const bool isLeaveEvent( type == GDK_LEAVE_NOTIFY ); 161 162 gint xPointer, yPointer; 163 gdk_window_get_pointer( gtk_widget_get_window( _target ), &xPointer, &yPointer, 0L ); 164 165 bool activeFound( false ); 166 GtkWidget *activeWidget( 0L ); 167 GList *children( gtk_container_get_children( GTK_CONTAINER( _target ) ) ); 168 for( GList* child = g_list_first(children); child; child = g_list_next(child) ) 169 { 170 171 if( !( child->data && GTK_IS_MENU_ITEM( child->data ) ) ) continue; 172 173 GtkWidget* childWidget( GTK_WIDGET( child->data ) ); 174 registerChild( childWidget ); 175 const GtkStateType state( gtk_widget_get_state( childWidget ) ); 176 177 // do nothing for disabled child 178 if( state == GTK_STATE_INSENSITIVE ) continue; 179 180 const GtkAllocation allocation( Gtk::gtk_widget_get_allocation( childWidget ) ); 181 if( Gtk::gdk_rectangle_contains( &allocation, xPointer, yPointer ) ) 182 { 183 184 activeFound = true; 185 if( state != GTK_STATE_PRELIGHT ) 186 { 187 updateState( childWidget, allocation, true ); 188 if( !isLeaveEvent ) gtk_widget_set_state( childWidget, GTK_STATE_PRELIGHT ); 189 } 190 191 } else if( state != GTK_STATE_NORMAL ) { 192 193 activeWidget = childWidget; 194 195 } 196 } 197 198 if( children ) g_list_free( children ); 199 200 // fade-out current 201 if( _current.isValid() && !activeFound && !menuItemIsActive( _current._widget ) ) 202 { updateState( _current._widget, _current._rect, false ); } 203 204 // disable previous active widget, if either another active widget was found, or this one is not active 205 if( activeWidget && (activeFound || !menuItemIsActive( activeWidget ) ) ) 206 { gtk_widget_set_state( activeWidget, GTK_STATE_NORMAL ); } 207 208 return; 209 210 } 211 212 //________________________________________________________________________________ menuItemIsActive(GtkWidget * widget) const213 bool MenuBarStateData::menuItemIsActive( GtkWidget* widget ) const 214 { 215 216 // check argument 217 if( !GTK_IS_MENU_ITEM( widget ) ) return false; 218 219 // check menu 220 GtkWidget* menu( gtk_menu_item_get_submenu( GTK_MENU_ITEM( widget ) ) ); 221 if( !GTK_IS_MENU( menu ) ) return false; 222 223 GtkWidget* topLevel( gtk_widget_get_toplevel( menu ) ); 224 if( !topLevel ) return false; 225 226 return 227 GTK_WIDGET_VISIBLE( menu ) && 228 GTK_WIDGET_REALIZED( topLevel ) && 229 GTK_WIDGET_VISIBLE( topLevel ); 230 } 231 232 //________________________________________________________________________________ updateState(GtkWidget * widget,const GdkRectangle & rect,bool state)233 bool MenuBarStateData::updateState( GtkWidget* widget, const GdkRectangle& rect, bool state ) 234 { 235 236 // do nothing if animations are disabled 237 if( !_animationsEnabled ) return true; 238 239 if( state && widget != _current._widget ) 240 { 241 242 // stop current animation if running 243 if( _current._timeLine.isRunning() ) _current._timeLine.stop(); 244 245 // stop previous animation if running 246 if( _current.isValid() ) 247 { 248 if( _previous._timeLine.isRunning() ) _previous._timeLine.stop(); 249 250 if( _previous.isValid() ) 251 { _dirtyRect = _previous._rect; } 252 253 // move current to previous 254 _previous.copy( _current ); 255 } 256 257 // assign new widget to current and start animation 258 const bool animate( !_current.isValid() ); 259 GdkRectangle startRect( _current._rect ); 260 _current.update( widget, rect ); 261 if( _current.isValid() ) 262 { 263 if( animate ) _current._timeLine.start(); 264 else if( followMouse() ) startAnimation( startRect, _current._rect ); 265 else delayedUpdate( this ); 266 } 267 268 return true; 269 270 } else if( (!state) && widget == _current._widget ) { 271 272 // stop current animation if running 273 if( _current._timeLine.isRunning() ) _current._timeLine.stop(); 274 275 // stop previous animation if running 276 if( _previous._timeLine.isRunning() ) _previous._timeLine.stop(); 277 278 if( _previous.isValid() ) 279 { _dirtyRect = _previous._rect; } 280 281 // move current to previous; clear current, and animate 282 _previous.copy( _current ); 283 _current.clear(); 284 if( _previous.isValid() && gtk_widget_get_state( _previous._widget ) == GTK_STATE_PRELIGHT ) _previous._timeLine.start(); 285 286 return true; 287 288 } else return false; 289 290 } 291 292 //________________________________________________________________________________ dirtyRect(void)293 GdkRectangle MenuBarStateData::dirtyRect( void ) 294 { 295 296 GdkRectangle rect( Gtk::gdk_rectangle() ); 297 Gtk::gdk_rectangle_union( &_previous._rect, &_current._rect, &rect ); 298 299 // add _dirtyRect 300 if( Gtk::gdk_rectangle_is_valid( &_dirtyRect ) ) 301 { 302 Gtk::gdk_rectangle_union( &_dirtyRect, &rect, &rect ); 303 _dirtyRect = Gtk::gdk_rectangle(); 304 } 305 306 // add followMouse dirtyRect 307 if( followMouse() ) 308 { 309 const GdkRectangle followMouseRect( FollowMouseData::dirtyRect() ); 310 Gtk::gdk_rectangle_union( &followMouseRect, &rect, &rect ); 311 } 312 313 return rect; 314 315 } 316 317 //____________________________________________________________________________________________ childDestroyNotifyEvent(GtkWidget * widget,gpointer data)318 gboolean MenuBarStateData::childDestroyNotifyEvent( GtkWidget* widget, gpointer data ) 319 { 320 #if OXYGEN_DEBUG 321 std::cerr 322 << "Oxygen::MenuBarStateData::childDestroyNotifyEvent -" 323 << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")" 324 << std::endl; 325 #endif 326 static_cast<MenuBarStateData*>(data)->unregisterChild( widget ); 327 return FALSE; 328 } 329 330 //________________________________________________________________________________ motionNotifyEvent(GtkWidget *,GdkEventMotion *,gpointer pointer)331 gboolean MenuBarStateData::motionNotifyEvent(GtkWidget*, GdkEventMotion*, gpointer pointer ) 332 { 333 static_cast<MenuBarStateData*>( pointer )->updateItems( GDK_MOTION_NOTIFY ); 334 return FALSE; 335 } 336 337 //________________________________________________________________________________ leaveNotifyEvent(GtkWidget *,GdkEventCrossing *,gpointer pointer)338 gboolean MenuBarStateData::leaveNotifyEvent( GtkWidget*, GdkEventCrossing*, gpointer pointer ) 339 { 340 static_cast<MenuBarStateData*>( pointer )->updateItems( GDK_LEAVE_NOTIFY ); 341 return FALSE; 342 } 343 344 //_____________________________________________ delayedUpdate(gpointer pointer)345 gboolean MenuBarStateData::delayedUpdate( gpointer pointer ) 346 { 347 348 MenuBarStateData& data( *static_cast<MenuBarStateData*>( pointer ) ); 349 350 if( data._target ) 351 { 352 const GdkRectangle rect( data.dirtyRect() ); 353 Gtk::gtk_widget_queue_draw( data._target, &rect ); 354 } 355 356 return FALSE; 357 358 } 359 360 //_____________________________________________ followMouseUpdate(gpointer pointer)361 gboolean MenuBarStateData::followMouseUpdate( gpointer pointer ) 362 { 363 364 MenuBarStateData& data( *static_cast<MenuBarStateData*>( pointer ) ); 365 if( data._target && data.followMouse() ) 366 { 367 data.updateAnimatedRect(); 368 GdkRectangle rect( data.dirtyRect() ); 369 Gtk::gtk_widget_queue_draw( data._target, &rect ); 370 } 371 372 return FALSE; 373 374 } 375 } 376