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, &currentRect, &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 }