1 /*
2 * this file is part of the oxygen gtk engine
3 * Copyright (c) 2010 Hugo Pereira Da Costa <hugo.pereira@free.fr>
4 *
5 * This  library is free  software; you can  redistribute it and/or
6 * modify it  under  the terms  of the  GNU Lesser  General  Public
7 * License  as published  by the Free  Software  Foundation; either
8 * version 2 of the License, or(at your option ) any later version.
9 *
10 * This library is distributed  in the hope that it will be useful,
11 * but  WITHOUT ANY WARRANTY; without even  the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License  along  with  this library;  if not,  write to  the Free
17 * Software Foundation, Inc., 51  Franklin St, Fifth Floor, Boston,
18 * MA 02110-1301, USA.
19 */
20 
21 #include "oxygenmenustatedata.h"
22 #include "../oxygengtkutils.h"
23 #include "../config.h"
24 
25 #include <cassert>
26 #include <gtk/gtk.h>
27 
28 namespace Oxygen
29 {
30 
31     //________________________________________________________________________________
32     const int MenuStateData::_timeOut = 50;
connect(GtkWidget * widget)33     void MenuStateData::connect( GtkWidget* widget )
34     {
35 
36         #if OXYGEN_DEBUG
37         std::cerr
38             << "Oxygen::MenuStateData::connect - "
39             << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
40             << std::endl;
41         #endif
42 
43         _target = widget;
44 
45         // save paddings
46         if( GTK_IS_MENU( widget ) )
47         {
48             gtk_widget_style_get( _target,
49                 "vertical-padding", &_yPadding,
50                 "horizontal-padding", &_xPadding,
51                 NULL );
52         }
53 
54         // this accounts for x/y thickness.
55         // needs to retrieve it from widget
56         _xPadding += gtk_widget_get_style( widget )->xthickness;
57         _yPadding += gtk_widget_get_style( widget )->ythickness;
58 
59         // connect signals
60         _motionId.connect( G_OBJECT(widget), "motion-notify-event", G_CALLBACK( motionNotifyEvent ), this );
61         _leaveId.connect( G_OBJECT(widget), "leave-notify-event", G_CALLBACK( leaveNotifyEvent ), this );
62 
63         // connect timeLines
64         _current._timeLine.connect( (GSourceFunc)delayedUpdate, this );
65         _previous._timeLine.connect( (GSourceFunc)delayedUpdate, this );
66 
67         // set directions
68         _current._timeLine.setDirection( TimeLine::Forward );
69         _previous._timeLine.setDirection( TimeLine::Backward );
70 
71         // follow mouse animation
72         FollowMouseData::connect( (GSourceFunc)followMouseUpdate, this );
73 
74     }
75 
76     //________________________________________________________________________________
disconnect(GtkWidget * widget)77     void MenuStateData::disconnect( GtkWidget* widget )
78     {
79 
80         #if OXYGEN_DEBUG
81         std::cerr
82             << "Oxygen::MenuStateData::disconnect - "
83             << " " << _target << " (" << (_target ? G_OBJECT_TYPE_NAME( _target ) : "0x0") << ")"
84             << std::endl;
85         #endif
86 
87         _target = 0L;
88 
89         // disconnect signal
90         _motionId.disconnect();
91         _leaveId.disconnect();
92 
93         // disconnect timelines
94         _current._timeLine.disconnect();
95         _previous._timeLine.disconnect();
96         _timer.stop();
97 
98         // disconnect all children
99         for( ChildrenMap::iterator iter = _children.begin(); iter != _children.end(); ++iter )
100         { iter->second.disconnect(); }
101 
102         _children.clear();
103 
104         // base class
105         FollowMouseData::disconnect();
106 
107    }
108 
109     //________________________________________________________________________________
registerChild(GtkWidget * widget)110     void MenuStateData::registerChild( GtkWidget* widget )
111     {
112         if( widget && _children.find( widget ) == _children.end() )
113         {
114 
115             #if OXYGEN_DEBUG
116             std::cerr
117                 << "Oxygen::MenuStateData::registerChild -"
118                 << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
119                 << std::endl;
120             #endif
121 
122             Signal destroyId;
123             destroyId.connect( G_OBJECT( widget ), "destroy", G_CALLBACK( childDestroyNotifyEvent ), this );
124             _children.insert( std::make_pair( widget, destroyId ) );
125         }
126 
127     }
128 
129     //________________________________________________________________________________
unregisterChild(GtkWidget * widget)130     void MenuStateData::unregisterChild( GtkWidget* widget )
131     {
132 
133         #if OXYGEN_DEBUG
134         std::cerr
135             << "Oxygen::MenuStateData::unregisterChild -"
136             << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
137             << std::endl;
138         #endif
139 
140         ChildrenMap::iterator iter( _children.find( widget ) );
141 
142         // erase from children map
143         if( iter != _children.end() )
144         {
145             iter->second.disconnect();
146             _children.erase( iter );
147         }
148 
149         // reset corresponding data, if matches
150         if( widget == _previous._widget )
151         {
152             _previous._widget = 0L;
153             _previous._timeLine.disconnect();
154         }
155 
156         if( widget == _current._widget )
157         {
158             _current._widget = 0L;
159             _current._timeLine.disconnect();
160         }
161 
162     }
163 
164     //________________________________________________________________________________
updateItems(void)165     void MenuStateData::updateItems( void )
166     {
167 
168         if( !_target ) return;
169 
170         gint xPointer, yPointer;
171         gdk_window_get_pointer( gtk_widget_get_window( _target ), &xPointer, &yPointer, 0L );
172 
173         GdkWindow* window( gtk_widget_get_window( _target ) );
174         GdkWindow* childWindow( 0L );
175 
176         // reset offset
177         int xOffset(0);
178         int yOffset(0);
179 
180         bool delayed( false );
181         bool activeFound( false );
182         GList *children( gtk_container_get_children( GTK_CONTAINER( _target ) ) );
183         for( GList* child = g_list_first(children); child; child = g_list_next(child) )
184         {
185 
186             if( !( child->data && GTK_IS_MENU_ITEM( child->data ) ) ) continue;
187 
188             GtkWidget* childWidget( GTK_WIDGET( child->data ) );
189             registerChild( childWidget );
190             const GtkStateType state( gtk_widget_get_state( childWidget ) );
191 
192             // do nothing for disabled child
193             const bool active( state != GTK_STATE_INSENSITIVE && !GTK_IS_SEPARATOR_MENU_ITEM( childWidget ) );
194 
195             // update offsets
196             if( childWindow != gtk_widget_get_window( childWidget ) )
197             {
198 
199                 childWindow = gtk_widget_get_window( childWidget );
200                 Gtk::gdk_window_translate_origin( window, childWindow, &xOffset, &yOffset );
201 
202             }
203 
204             // get allocation and add offsets
205             GtkAllocation allocation( Gtk::gtk_widget_get_allocation( childWidget ) );
206             allocation.x += xOffset;
207             allocation.y += yOffset;
208 
209             if( Gtk::gdk_rectangle_contains( &allocation, xPointer, yPointer ) )
210             {
211 
212                 if( active )
213                 {
214 
215                     activeFound = true;
216                     if( state != GTK_STATE_PRELIGHT )
217                     { updateState( childWidget, Gtk::gtk_widget_get_allocation( childWidget ), xOffset, yOffset, true ); }
218 
219                 } else delayed = true;
220 
221                 break;
222 
223             }
224 
225         }
226 
227         if( children ) g_list_free( children );
228 
229         // fade-out current
230         if( _current.isValid() && !activeFound && !menuItemIsActive( _current._widget ) )
231         { updateState( _current._widget, _current._rect, _current._xOffset, _current._yOffset, false, delayed ); }
232 
233         return;
234 
235     }
236 
237     //________________________________________________________________________________
menuItemIsActive(GtkWidget * widget) const238     bool MenuStateData::menuItemIsActive( GtkWidget* widget ) const
239     {
240 
241         // check argument
242         if( !GTK_IS_MENU_ITEM( widget ) ) return false;
243 
244         // check menu
245         GtkWidget* menu( gtk_menu_item_get_submenu( GTK_MENU_ITEM( widget ) ) );
246         if( !GTK_IS_MENU( menu ) ) return false;
247 
248         GtkWidget* topLevel( gtk_widget_get_toplevel( menu ) );
249         if( !topLevel ) return false;
250 
251         return
252             GTK_WIDGET_VISIBLE( menu ) &&
253             GTK_WIDGET_REALIZED( topLevel ) &&
254             GTK_WIDGET_VISIBLE( topLevel );
255     }
256 
257     //________________________________________________________________________________
updateState(GtkWidget * widget,const GdkRectangle & rect,int xOffset,int yOffset,bool state,bool delayed)258     bool MenuStateData::updateState( GtkWidget* widget, const GdkRectangle& rect, int xOffset, int yOffset, bool state, bool delayed )
259     {
260 
261         if( state && widget != _current._widget )
262         {
263 
264             // stop timer
265             if( _timer.isRunning() ) _timer.stop();
266 
267             // stop current animation if running
268             if( _current._timeLine.isRunning() ) _current._timeLine.stop();
269 
270             // stop previous animation if running
271             if( _current.isValid() )
272             {
273                 if( _previous._timeLine.isRunning() ) _previous._timeLine.stop();
274 
275                 if( _previous.isValid() )
276                 {
277                     _dirtyRect = _previous._rect;
278                     _dirtyRect.x += _previous._xOffset;
279                     _dirtyRect.y += _previous._yOffset;
280                 }
281 
282                 // move current to previous
283                 _previous.copy( _current );
284             }
285 
286             // assign new widget to current and start animation
287             const bool animate( !_current.isValid() );
288             const GdkRectangle startRect( _current._rect );
289             const int startOffset( _current._yOffset );
290             _current.update( widget, rect, xOffset, yOffset );
291 
292             if( _current.isValid() )
293             {
294                 if( animate ) _current._timeLine.start();
295                 else if( followMouse() && (startOffset == _current._yOffset ) ) startAnimation( startRect, _current._rect );
296                 else delayedUpdate( this );
297             }
298 
299             return true;
300 
301         } else if( (!state) && widget == _current._widget ) {
302 
303             // stop current animation if running
304             if( _current._timeLine.isRunning() ) _current._timeLine.stop();
305 
306             // stop previous animation if running
307             if( _previous._timeLine.isRunning() ) _previous._timeLine.stop();
308 
309             if( _previous.isValid() )
310             {
311                 _dirtyRect = _previous._rect;
312                 _dirtyRect.x += _previous._xOffset;
313                 _dirtyRect.y += _previous._yOffset;
314             }
315 
316             // move current to previous; clear current, and animate
317             if( followMouse() && delayed ) {
318 
319                 if( !_timer.isRunning() )
320                 { _timer.start( _timeOut, (GSourceFunc)delayedAnimate, this ); }
321 
322             } else {
323 
324                 if( _timer.isRunning() ) _timer.stop();
325                 _previous.copy( _current );
326                 _current.clear();
327                 if( _previous.isValid() && gtk_widget_get_state( _previous._widget ) == GTK_STATE_PRELIGHT )
328                 { _previous._timeLine.start(); }
329 
330             }
331 
332             return true;
333 
334         } else return false;
335 
336     }
337 
338     //_____________________________________________
dirtyRect(void)339     GdkRectangle MenuStateData::dirtyRect( void )
340     {
341 
342         GdkRectangle rect( Gtk::gdk_rectangle() );
343         const GdkRectangle previousRect( _previous.dirtyRect() );
344         const GdkRectangle currentRect( _current.dirtyRect() );
345         Gtk::gdk_rectangle_union( &previousRect, &currentRect, &rect );
346 
347         // add _dirtyRect
348         if( Gtk::gdk_rectangle_is_valid( &_dirtyRect ) )
349         {
350             Gtk::gdk_rectangle_union( &_dirtyRect, &rect, &rect );
351             _dirtyRect = Gtk::gdk_rectangle();
352         }
353 
354         // add followMouse dirtyRect
355         if( followMouse() )
356         {
357 
358             // retrieve dirty rect and add relevant offsets
359             GdkRectangle followMouseRect( FollowMouseData::dirtyRect() );
360             if( Gtk::gdk_rectangle_is_valid( &_current._rect ) )
361             {
362 
363                 followMouseRect.x += _current._xOffset;
364                 followMouseRect.y += _current._yOffset;
365 
366             } else if( Gtk::gdk_rectangle_is_valid( &_previous._rect ) ) {
367 
368                 followMouseRect.x += _previous._xOffset;
369                 followMouseRect.y += _previous._yOffset;
370 
371             } else if( Gtk::gdk_rectangle_is_valid( &followMouseRect ) && _target ) {
372 
373                 // no valid offset found. Add full allocation
374                 followMouseRect = Gtk::gtk_widget_get_allocation( _target );
375                 followMouseRect.x += _xPadding;
376                 followMouseRect.y += _yPadding;
377                 followMouseRect.width -= 2*_xPadding;
378                 followMouseRect.height -= 2*_yPadding;
379 
380             }
381 
382             Gtk::gdk_rectangle_union( &followMouseRect, &rect, &rect );
383         }
384 
385         // extend rect by some arbitrary number to prevent glitches
386         if( Gtk::gdk_rectangle_is_valid( &rect ) ) rect.height += 1;
387 
388         return rect;
389 
390     }
391 
392     //____________________________________________________________________________________________
childDestroyNotifyEvent(GtkWidget * widget,gpointer data)393     gboolean MenuStateData::childDestroyNotifyEvent( GtkWidget* widget, gpointer data )
394     {
395         #if OXYGEN_DEBUG
396         std::cerr
397             << "Oxygen::MenuStateData::childDestroyNotifyEvent -"
398             << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
399             << std::endl;
400         #endif
401         static_cast<MenuStateData*>(data)->unregisterChild( widget );
402         return FALSE;
403     }
404 
405     //________________________________________________________________________________
motionNotifyEvent(GtkWidget *,GdkEventMotion *,gpointer pointer)406     gboolean MenuStateData::motionNotifyEvent(GtkWidget*, GdkEventMotion*, gpointer pointer )
407     {
408         static_cast<MenuStateData*>( pointer )->updateItems();
409         return FALSE;
410     }
411 
412     //________________________________________________________________________________
leaveNotifyEvent(GtkWidget *,GdkEventCrossing *,gpointer pointer)413     gboolean MenuStateData::leaveNotifyEvent( GtkWidget*, GdkEventCrossing*, gpointer pointer )
414     {
415         static_cast<MenuStateData*>( pointer )->updateItems();
416         return FALSE;
417     }
418 
419     //_____________________________________________
delayedUpdate(gpointer pointer)420     gboolean MenuStateData::delayedUpdate( gpointer pointer )
421     {
422 
423         MenuStateData& data( *static_cast<MenuStateData*>( pointer ) );
424 
425         if( data._target )
426         {
427             const GdkRectangle rect( data.dirtyRect() );
428             Gtk::gtk_widget_queue_draw( data._target, &rect );
429         }
430 
431         return FALSE;
432 
433     }
434 
435     //_____________________________________________
followMouseUpdate(gpointer pointer)436     gboolean MenuStateData::followMouseUpdate( gpointer pointer )
437     {
438 
439         MenuStateData& data( *static_cast<MenuStateData*>( pointer ) );
440 
441         if( data._target && data.followMouse() )
442         {
443 
444             data.updateAnimatedRect();
445             const GdkRectangle rect( data.dirtyRect() );
446             Gtk::gtk_widget_queue_draw( data._target, &rect );
447 
448         }
449 
450         return FALSE;
451 
452     }
453 
454     //_____________________________________________
delayedAnimate(gpointer pointer)455     gboolean MenuStateData::delayedAnimate( gpointer pointer )
456     {
457 
458         MenuStateData& data( *static_cast<MenuStateData*>( pointer ) );
459         data._previous.copy( data._current );
460         data._current.clear();
461 
462         if( data._previous.isValid() )
463         { data._previous._timeLine.start(); }
464 
465         return FALSE;
466 
467     }
468 
469 }
470