1 /* 2 * this file is part of the oxygen gtk engine 3 * Copyright (c) 2010 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 "oxygenmenustatedata.h" 22 #include "../oxygengtkutils.h" 23 #include "../config.h" 24 25 #include <cassert> 26 #include <gtk/gtk.h> 27 28 namespace Oxygen 29 { 30 31 //________________________________________________________________________________ 32 const int MenuStateData::_timeOut = 50; connect(GtkWidget * widget)33 void MenuStateData::connect( GtkWidget* widget ) 34 { 35 36 #if OXYGEN_DEBUG 37 std::cerr 38 << "Oxygen::MenuStateData::connect - " 39 << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")" 40 << std::endl; 41 #endif 42 43 _target = widget; 44 45 // save paddings 46 if( GTK_IS_MENU( widget ) ) 47 { 48 gtk_widget_style_get( _target, 49 "vertical-padding", &_yPadding, 50 "horizontal-padding", &_xPadding, 51 NULL ); 52 } 53 54 // this accounts for x/y thickness. 55 // needs to retrieve it from widget 56 _xPadding += gtk_widget_get_style( widget )->xthickness; 57 _yPadding += gtk_widget_get_style( widget )->ythickness; 58 59 // connect signals 60 _motionId.connect( G_OBJECT(widget), "motion-notify-event", G_CALLBACK( motionNotifyEvent ), this ); 61 _leaveId.connect( G_OBJECT(widget), "leave-notify-event", G_CALLBACK( leaveNotifyEvent ), this ); 62 63 // connect timeLines 64 _current._timeLine.connect( (GSourceFunc)delayedUpdate, this ); 65 _previous._timeLine.connect( (GSourceFunc)delayedUpdate, this ); 66 67 // set directions 68 _current._timeLine.setDirection( TimeLine::Forward ); 69 _previous._timeLine.setDirection( TimeLine::Backward ); 70 71 // follow mouse animation 72 FollowMouseData::connect( (GSourceFunc)followMouseUpdate, this ); 73 74 } 75 76 //________________________________________________________________________________ disconnect(GtkWidget * widget)77 void MenuStateData::disconnect( GtkWidget* widget ) 78 { 79 80 #if OXYGEN_DEBUG 81 std::cerr 82 << "Oxygen::MenuStateData::disconnect - " 83 << " " << _target << " (" << (_target ? G_OBJECT_TYPE_NAME( _target ) : "0x0") << ")" 84 << std::endl; 85 #endif 86 87 _target = 0L; 88 89 // disconnect signal 90 _motionId.disconnect(); 91 _leaveId.disconnect(); 92 93 // disconnect timelines 94 _current._timeLine.disconnect(); 95 _previous._timeLine.disconnect(); 96 _timer.stop(); 97 98 // disconnect all children 99 for( ChildrenMap::iterator iter = _children.begin(); iter != _children.end(); ++iter ) 100 { iter->second.disconnect(); } 101 102 _children.clear(); 103 104 // base class 105 FollowMouseData::disconnect(); 106 107 } 108 109 //________________________________________________________________________________ registerChild(GtkWidget * widget)110 void MenuStateData::registerChild( GtkWidget* widget ) 111 { 112 if( widget && _children.find( widget ) == _children.end() ) 113 { 114 115 #if OXYGEN_DEBUG 116 std::cerr 117 << "Oxygen::MenuStateData::registerChild -" 118 << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")" 119 << std::endl; 120 #endif 121 122 Signal destroyId; 123 destroyId.connect( G_OBJECT( widget ), "destroy", G_CALLBACK( childDestroyNotifyEvent ), this ); 124 _children.insert( std::make_pair( widget, destroyId ) ); 125 } 126 127 } 128 129 //________________________________________________________________________________ unregisterChild(GtkWidget * widget)130 void MenuStateData::unregisterChild( GtkWidget* widget ) 131 { 132 133 #if OXYGEN_DEBUG 134 std::cerr 135 << "Oxygen::MenuStateData::unregisterChild -" 136 << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")" 137 << std::endl; 138 #endif 139 140 ChildrenMap::iterator iter( _children.find( widget ) ); 141 142 // erase from children map 143 if( iter != _children.end() ) 144 { 145 iter->second.disconnect(); 146 _children.erase( iter ); 147 } 148 149 // reset corresponding data, if matches 150 if( widget == _previous._widget ) 151 { 152 _previous._widget = 0L; 153 _previous._timeLine.disconnect(); 154 } 155 156 if( widget == _current._widget ) 157 { 158 _current._widget = 0L; 159 _current._timeLine.disconnect(); 160 } 161 162 } 163 164 //________________________________________________________________________________ updateItems(void)165 void MenuStateData::updateItems( void ) 166 { 167 168 if( !_target ) return; 169 170 gint xPointer, yPointer; 171 gdk_window_get_pointer( gtk_widget_get_window( _target ), &xPointer, &yPointer, 0L ); 172 173 GdkWindow* window( gtk_widget_get_window( _target ) ); 174 GdkWindow* childWindow( 0L ); 175 176 // reset offset 177 int xOffset(0); 178 int yOffset(0); 179 180 bool delayed( false ); 181 bool activeFound( false ); 182 GList *children( gtk_container_get_children( GTK_CONTAINER( _target ) ) ); 183 for( GList* child = g_list_first(children); child; child = g_list_next(child) ) 184 { 185 186 if( !( child->data && GTK_IS_MENU_ITEM( child->data ) ) ) continue; 187 188 GtkWidget* childWidget( GTK_WIDGET( child->data ) ); 189 registerChild( childWidget ); 190 const GtkStateType state( gtk_widget_get_state( childWidget ) ); 191 192 // do nothing for disabled child 193 const bool active( state != GTK_STATE_INSENSITIVE && !GTK_IS_SEPARATOR_MENU_ITEM( childWidget ) ); 194 195 // update offsets 196 if( childWindow != gtk_widget_get_window( childWidget ) ) 197 { 198 199 childWindow = gtk_widget_get_window( childWidget ); 200 Gtk::gdk_window_translate_origin( window, childWindow, &xOffset, &yOffset ); 201 202 } 203 204 // get allocation and add offsets 205 GtkAllocation allocation( Gtk::gtk_widget_get_allocation( childWidget ) ); 206 allocation.x += xOffset; 207 allocation.y += yOffset; 208 209 if( Gtk::gdk_rectangle_contains( &allocation, xPointer, yPointer ) ) 210 { 211 212 if( active ) 213 { 214 215 activeFound = true; 216 if( state != GTK_STATE_PRELIGHT ) 217 { updateState( childWidget, Gtk::gtk_widget_get_allocation( childWidget ), xOffset, yOffset, true ); } 218 219 } else delayed = true; 220 221 break; 222 223 } 224 225 } 226 227 if( children ) g_list_free( children ); 228 229 // fade-out current 230 if( _current.isValid() && !activeFound && !menuItemIsActive( _current._widget ) ) 231 { updateState( _current._widget, _current._rect, _current._xOffset, _current._yOffset, false, delayed ); } 232 233 return; 234 235 } 236 237 //________________________________________________________________________________ menuItemIsActive(GtkWidget * widget) const238 bool MenuStateData::menuItemIsActive( GtkWidget* widget ) const 239 { 240 241 // check argument 242 if( !GTK_IS_MENU_ITEM( widget ) ) return false; 243 244 // check menu 245 GtkWidget* menu( gtk_menu_item_get_submenu( GTK_MENU_ITEM( widget ) ) ); 246 if( !GTK_IS_MENU( menu ) ) return false; 247 248 GtkWidget* topLevel( gtk_widget_get_toplevel( menu ) ); 249 if( !topLevel ) return false; 250 251 return 252 GTK_WIDGET_VISIBLE( menu ) && 253 GTK_WIDGET_REALIZED( topLevel ) && 254 GTK_WIDGET_VISIBLE( topLevel ); 255 } 256 257 //________________________________________________________________________________ updateState(GtkWidget * widget,const GdkRectangle & rect,int xOffset,int yOffset,bool state,bool delayed)258 bool MenuStateData::updateState( GtkWidget* widget, const GdkRectangle& rect, int xOffset, int yOffset, bool state, bool delayed ) 259 { 260 261 if( state && widget != _current._widget ) 262 { 263 264 // stop timer 265 if( _timer.isRunning() ) _timer.stop(); 266 267 // stop current animation if running 268 if( _current._timeLine.isRunning() ) _current._timeLine.stop(); 269 270 // stop previous animation if running 271 if( _current.isValid() ) 272 { 273 if( _previous._timeLine.isRunning() ) _previous._timeLine.stop(); 274 275 if( _previous.isValid() ) 276 { 277 _dirtyRect = _previous._rect; 278 _dirtyRect.x += _previous._xOffset; 279 _dirtyRect.y += _previous._yOffset; 280 } 281 282 // move current to previous 283 _previous.copy( _current ); 284 } 285 286 // assign new widget to current and start animation 287 const bool animate( !_current.isValid() ); 288 const GdkRectangle startRect( _current._rect ); 289 const int startOffset( _current._yOffset ); 290 _current.update( widget, rect, xOffset, yOffset ); 291 292 if( _current.isValid() ) 293 { 294 if( animate ) _current._timeLine.start(); 295 else if( followMouse() && (startOffset == _current._yOffset ) ) startAnimation( startRect, _current._rect ); 296 else delayedUpdate( this ); 297 } 298 299 return true; 300 301 } else if( (!state) && widget == _current._widget ) { 302 303 // stop current animation if running 304 if( _current._timeLine.isRunning() ) _current._timeLine.stop(); 305 306 // stop previous animation if running 307 if( _previous._timeLine.isRunning() ) _previous._timeLine.stop(); 308 309 if( _previous.isValid() ) 310 { 311 _dirtyRect = _previous._rect; 312 _dirtyRect.x += _previous._xOffset; 313 _dirtyRect.y += _previous._yOffset; 314 } 315 316 // move current to previous; clear current, and animate 317 if( followMouse() && delayed ) { 318 319 if( !_timer.isRunning() ) 320 { _timer.start( _timeOut, (GSourceFunc)delayedAnimate, this ); } 321 322 } else { 323 324 if( _timer.isRunning() ) _timer.stop(); 325 _previous.copy( _current ); 326 _current.clear(); 327 if( _previous.isValid() && gtk_widget_get_state( _previous._widget ) == GTK_STATE_PRELIGHT ) 328 { _previous._timeLine.start(); } 329 330 } 331 332 return true; 333 334 } else return false; 335 336 } 337 338 //_____________________________________________ dirtyRect(void)339 GdkRectangle MenuStateData::dirtyRect( void ) 340 { 341 342 GdkRectangle rect( Gtk::gdk_rectangle() ); 343 const GdkRectangle previousRect( _previous.dirtyRect() ); 344 const GdkRectangle currentRect( _current.dirtyRect() ); 345 Gtk::gdk_rectangle_union( &previousRect, ¤tRect, &rect ); 346 347 // add _dirtyRect 348 if( Gtk::gdk_rectangle_is_valid( &_dirtyRect ) ) 349 { 350 Gtk::gdk_rectangle_union( &_dirtyRect, &rect, &rect ); 351 _dirtyRect = Gtk::gdk_rectangle(); 352 } 353 354 // add followMouse dirtyRect 355 if( followMouse() ) 356 { 357 358 // retrieve dirty rect and add relevant offsets 359 GdkRectangle followMouseRect( FollowMouseData::dirtyRect() ); 360 if( Gtk::gdk_rectangle_is_valid( &_current._rect ) ) 361 { 362 363 followMouseRect.x += _current._xOffset; 364 followMouseRect.y += _current._yOffset; 365 366 } else if( Gtk::gdk_rectangle_is_valid( &_previous._rect ) ) { 367 368 followMouseRect.x += _previous._xOffset; 369 followMouseRect.y += _previous._yOffset; 370 371 } else if( Gtk::gdk_rectangle_is_valid( &followMouseRect ) && _target ) { 372 373 // no valid offset found. Add full allocation 374 followMouseRect = Gtk::gtk_widget_get_allocation( _target ); 375 followMouseRect.x += _xPadding; 376 followMouseRect.y += _yPadding; 377 followMouseRect.width -= 2*_xPadding; 378 followMouseRect.height -= 2*_yPadding; 379 380 } 381 382 Gtk::gdk_rectangle_union( &followMouseRect, &rect, &rect ); 383 } 384 385 // extend rect by some arbitrary number to prevent glitches 386 if( Gtk::gdk_rectangle_is_valid( &rect ) ) rect.height += 1; 387 388 return rect; 389 390 } 391 392 //____________________________________________________________________________________________ childDestroyNotifyEvent(GtkWidget * widget,gpointer data)393 gboolean MenuStateData::childDestroyNotifyEvent( GtkWidget* widget, gpointer data ) 394 { 395 #if OXYGEN_DEBUG 396 std::cerr 397 << "Oxygen::MenuStateData::childDestroyNotifyEvent -" 398 << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")" 399 << std::endl; 400 #endif 401 static_cast<MenuStateData*>(data)->unregisterChild( widget ); 402 return FALSE; 403 } 404 405 //________________________________________________________________________________ motionNotifyEvent(GtkWidget *,GdkEventMotion *,gpointer pointer)406 gboolean MenuStateData::motionNotifyEvent(GtkWidget*, GdkEventMotion*, gpointer pointer ) 407 { 408 static_cast<MenuStateData*>( pointer )->updateItems(); 409 return FALSE; 410 } 411 412 //________________________________________________________________________________ leaveNotifyEvent(GtkWidget *,GdkEventCrossing *,gpointer pointer)413 gboolean MenuStateData::leaveNotifyEvent( GtkWidget*, GdkEventCrossing*, gpointer pointer ) 414 { 415 static_cast<MenuStateData*>( pointer )->updateItems(); 416 return FALSE; 417 } 418 419 //_____________________________________________ delayedUpdate(gpointer pointer)420 gboolean MenuStateData::delayedUpdate( gpointer pointer ) 421 { 422 423 MenuStateData& data( *static_cast<MenuStateData*>( pointer ) ); 424 425 if( data._target ) 426 { 427 const GdkRectangle rect( data.dirtyRect() ); 428 Gtk::gtk_widget_queue_draw( data._target, &rect ); 429 } 430 431 return FALSE; 432 433 } 434 435 //_____________________________________________ followMouseUpdate(gpointer pointer)436 gboolean MenuStateData::followMouseUpdate( gpointer pointer ) 437 { 438 439 MenuStateData& data( *static_cast<MenuStateData*>( pointer ) ); 440 441 if( data._target && data.followMouse() ) 442 { 443 444 data.updateAnimatedRect(); 445 const GdkRectangle rect( data.dirtyRect() ); 446 Gtk::gtk_widget_queue_draw( data._target, &rect ); 447 448 } 449 450 return FALSE; 451 452 } 453 454 //_____________________________________________ delayedAnimate(gpointer pointer)455 gboolean MenuStateData::delayedAnimate( gpointer pointer ) 456 { 457 458 MenuStateData& data( *static_cast<MenuStateData*>( pointer ) ); 459 data._previous.copy( data._current ); 460 data._current.clear(); 461 462 if( data._previous.isValid() ) 463 { data._previous._timeLine.start(); } 464 465 return FALSE; 466 467 } 468 469 } 470