1 /*
2 * this file is part of the oxygen gtk engine
3 * Copyright (c) 2010 Hugo Pereira Da Costa <hugo.pereira@free.fr>
4 * Copyright (c) 2010 Ruslan Kabatsayev <b7.10110111@gmail.com>
5 *
6 * MenuBarState prelight effect is based on
7 * Redmond95 - a cairo based GTK+ engine
8 * Copyright (C) 2001 Red Hat, Inc. <@redhat.com>
9 * Copyright (C) 2006 Andrew Johnson <acjgenius@earthlink.net>
10 * Copyright (C) 2006-2007 Benjamin Berg <benjamin@sipsolutions.net>
11 *
12 * the menushell data code is largely inspired from the gtk redmond engine
13 *
14 * This  library is free  software; you can  redistribute it and/or
15 * modify it  under  the terms  of the  GNU Lesser  General  Public
16 * License  as published  by the Free  Software  Foundation; either
17 * version 2 of the License, or(at your option ) any later version.
18 *
19 * This library is distributed  in the hope that it will be useful,
20 * but  WITHOUT ANY WARRANTY; without even  the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 * Lesser General Public License for more details.
23 *
24 * You should have received a copy of the GNU Lesser General Public
25 * License  along  with  this library;  if not,  write to  the Free
26 * Software Foundation, Inc., 51  Franklin St, Fifth Floor, Boston,
27 * MA 02110-1301, USA.
28 */
29 
30 #include "oxygenmenubarstatedata.h"
31 #include "../oxygengtkutils.h"
32 #include "../config.h"
33 
34 #include <gtk/gtk.h>
35 
36 namespace Oxygen
37 {
38 
39     //________________________________________________________________________________
connect(GtkWidget * widget)40     void MenuBarStateData::connect( GtkWidget* widget )
41     {
42 
43         #if OXYGEN_DEBUG
44         std::cerr
45             << "Oxygen::MenuBarStateData::connect - "
46             << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
47             << std::endl;
48         #endif
49 
50         _target = widget;
51         _motionId.connect( G_OBJECT(widget), "motion-notify-event", G_CALLBACK( motionNotifyEvent ), this );
52         _leaveId.connect( G_OBJECT(widget), "leave-notify-event", G_CALLBACK( leaveNotifyEvent ), this );
53 
54         // connect timeLines
55         _current._timeLine.connect( (GSourceFunc)delayedUpdate, this );
56         _previous._timeLine.connect( (GSourceFunc)delayedUpdate, this );
57 
58         // set directions
59         _current._timeLine.setDirection( TimeLine::Forward );
60         _previous._timeLine.setDirection( TimeLine::Backward );
61 
62         // follow mouse animation
63         FollowMouseData::connect( (GSourceFunc)followMouseUpdate, this );
64 
65     }
66 
67     //________________________________________________________________________________
disconnect(GtkWidget *)68     void MenuBarStateData::disconnect( GtkWidget* )
69     {
70 
71         #if OXYGEN_DEBUG
72         std::cerr
73             << "Oxygen::MenuBarStateData::disconnect - "
74             << " " << _target << " (" << (_target ? G_OBJECT_TYPE_NAME( _target ) : "0x0") << ")"
75             << std::endl;
76         #endif
77 
78         _target = 0L;
79 
80         // disconnect signal
81         _motionId.disconnect();
82         _leaveId.disconnect();
83 
84         // disconnect timelines
85         _current._timeLine.disconnect();
86         _previous._timeLine.disconnect();
87 
88         // disconnect all children
89         for( ChildrenMap::iterator iter = _children.begin(); iter != _children.end(); ++iter )
90         { iter->second.disconnect(); }
91 
92         _children.clear();
93 
94         FollowMouseData::disconnect();
95 
96     }
97 
98 
99     //________________________________________________________________________________
registerChild(GtkWidget * widget)100     void MenuBarStateData::registerChild( GtkWidget* widget )
101     {
102         if( widget && _children.find( widget ) == _children.end() )
103         {
104 
105             #if OXYGEN_DEBUG
106             std::cerr
107                 << "Oxygen::MenuBarStateData::registerChild -"
108                 << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
109                 << std::endl;
110             #endif
111 
112             Signal destroyId;
113             destroyId.connect( G_OBJECT( widget ), "destroy", G_CALLBACK( childDestroyNotifyEvent ), this );
114             _children.insert( std::make_pair( widget, destroyId ) );
115         }
116 
117     }
118 
119     //________________________________________________________________________________
unregisterChild(GtkWidget * widget)120     void MenuBarStateData::unregisterChild( GtkWidget* widget )
121     {
122 
123         #if OXYGEN_DEBUG
124         std::cerr
125             << "Oxygen::MenuBarStateData::unregisterChild -"
126             << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
127             << std::endl;
128         #endif
129 
130         ChildrenMap::iterator iter( _children.find( widget ) );
131 
132         // erase from children map
133         if( iter != _children.end() )
134         {
135             iter->second.disconnect();
136             _children.erase( iter );
137         }
138 
139         // reset corresponding data, if matches
140         if( widget == _previous._widget )
141         {
142             _previous._widget = 0L;
143             _previous._timeLine.disconnect();
144         }
145 
146         if( widget == _current._widget )
147         {
148             _current._widget = 0L;
149             _current._timeLine.disconnect();
150         }
151 
152     }
153 
154     //________________________________________________________________________________
updateItems(GdkEventType type)155     void MenuBarStateData::updateItems( GdkEventType type )
156     {
157 
158         if( !_target ) return;
159 
160         const bool isLeaveEvent( type == GDK_LEAVE_NOTIFY );
161 
162         gint xPointer, yPointer;
163         gdk_window_get_pointer( gtk_widget_get_window( _target ), &xPointer, &yPointer, 0L );
164 
165         bool activeFound( false );
166         GtkWidget *activeWidget( 0L );
167         GList *children( gtk_container_get_children( GTK_CONTAINER( _target ) ) );
168         for( GList* child = g_list_first(children); child; child = g_list_next(child) )
169         {
170 
171             if( !( child->data && GTK_IS_MENU_ITEM( child->data ) ) ) continue;
172 
173             GtkWidget* childWidget( GTK_WIDGET( child->data ) );
174             registerChild( childWidget );
175             const GtkStateType state( gtk_widget_get_state( childWidget ) );
176 
177             // do nothing for disabled child
178             if( state == GTK_STATE_INSENSITIVE ) continue;
179 
180             const GtkAllocation allocation( Gtk::gtk_widget_get_allocation( childWidget ) );
181             if( Gtk::gdk_rectangle_contains( &allocation, xPointer, yPointer ) )
182             {
183 
184                 activeFound = true;
185                 if( state != GTK_STATE_PRELIGHT )
186                 {
187                     updateState( childWidget, allocation, true );
188                     if( !isLeaveEvent ) gtk_widget_set_state( childWidget, GTK_STATE_PRELIGHT );
189                 }
190 
191             } else if( state != GTK_STATE_NORMAL ) {
192 
193                 activeWidget = childWidget;
194 
195             }
196         }
197 
198         if( children ) g_list_free( children );
199 
200         // fade-out current
201         if( _current.isValid() && !activeFound && !menuItemIsActive( _current._widget ) )
202         { updateState( _current._widget, _current._rect, false ); }
203 
204         // disable previous active widget, if either another active widget was found, or this one is not active
205         if( activeWidget && (activeFound || !menuItemIsActive( activeWidget ) ) )
206         { gtk_widget_set_state( activeWidget, GTK_STATE_NORMAL ); }
207 
208         return;
209 
210     }
211 
212     //________________________________________________________________________________
menuItemIsActive(GtkWidget * widget) const213     bool MenuBarStateData::menuItemIsActive( GtkWidget* widget ) const
214     {
215 
216         // check argument
217         if( !GTK_IS_MENU_ITEM( widget ) ) return false;
218 
219         // check menu
220         GtkWidget* menu( gtk_menu_item_get_submenu( GTK_MENU_ITEM( widget ) ) );
221         if( !GTK_IS_MENU( menu ) ) return false;
222 
223         GtkWidget* topLevel( gtk_widget_get_toplevel( menu ) );
224         if( !topLevel ) return false;
225 
226         return
227             GTK_WIDGET_VISIBLE( menu ) &&
228             GTK_WIDGET_REALIZED( topLevel ) &&
229             GTK_WIDGET_VISIBLE( topLevel );
230     }
231 
232     //________________________________________________________________________________
updateState(GtkWidget * widget,const GdkRectangle & rect,bool state)233     bool MenuBarStateData::updateState( GtkWidget* widget, const GdkRectangle& rect, bool state )
234     {
235 
236         // do nothing if animations are disabled
237         if( !_animationsEnabled ) return true;
238 
239         if( state && widget != _current._widget )
240         {
241 
242             // stop current animation if running
243             if( _current._timeLine.isRunning() ) _current._timeLine.stop();
244 
245             // stop previous animation if running
246             if( _current.isValid() )
247             {
248                 if( _previous._timeLine.isRunning() ) _previous._timeLine.stop();
249 
250                 if( _previous.isValid() )
251                 { _dirtyRect = _previous._rect; }
252 
253                 // move current to previous
254                 _previous.copy( _current );
255             }
256 
257             // assign new widget to current and start animation
258             const bool animate( !_current.isValid() );
259             GdkRectangle startRect( _current._rect );
260             _current.update( widget, rect );
261             if( _current.isValid() )
262             {
263                 if( animate ) _current._timeLine.start();
264                 else if( followMouse() ) startAnimation( startRect, _current._rect );
265                 else delayedUpdate( this );
266             }
267 
268             return true;
269 
270         } else if( (!state) && widget == _current._widget ) {
271 
272             // stop current animation if running
273             if( _current._timeLine.isRunning() ) _current._timeLine.stop();
274 
275             // stop previous animation if running
276             if( _previous._timeLine.isRunning() ) _previous._timeLine.stop();
277 
278             if( _previous.isValid() )
279             { _dirtyRect = _previous._rect; }
280 
281             // move current to previous; clear current, and animate
282             _previous.copy( _current );
283             _current.clear();
284             if( _previous.isValid() && gtk_widget_get_state( _previous._widget ) == GTK_STATE_PRELIGHT ) _previous._timeLine.start();
285 
286             return true;
287 
288         } else return false;
289 
290     }
291 
292     //________________________________________________________________________________
dirtyRect(void)293     GdkRectangle MenuBarStateData::dirtyRect( void )
294     {
295 
296         GdkRectangle rect( Gtk::gdk_rectangle() );
297         Gtk::gdk_rectangle_union( &_previous._rect, &_current._rect, &rect );
298 
299         // add _dirtyRect
300         if( Gtk::gdk_rectangle_is_valid( &_dirtyRect ) )
301         {
302             Gtk::gdk_rectangle_union( &_dirtyRect, &rect, &rect );
303             _dirtyRect = Gtk::gdk_rectangle();
304         }
305 
306         // add followMouse dirtyRect
307         if( followMouse() )
308         {
309             const GdkRectangle followMouseRect( FollowMouseData::dirtyRect() );
310             Gtk::gdk_rectangle_union( &followMouseRect, &rect, &rect );
311         }
312 
313         return rect;
314 
315     }
316 
317     //____________________________________________________________________________________________
childDestroyNotifyEvent(GtkWidget * widget,gpointer data)318     gboolean MenuBarStateData::childDestroyNotifyEvent( GtkWidget* widget, gpointer data )
319     {
320         #if OXYGEN_DEBUG
321         std::cerr
322             << "Oxygen::MenuBarStateData::childDestroyNotifyEvent -"
323             << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
324             << std::endl;
325         #endif
326         static_cast<MenuBarStateData*>(data)->unregisterChild( widget );
327         return FALSE;
328     }
329 
330     //________________________________________________________________________________
motionNotifyEvent(GtkWidget *,GdkEventMotion *,gpointer pointer)331     gboolean MenuBarStateData::motionNotifyEvent(GtkWidget*, GdkEventMotion*, gpointer pointer )
332     {
333         static_cast<MenuBarStateData*>( pointer )->updateItems( GDK_MOTION_NOTIFY );
334         return FALSE;
335     }
336 
337     //________________________________________________________________________________
leaveNotifyEvent(GtkWidget *,GdkEventCrossing *,gpointer pointer)338     gboolean MenuBarStateData::leaveNotifyEvent( GtkWidget*, GdkEventCrossing*, gpointer pointer )
339     {
340         static_cast<MenuBarStateData*>( pointer )->updateItems( GDK_LEAVE_NOTIFY );
341         return FALSE;
342     }
343 
344     //_____________________________________________
delayedUpdate(gpointer pointer)345     gboolean MenuBarStateData::delayedUpdate( gpointer pointer )
346     {
347 
348         MenuBarStateData& data( *static_cast<MenuBarStateData*>( pointer ) );
349 
350         if( data._target )
351         {
352             const GdkRectangle rect( data.dirtyRect() );
353             Gtk::gtk_widget_queue_draw( data._target, &rect );
354         }
355 
356         return FALSE;
357 
358     }
359 
360     //_____________________________________________
followMouseUpdate(gpointer pointer)361     gboolean MenuBarStateData::followMouseUpdate( gpointer pointer )
362     {
363 
364         MenuBarStateData& data( *static_cast<MenuBarStateData*>( pointer ) );
365         if( data._target && data.followMouse() )
366         {
367             data.updateAnimatedRect();
368             GdkRectangle rect( data.dirtyRect() );
369             Gtk::gtk_widget_queue_draw( data._target, &rect );
370         }
371 
372         return FALSE;
373 
374     }
375 }
376