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     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 }