File indexing completed on 2024-05-12 17:05:48
0001 /* 0002 this file is part of the oxygen gtk engine 0003 SPDX-FileCopyrightText: 2010 Hugo Pereira Da Costa <hugo.pereira@free.fr> 0004 SPDX-FileCopyrightText: 2010 Ruslan Kabatsayev <b7.10110111@gmail.com> 0005 0006 MenuBarState prelight effect is based on 0007 Redmond95 - a cairo based GTK+ engine 0008 SPDX-FileCopyrightText: 2001 Red Hat Inc. <@redhat.com> 0009 SPDX-FileCopyrightText: 2006 Andrew Johnson <acjgenius@earthlink.net> 0010 SPDX-FileCopyrightText: 2006-2007 Benjamin Berg <benjamin@sipsolutions.net> 0011 0012 the menushell data code is largely inspired from the gtk redmond engine 0013 0014 SPDX-License-Identifier: LGPL-2.0-or-later 0015 */ 0016 0017 #include "oxygenmenubarstatedata.h" 0018 #include "../oxygengtkutils.h" 0019 #include "../config.h" 0020 0021 #include <gtk/gtk.h> 0022 0023 namespace Oxygen 0024 { 0025 0026 //________________________________________________________________________________ 0027 void MenuBarStateData::connect( GtkWidget* widget ) 0028 { 0029 0030 #if OXYGEN_DEBUG 0031 std::cerr 0032 << "Oxygen::MenuBarStateData::connect - " 0033 << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")" 0034 << std::endl; 0035 #endif 0036 0037 _target = widget; 0038 _motionId.connect( G_OBJECT(widget), "motion-notify-event", G_CALLBACK( motionNotifyEvent ), this ); 0039 _leaveId.connect( G_OBJECT(widget), "leave-notify-event", G_CALLBACK( leaveNotifyEvent ), this ); 0040 0041 // connect timeLines 0042 _current._timeLine.connect( (GSourceFunc)delayedUpdate, this ); 0043 _previous._timeLine.connect( (GSourceFunc)delayedUpdate, this ); 0044 0045 // set directions 0046 _current._timeLine.setDirection( TimeLine::Forward ); 0047 _previous._timeLine.setDirection( TimeLine::Backward ); 0048 0049 // follow mouse animation 0050 FollowMouseData::connect( (GSourceFunc)followMouseUpdate, this ); 0051 0052 } 0053 0054 //________________________________________________________________________________ 0055 void MenuBarStateData::disconnect( GtkWidget* ) 0056 { 0057 0058 #if OXYGEN_DEBUG 0059 std::cerr 0060 << "Oxygen::MenuBarStateData::disconnect - " 0061 << " " << _target << " (" << (_target ? G_OBJECT_TYPE_NAME( _target ) : "0x0") << ")" 0062 << std::endl; 0063 #endif 0064 0065 _target = 0L; 0066 0067 // disconnect signal 0068 _motionId.disconnect(); 0069 _leaveId.disconnect(); 0070 0071 // disconnect timelines 0072 _current._timeLine.disconnect(); 0073 _previous._timeLine.disconnect(); 0074 0075 // disconnect all children 0076 for( ChildrenMap::iterator iter = _children.begin(); iter != _children.end(); ++iter ) 0077 { iter->second.disconnect(); } 0078 0079 _children.clear(); 0080 0081 FollowMouseData::disconnect(); 0082 0083 } 0084 0085 0086 //________________________________________________________________________________ 0087 void MenuBarStateData::registerChild( GtkWidget* widget ) 0088 { 0089 if( widget && _children.find( widget ) == _children.end() ) 0090 { 0091 0092 #if OXYGEN_DEBUG 0093 std::cerr 0094 << "Oxygen::MenuBarStateData::registerChild -" 0095 << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")" 0096 << std::endl; 0097 #endif 0098 0099 Signal destroyId; 0100 destroyId.connect( G_OBJECT( widget ), "destroy", G_CALLBACK( childDestroyNotifyEvent ), this ); 0101 _children.insert( std::make_pair( widget, destroyId ) ); 0102 } 0103 0104 } 0105 0106 //________________________________________________________________________________ 0107 void MenuBarStateData::unregisterChild( GtkWidget* widget ) 0108 { 0109 0110 #if OXYGEN_DEBUG 0111 std::cerr 0112 << "Oxygen::MenuBarStateData::unregisterChild -" 0113 << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")" 0114 << std::endl; 0115 #endif 0116 0117 ChildrenMap::iterator iter( _children.find( widget ) ); 0118 0119 // erase from children map 0120 if( iter != _children.end() ) 0121 { 0122 iter->second.disconnect(); 0123 _children.erase( iter ); 0124 } 0125 0126 // reset corresponding data, if matches 0127 if( widget == _previous._widget ) 0128 { 0129 _previous._widget = 0L; 0130 _previous._timeLine.disconnect(); 0131 } 0132 0133 if( widget == _current._widget ) 0134 { 0135 _current._widget = 0L; 0136 _current._timeLine.disconnect(); 0137 } 0138 0139 } 0140 0141 //________________________________________________________________________________ 0142 void MenuBarStateData::updateItems( GdkEventType type ) 0143 { 0144 0145 if( !_target ) return; 0146 0147 const bool isLeaveEvent( type == GDK_LEAVE_NOTIFY ); 0148 0149 gint xPointer, yPointer; 0150 gdk_window_get_pointer( gtk_widget_get_window( _target ), &xPointer, &yPointer, 0L ); 0151 0152 bool activeFound( false ); 0153 GtkWidget *activeWidget( 0L ); 0154 GList *children( gtk_container_get_children( GTK_CONTAINER( _target ) ) ); 0155 for( GList* child = g_list_first(children); child; child = g_list_next(child) ) 0156 { 0157 0158 if( !( child->data && GTK_IS_MENU_ITEM( child->data ) ) ) continue; 0159 0160 GtkWidget* childWidget( GTK_WIDGET( child->data ) ); 0161 registerChild( childWidget ); 0162 const GtkStateType state( gtk_widget_get_state( childWidget ) ); 0163 0164 // do nothing for disabled child 0165 if( state == GTK_STATE_INSENSITIVE ) continue; 0166 0167 const GtkAllocation allocation( Gtk::gtk_widget_get_allocation( childWidget ) ); 0168 if( Gtk::gdk_rectangle_contains( &allocation, xPointer, yPointer ) ) 0169 { 0170 0171 activeFound = true; 0172 if( state != GTK_STATE_PRELIGHT ) 0173 { 0174 updateState( childWidget, allocation, true ); 0175 if( !isLeaveEvent ) gtk_widget_set_state( childWidget, GTK_STATE_PRELIGHT ); 0176 } 0177 0178 } else if( state != GTK_STATE_NORMAL ) { 0179 0180 activeWidget = childWidget; 0181 0182 } 0183 } 0184 0185 if( children ) g_list_free( children ); 0186 0187 // fade-out current 0188 if( _current.isValid() && !activeFound && !menuItemIsActive( _current._widget ) ) 0189 { updateState( _current._widget, _current._rect, false ); } 0190 0191 // disable previous active widget, if either another active widget was found, or this one is not active 0192 if( activeWidget && (activeFound || !menuItemIsActive( activeWidget ) ) ) 0193 { gtk_widget_set_state( activeWidget, GTK_STATE_NORMAL ); } 0194 0195 return; 0196 0197 } 0198 0199 //________________________________________________________________________________ 0200 bool MenuBarStateData::menuItemIsActive( GtkWidget* widget ) const 0201 { 0202 0203 // check argument 0204 if( !GTK_IS_MENU_ITEM( widget ) ) return false; 0205 0206 // check menu 0207 GtkWidget* menu( gtk_menu_item_get_submenu( GTK_MENU_ITEM( widget ) ) ); 0208 if( !GTK_IS_MENU( menu ) ) return false; 0209 0210 GtkWidget* topLevel( gtk_widget_get_toplevel( menu ) ); 0211 if( !topLevel ) return false; 0212 0213 return 0214 GTK_WIDGET_VISIBLE( menu ) && 0215 GTK_WIDGET_REALIZED( topLevel ) && 0216 GTK_WIDGET_VISIBLE( topLevel ); 0217 } 0218 0219 //________________________________________________________________________________ 0220 bool MenuBarStateData::updateState( GtkWidget* widget, const GdkRectangle& rect, bool state ) 0221 { 0222 0223 // do nothing if animations are disabled 0224 if( !_animationsEnabled ) return true; 0225 0226 if( state && widget != _current._widget ) 0227 { 0228 0229 // stop current animation if running 0230 if( _current._timeLine.isRunning() ) _current._timeLine.stop(); 0231 0232 // stop previous animation if running 0233 if( _current.isValid() ) 0234 { 0235 if( _previous._timeLine.isRunning() ) _previous._timeLine.stop(); 0236 0237 if( _previous.isValid() ) 0238 { _dirtyRect = _previous._rect; } 0239 0240 // move current to previous 0241 _previous.copy( _current ); 0242 } 0243 0244 // assign new widget to current and start animation 0245 const bool animate( !_current.isValid() ); 0246 GdkRectangle startRect( _current._rect ); 0247 _current.update( widget, rect ); 0248 if( _current.isValid() ) 0249 { 0250 if( animate ) _current._timeLine.start(); 0251 else if( followMouse() ) startAnimation( startRect, _current._rect ); 0252 else delayedUpdate( this ); 0253 } 0254 0255 return true; 0256 0257 } else if( (!state) && widget == _current._widget ) { 0258 0259 // stop current animation if running 0260 if( _current._timeLine.isRunning() ) _current._timeLine.stop(); 0261 0262 // stop previous animation if running 0263 if( _previous._timeLine.isRunning() ) _previous._timeLine.stop(); 0264 0265 if( _previous.isValid() ) 0266 { _dirtyRect = _previous._rect; } 0267 0268 // move current to previous; clear current, and animate 0269 _previous.copy( _current ); 0270 _current.clear(); 0271 if( _previous.isValid() && gtk_widget_get_state( _previous._widget ) == GTK_STATE_PRELIGHT ) _previous._timeLine.start(); 0272 0273 return true; 0274 0275 } else return false; 0276 0277 } 0278 0279 //________________________________________________________________________________ 0280 GdkRectangle MenuBarStateData::dirtyRect( void ) 0281 { 0282 0283 GdkRectangle rect( Gtk::gdk_rectangle() ); 0284 Gtk::gdk_rectangle_union( &_previous._rect, &_current._rect, &rect ); 0285 0286 // add _dirtyRect 0287 if( Gtk::gdk_rectangle_is_valid( &_dirtyRect ) ) 0288 { 0289 Gtk::gdk_rectangle_union( &_dirtyRect, &rect, &rect ); 0290 _dirtyRect = Gtk::gdk_rectangle(); 0291 } 0292 0293 // add followMouse dirtyRect 0294 if( followMouse() ) 0295 { 0296 const GdkRectangle followMouseRect( FollowMouseData::dirtyRect() ); 0297 Gtk::gdk_rectangle_union( &followMouseRect, &rect, &rect ); 0298 } 0299 0300 return rect; 0301 0302 } 0303 0304 //____________________________________________________________________________________________ 0305 gboolean MenuBarStateData::childDestroyNotifyEvent( GtkWidget* widget, gpointer data ) 0306 { 0307 #if OXYGEN_DEBUG 0308 std::cerr 0309 << "Oxygen::MenuBarStateData::childDestroyNotifyEvent -" 0310 << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")" 0311 << std::endl; 0312 #endif 0313 static_cast<MenuBarStateData*>(data)->unregisterChild( widget ); 0314 return FALSE; 0315 } 0316 0317 //________________________________________________________________________________ 0318 gboolean MenuBarStateData::motionNotifyEvent(GtkWidget*, GdkEventMotion*, gpointer pointer ) 0319 { 0320 static_cast<MenuBarStateData*>( pointer )->updateItems( GDK_MOTION_NOTIFY ); 0321 return FALSE; 0322 } 0323 0324 //________________________________________________________________________________ 0325 gboolean MenuBarStateData::leaveNotifyEvent( GtkWidget*, GdkEventCrossing*, gpointer pointer ) 0326 { 0327 static_cast<MenuBarStateData*>( pointer )->updateItems( GDK_LEAVE_NOTIFY ); 0328 return FALSE; 0329 } 0330 0331 //_____________________________________________ 0332 gboolean MenuBarStateData::delayedUpdate( gpointer pointer ) 0333 { 0334 0335 MenuBarStateData& data( *static_cast<MenuBarStateData*>( pointer ) ); 0336 0337 if( data._target ) 0338 { 0339 const GdkRectangle rect( data.dirtyRect() ); 0340 Gtk::gtk_widget_queue_draw( data._target, &rect ); 0341 } 0342 0343 return FALSE; 0344 0345 } 0346 0347 //_____________________________________________ 0348 gboolean MenuBarStateData::followMouseUpdate( gpointer pointer ) 0349 { 0350 0351 MenuBarStateData& data( *static_cast<MenuBarStateData*>( pointer ) ); 0352 if( data._target && data.followMouse() ) 0353 { 0354 data.updateAnimatedRect(); 0355 GdkRectangle rect( data.dirtyRect() ); 0356 Gtk::gtk_widget_queue_draw( data._target, &rect ); 0357 } 0358 0359 return FALSE; 0360 0361 } 0362 }