File indexing completed on 2024-05-12 05:34:34
0001 /* 0002 this file is part of the oxygen gtk engine 0003 SPDX-FileCopyrightText: 2010 Hugo Pereira Da Costa <hugo.pereira@free.fr> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "oxygenmenustatedata.h" 0009 #include "../oxygengtkutils.h" 0010 #include "../config.h" 0011 0012 #include <cassert> 0013 #include <gtk/gtk.h> 0014 0015 namespace Oxygen 0016 { 0017 0018 //________________________________________________________________________________ 0019 const int MenuStateData::_timeOut = 50; 0020 void MenuStateData::connect( GtkWidget* widget ) 0021 { 0022 0023 #if OXYGEN_DEBUG 0024 std::cerr 0025 << "Oxygen::MenuStateData::connect - " 0026 << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")" 0027 << std::endl; 0028 #endif 0029 0030 _target = widget; 0031 0032 // save paddings 0033 if( GTK_IS_MENU( widget ) ) 0034 { 0035 gtk_widget_style_get( _target, 0036 "vertical-padding", &_yPadding, 0037 "horizontal-padding", &_xPadding, 0038 NULL ); 0039 } 0040 0041 // this accounts for x/y thickness. 0042 // needs to retrieve it from widget 0043 _xPadding += gtk_widget_get_style( widget )->xthickness; 0044 _yPadding += gtk_widget_get_style( widget )->ythickness; 0045 0046 // connect signals 0047 _motionId.connect( G_OBJECT(widget), "motion-notify-event", G_CALLBACK( motionNotifyEvent ), this ); 0048 _leaveId.connect( G_OBJECT(widget), "leave-notify-event", G_CALLBACK( leaveNotifyEvent ), this ); 0049 0050 // connect timeLines 0051 _current._timeLine.connect( (GSourceFunc)delayedUpdate, this ); 0052 _previous._timeLine.connect( (GSourceFunc)delayedUpdate, this ); 0053 0054 // set directions 0055 _current._timeLine.setDirection( TimeLine::Forward ); 0056 _previous._timeLine.setDirection( TimeLine::Backward ); 0057 0058 // follow mouse animation 0059 FollowMouseData::connect( (GSourceFunc)followMouseUpdate, this ); 0060 0061 } 0062 0063 //________________________________________________________________________________ 0064 void MenuStateData::disconnect( GtkWidget* widget ) 0065 { 0066 0067 #if OXYGEN_DEBUG 0068 std::cerr 0069 << "Oxygen::MenuStateData::disconnect - " 0070 << " " << _target << " (" << (_target ? G_OBJECT_TYPE_NAME( _target ) : "0x0") << ")" 0071 << std::endl; 0072 #endif 0073 0074 _target = 0L; 0075 0076 // disconnect signal 0077 _motionId.disconnect(); 0078 _leaveId.disconnect(); 0079 0080 // disconnect timelines 0081 _current._timeLine.disconnect(); 0082 _previous._timeLine.disconnect(); 0083 _timer.stop(); 0084 0085 // disconnect all children 0086 for( ChildrenMap::iterator iter = _children.begin(); iter != _children.end(); ++iter ) 0087 { iter->second.disconnect(); } 0088 0089 _children.clear(); 0090 0091 // base class 0092 FollowMouseData::disconnect(); 0093 0094 } 0095 0096 //________________________________________________________________________________ 0097 void MenuStateData::registerChild( GtkWidget* widget ) 0098 { 0099 if( widget && _children.find( widget ) == _children.end() ) 0100 { 0101 0102 #if OXYGEN_DEBUG 0103 std::cerr 0104 << "Oxygen::MenuStateData::registerChild -" 0105 << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")" 0106 << std::endl; 0107 #endif 0108 0109 Signal destroyId; 0110 destroyId.connect( G_OBJECT( widget ), "destroy", G_CALLBACK( childDestroyNotifyEvent ), this ); 0111 _children.insert( std::make_pair( widget, destroyId ) ); 0112 } 0113 0114 } 0115 0116 //________________________________________________________________________________ 0117 void MenuStateData::unregisterChild( GtkWidget* widget ) 0118 { 0119 0120 #if OXYGEN_DEBUG 0121 std::cerr 0122 << "Oxygen::MenuStateData::unregisterChild -" 0123 << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")" 0124 << std::endl; 0125 #endif 0126 0127 ChildrenMap::iterator iter( _children.find( widget ) ); 0128 0129 // erase from children map 0130 if( iter != _children.end() ) 0131 { 0132 iter->second.disconnect(); 0133 _children.erase( iter ); 0134 } 0135 0136 // reset corresponding data, if matches 0137 if( widget == _previous._widget ) 0138 { 0139 _previous._widget = 0L; 0140 _previous._timeLine.disconnect(); 0141 } 0142 0143 if( widget == _current._widget ) 0144 { 0145 _current._widget = 0L; 0146 _current._timeLine.disconnect(); 0147 } 0148 0149 } 0150 0151 //________________________________________________________________________________ 0152 void MenuStateData::updateItems( void ) 0153 { 0154 0155 if( !_target ) return; 0156 0157 gint xPointer, yPointer; 0158 gdk_window_get_pointer( gtk_widget_get_window( _target ), &xPointer, &yPointer, 0L ); 0159 0160 GdkWindow* window( gtk_widget_get_window( _target ) ); 0161 GdkWindow* childWindow( 0L ); 0162 0163 // reset offset 0164 int xOffset(0); 0165 int yOffset(0); 0166 0167 bool delayed( false ); 0168 bool activeFound( false ); 0169 GList *children( gtk_container_get_children( GTK_CONTAINER( _target ) ) ); 0170 for( GList* child = g_list_first(children); child; child = g_list_next(child) ) 0171 { 0172 0173 if( !( child->data && GTK_IS_MENU_ITEM( child->data ) ) ) continue; 0174 0175 GtkWidget* childWidget( GTK_WIDGET( child->data ) ); 0176 registerChild( childWidget ); 0177 const GtkStateType state( gtk_widget_get_state( childWidget ) ); 0178 0179 // do nothing for disabled child 0180 const bool active( state != GTK_STATE_INSENSITIVE && !GTK_IS_SEPARATOR_MENU_ITEM( childWidget ) ); 0181 0182 // update offsets 0183 if( childWindow != gtk_widget_get_window( childWidget ) ) 0184 { 0185 0186 childWindow = gtk_widget_get_window( childWidget ); 0187 Gtk::gdk_window_translate_origin( window, childWindow, &xOffset, &yOffset ); 0188 0189 } 0190 0191 // get allocation and add offsets 0192 GtkAllocation allocation( Gtk::gtk_widget_get_allocation( childWidget ) ); 0193 allocation.x += xOffset; 0194 allocation.y += yOffset; 0195 0196 if( Gtk::gdk_rectangle_contains( &allocation, xPointer, yPointer ) ) 0197 { 0198 0199 if( active ) 0200 { 0201 0202 activeFound = true; 0203 if( state != GTK_STATE_PRELIGHT ) 0204 { updateState( childWidget, Gtk::gtk_widget_get_allocation( childWidget ), xOffset, yOffset, true ); } 0205 0206 } else delayed = true; 0207 0208 break; 0209 0210 } 0211 0212 } 0213 0214 if( children ) g_list_free( children ); 0215 0216 // fade-out current 0217 if( _current.isValid() && !activeFound && !menuItemIsActive( _current._widget ) ) 0218 { updateState( _current._widget, _current._rect, _current._xOffset, _current._yOffset, false, delayed ); } 0219 0220 return; 0221 0222 } 0223 0224 //________________________________________________________________________________ 0225 bool MenuStateData::menuItemIsActive( GtkWidget* widget ) const 0226 { 0227 0228 // check argument 0229 if( !GTK_IS_MENU_ITEM( widget ) ) return false; 0230 0231 // check menu 0232 GtkWidget* menu( gtk_menu_item_get_submenu( GTK_MENU_ITEM( widget ) ) ); 0233 if( !GTK_IS_MENU( menu ) ) return false; 0234 0235 GtkWidget* topLevel( gtk_widget_get_toplevel( menu ) ); 0236 if( !topLevel ) return false; 0237 0238 return 0239 GTK_WIDGET_VISIBLE( menu ) && 0240 GTK_WIDGET_REALIZED( topLevel ) && 0241 GTK_WIDGET_VISIBLE( topLevel ); 0242 } 0243 0244 //________________________________________________________________________________ 0245 bool MenuStateData::updateState( GtkWidget* widget, const GdkRectangle& rect, int xOffset, int yOffset, bool state, bool delayed ) 0246 { 0247 0248 if( state && widget != _current._widget ) 0249 { 0250 0251 // stop timer 0252 if( _timer.isRunning() ) _timer.stop(); 0253 0254 // stop current animation if running 0255 if( _current._timeLine.isRunning() ) _current._timeLine.stop(); 0256 0257 // stop previous animation if running 0258 if( _current.isValid() ) 0259 { 0260 if( _previous._timeLine.isRunning() ) _previous._timeLine.stop(); 0261 0262 if( _previous.isValid() ) 0263 { 0264 _dirtyRect = _previous._rect; 0265 _dirtyRect.x += _previous._xOffset; 0266 _dirtyRect.y += _previous._yOffset; 0267 } 0268 0269 // move current to previous 0270 _previous.copy( _current ); 0271 } 0272 0273 // assign new widget to current and start animation 0274 const bool animate( !_current.isValid() ); 0275 const GdkRectangle startRect( _current._rect ); 0276 const int startOffset( _current._yOffset ); 0277 _current.update( widget, rect, xOffset, yOffset ); 0278 0279 if( _current.isValid() ) 0280 { 0281 if( animate ) _current._timeLine.start(); 0282 else if( followMouse() && (startOffset == _current._yOffset ) ) startAnimation( startRect, _current._rect ); 0283 else delayedUpdate( this ); 0284 } 0285 0286 return true; 0287 0288 } else if( (!state) && widget == _current._widget ) { 0289 0290 // stop current animation if running 0291 if( _current._timeLine.isRunning() ) _current._timeLine.stop(); 0292 0293 // stop previous animation if running 0294 if( _previous._timeLine.isRunning() ) _previous._timeLine.stop(); 0295 0296 if( _previous.isValid() ) 0297 { 0298 _dirtyRect = _previous._rect; 0299 _dirtyRect.x += _previous._xOffset; 0300 _dirtyRect.y += _previous._yOffset; 0301 } 0302 0303 // move current to previous; clear current, and animate 0304 if( followMouse() && delayed ) { 0305 0306 if( !_timer.isRunning() ) 0307 { _timer.start( _timeOut, (GSourceFunc)delayedAnimate, this ); } 0308 0309 } else { 0310 0311 if( _timer.isRunning() ) _timer.stop(); 0312 _previous.copy( _current ); 0313 _current.clear(); 0314 if( _previous.isValid() && gtk_widget_get_state( _previous._widget ) == GTK_STATE_PRELIGHT ) 0315 { _previous._timeLine.start(); } 0316 0317 } 0318 0319 return true; 0320 0321 } else return false; 0322 0323 } 0324 0325 //_____________________________________________ 0326 GdkRectangle MenuStateData::dirtyRect( void ) 0327 { 0328 0329 GdkRectangle rect( Gtk::gdk_rectangle() ); 0330 const GdkRectangle previousRect( _previous.dirtyRect() ); 0331 const GdkRectangle currentRect( _current.dirtyRect() ); 0332 Gtk::gdk_rectangle_union( &previousRect, ¤tRect, &rect ); 0333 0334 // add _dirtyRect 0335 if( Gtk::gdk_rectangle_is_valid( &_dirtyRect ) ) 0336 { 0337 Gtk::gdk_rectangle_union( &_dirtyRect, &rect, &rect ); 0338 _dirtyRect = Gtk::gdk_rectangle(); 0339 } 0340 0341 // add followMouse dirtyRect 0342 if( followMouse() ) 0343 { 0344 0345 // retrieve dirty rect and add relevant offsets 0346 GdkRectangle followMouseRect( FollowMouseData::dirtyRect() ); 0347 if( Gtk::gdk_rectangle_is_valid( &_current._rect ) ) 0348 { 0349 0350 followMouseRect.x += _current._xOffset; 0351 followMouseRect.y += _current._yOffset; 0352 0353 } else if( Gtk::gdk_rectangle_is_valid( &_previous._rect ) ) { 0354 0355 followMouseRect.x += _previous._xOffset; 0356 followMouseRect.y += _previous._yOffset; 0357 0358 } else if( Gtk::gdk_rectangle_is_valid( &followMouseRect ) && _target ) { 0359 0360 // no valid offset found. Add full allocation 0361 followMouseRect = Gtk::gtk_widget_get_allocation( _target ); 0362 followMouseRect.x += _xPadding; 0363 followMouseRect.y += _yPadding; 0364 followMouseRect.width -= 2*_xPadding; 0365 followMouseRect.height -= 2*_yPadding; 0366 0367 } 0368 0369 Gtk::gdk_rectangle_union( &followMouseRect, &rect, &rect ); 0370 } 0371 0372 // extend rect by some arbitrary number to prevent glitches 0373 if( Gtk::gdk_rectangle_is_valid( &rect ) ) rect.height += 1; 0374 0375 return rect; 0376 0377 } 0378 0379 //____________________________________________________________________________________________ 0380 gboolean MenuStateData::childDestroyNotifyEvent( GtkWidget* widget, gpointer data ) 0381 { 0382 #if OXYGEN_DEBUG 0383 std::cerr 0384 << "Oxygen::MenuStateData::childDestroyNotifyEvent -" 0385 << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")" 0386 << std::endl; 0387 #endif 0388 static_cast<MenuStateData*>(data)->unregisterChild( widget ); 0389 return FALSE; 0390 } 0391 0392 //________________________________________________________________________________ 0393 gboolean MenuStateData::motionNotifyEvent(GtkWidget*, GdkEventMotion*, gpointer pointer ) 0394 { 0395 static_cast<MenuStateData*>( pointer )->updateItems(); 0396 return FALSE; 0397 } 0398 0399 //________________________________________________________________________________ 0400 gboolean MenuStateData::leaveNotifyEvent( GtkWidget*, GdkEventCrossing*, gpointer pointer ) 0401 { 0402 static_cast<MenuStateData*>( pointer )->updateItems(); 0403 return FALSE; 0404 } 0405 0406 //_____________________________________________ 0407 gboolean MenuStateData::delayedUpdate( gpointer pointer ) 0408 { 0409 0410 MenuStateData& data( *static_cast<MenuStateData*>( pointer ) ); 0411 0412 if( data._target ) 0413 { 0414 const GdkRectangle rect( data.dirtyRect() ); 0415 Gtk::gtk_widget_queue_draw( data._target, &rect ); 0416 } 0417 0418 return FALSE; 0419 0420 } 0421 0422 //_____________________________________________ 0423 gboolean MenuStateData::followMouseUpdate( gpointer pointer ) 0424 { 0425 0426 MenuStateData& data( *static_cast<MenuStateData*>( pointer ) ); 0427 0428 if( data._target && data.followMouse() ) 0429 { 0430 0431 data.updateAnimatedRect(); 0432 const GdkRectangle rect( data.dirtyRect() ); 0433 Gtk::gtk_widget_queue_draw( data._target, &rect ); 0434 0435 } 0436 0437 return FALSE; 0438 0439 } 0440 0441 //_____________________________________________ 0442 gboolean MenuStateData::delayedAnimate( gpointer pointer ) 0443 { 0444 0445 MenuStateData& data( *static_cast<MenuStateData*>( pointer ) ); 0446 data._previous.copy( data._current ); 0447 data._current.clear(); 0448 0449 if( data._previous.isValid() ) 0450 { data._previous._timeLine.start(); } 0451 0452 return FALSE; 0453 0454 } 0455 0456 }