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 "oxygentoolbarstatedata.h"
22 #include "../oxygengtkutils.h"
23 #include "../config.h"
24 
25 #include <gtk/gtk.h>
26 
27 namespace Oxygen
28 {
29 
30     //________________________________________________________________________________
31     const int ToolBarStateData::_timeOut = 50;
connect(GtkWidget * widget)32     void ToolBarStateData::connect( GtkWidget* widget )
33     {
34 
35         #if OXYGEN_DEBUG
36         std::cerr
37             << "Oxygen::ToolBarStateData::connect - "
38             << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
39             << std::endl;
40         #endif
41 
42         // store widget
43         _target = widget;
44 
45         // connect signals
46         _leaveId.connect( G_OBJECT(widget), "leave-notify-event", G_CALLBACK( leaveNotifyEvent ), this );
47 
48         // connect timeLines
49         _current._timeLine.connect( (GSourceFunc)delayedUpdate, this );
50         _previous._timeLine.connect( (GSourceFunc)delayedUpdate, this );
51 
52         // set directions
53         _current._timeLine.setDirection( TimeLine::Forward );
54         _previous._timeLine.setDirection( TimeLine::Backward );
55 
56         // follow mouse animation
57         FollowMouseData::connect( (GSourceFunc)followMouseUpdate, this );
58 
59     }
60 
61     //________________________________________________________________________________
disconnect(GtkWidget * widget)62     void ToolBarStateData::disconnect( GtkWidget* widget )
63     {
64 
65         #if OXYGEN_DEBUG
66         std::cerr
67             << "Oxygen::ToolBarStateData::disconnect - "
68             << " " << _target << " (" << (_target ? G_OBJECT_TYPE_NAME( _target ) : "0x0") << ")"
69             << std::endl;
70         #endif
71 
72         _target = 0L;
73 
74         // disconnect signal
75         _leaveId.disconnect();
76 
77         // disconnect timelines
78         _current._timeLine.disconnect();
79         _previous._timeLine.disconnect();
80         _timer.stop();
81 
82         // disconnect all children
83         for( HoverDataMap::iterator iter = _hoverData.begin(); iter != _hoverData.end(); ++iter )
84         { iter->second.disconnect(); }
85 
86         _hoverData.clear();
87 
88         FollowMouseData::disconnect();
89 
90     }
91 
92     //________________________________________________________________________________
registerChild(GtkWidget * widget,bool value)93     void ToolBarStateData::registerChild( GtkWidget* widget, bool value )
94     {
95 
96         // make sure widget is not already in map
97         if( _hoverData.find( widget ) == _hoverData.end() )
98         {
99 
100             #if OXYGEN_DEBUG
101             std::cerr
102                 << "Oxygen::ToolBarStateData::registerChild -"
103                 << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
104                 << std::endl;
105             #endif
106 
107             // allocate new Hover data
108             HoverData data;
109             data._destroyId.connect( G_OBJECT(widget), "destroy", G_CALLBACK( childDestroyNotifyEvent ), this );
110             data._enterId.connect( G_OBJECT(widget), "enter-notify-event", G_CALLBACK( childEnterNotifyEvent ), this );
111             data._leaveId.connect( G_OBJECT(widget), "leave-notify-event", G_CALLBACK( childLeaveNotifyEvent ), this );
112 
113             // and insert in map
114             _hoverData.insert( std::make_pair( widget, data ) );
115             updateState( widget, value, false );
116 
117 
118         }
119 
120     }
121 
122     //________________________________________________________________________________
unregisterChild(GtkWidget * widget)123     void ToolBarStateData::unregisterChild( GtkWidget* widget )
124     {
125 
126         #if OXYGEN_DEBUG
127         std::cerr
128             << "Oxygen::ToolBarStateData::unregisterChild -"
129             << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
130             << std::endl;
131         #endif
132 
133         // loopup in hover map
134         HoverDataMap::iterator iter( _hoverData.find( widget ) );
135         if( iter != _hoverData.end() )
136         {
137             iter->second.disconnect();
138             _hoverData.erase( iter );
139         }
140 
141         // disconnect previous and current
142         if( widget == _previous._widget )
143         {
144             _previous._widget = 0L;
145             _previous._timeLine.disconnect();
146         }
147 
148         if( widget == _current._widget )
149         {
150             _current._widget = 0L;
151             _current._timeLine.disconnect();
152         }
153 
154     }
155 
156     //________________________________________________________________________________
updateState(GtkWidget * widget,bool state,bool delayed)157     bool ToolBarStateData::updateState( GtkWidget* widget, bool state, bool delayed )
158     {
159 
160         const GdkRectangle rect( widget ? Gtk::gtk_widget_get_allocation( widget ) : Gtk::gdk_rectangle() );
161 
162         if( state && widget != _current._widget )
163         {
164 
165             // stop timer
166             if( _timer.isRunning() ) _timer.stop();
167 
168             // stop current animation if running
169             if( _current._timeLine.isRunning() ) _current._timeLine.stop();
170 
171             // stop previous animation if running
172             if( _current.isValid() )
173             {
174                 if( _previous._timeLine.isRunning() ) _previous._timeLine.stop();
175 
176                 if( _previous.isValid() )
177                 { _dirtyRect = _previous._rect; }
178 
179                 // move current to previous
180                 _previous.copy( _current );
181             }
182 
183             // assign new widget to current and start animation
184             const GdkRectangle startRect( _current._rect );
185             const bool animate( !_current.isValid() );
186             _current.update( widget, rect );
187             if( _current.isValid() )
188             {
189                 if( animate || !followMouse() ) _current._timeLine.start();
190                 else startAnimation( startRect, _current._rect );
191 
192             }
193 
194             return true;
195 
196         } else if( (!state) && widget == _current._widget ) {
197 
198             // stop current animation if running
199             if( _current._timeLine.isRunning() ) _current._timeLine.stop();
200 
201             // stop previous animation if running
202             if( _previous._timeLine.isRunning() ) _previous._timeLine.stop();
203 
204             if( _previous.isValid() )
205             { _dirtyRect = _previous._rect; }
206 
207             // move current to previous; clear current, and animate
208             if( followMouse() && delayed ) {
209 
210                 // start delayed animation
211                 if( !_timer.isRunning() )
212                 { _timer.start( _timeOut, (GSourceFunc)delayedAnimate, this ); }
213 
214             } else {
215 
216                 if( _timer.isRunning() ) _timer.stop();
217                 _previous.copy( _current );
218                 _current.clear();
219 
220                 // start fade-out timeLine
221                 if( _previous.isValid() ) _previous._timeLine.start();
222 
223             }
224 
225             return true;
226 
227         } else return false;
228 
229     }
230 
231     //_____________________________________________
dirtyRect(void)232     GdkRectangle ToolBarStateData::dirtyRect( void )
233     {
234 
235         GdkRectangle rect( Gtk::gdk_rectangle() );
236         Gtk::gdk_rectangle_union( &_previous._rect, &_current._rect, &rect );
237 
238         // add _dirtyRect
239         if( Gtk::gdk_rectangle_is_valid( &_dirtyRect ) )
240         {
241             Gtk::gdk_rectangle_union( &_dirtyRect, &rect, &rect );
242             _dirtyRect = Gtk::gdk_rectangle();
243         }
244 
245         // add followMouse dirtyRect
246         if( followMouse() )
247         {
248             const GdkRectangle followMouseRect( FollowMouseData::dirtyRect() );
249             Gtk::gdk_rectangle_union( &followMouseRect, &rect, &rect );
250         }
251 
252         // check validity
253         if( !Gtk::gdk_rectangle_is_valid( &rect ) ) return rect;
254 
255         /* Add viewport offsets if any */
256         if( GTK_IS_VIEWPORT( _target ) )
257         {
258             gint xOffset, yOffset;
259             Gtk::gtk_viewport_get_position( GTK_VIEWPORT( _target ), &xOffset, &yOffset );
260             rect.x -= xOffset;
261             rect.y -= yOffset;
262         }
263 
264         /* Add extra margin to rect. FIXME: this should be triggered from widget's metrics */
265         rect.x -= 2;
266         rect.y -= 2;
267         rect.width += 4;
268         rect.height += 4;
269 
270         return rect;
271 
272     }
273 
274     //____________________________________________________________________________________________
childDestroyNotifyEvent(GtkWidget * widget,gpointer data)275     gboolean ToolBarStateData::childDestroyNotifyEvent( GtkWidget* widget, gpointer data )
276     {
277         #if OXYGEN_DEBUG
278         std::cerr
279             << "Oxygen::ToolBarStateData::childDestroyNotifyEvent -"
280             << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
281             << std::endl;
282         #endif
283         static_cast<ToolBarStateData*>(data)->unregisterChild( widget );
284         return FALSE;
285     }
286 
287     //________________________________________________________________________________
childEnterNotifyEvent(GtkWidget * widget,GdkEventCrossing *,gpointer data)288     gboolean ToolBarStateData::childEnterNotifyEvent( GtkWidget* widget, GdkEventCrossing*, gpointer data )
289     {
290 
291         #if OXYGEN_DEBUG
292         std::cerr
293             << "Oxygen::ToolBarStateData::childEnterNotifyEvent -"
294             << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
295             << std::endl;
296         #endif
297 
298         static_cast<ToolBarStateData*>( data )->updateState( widget, true, false );
299         return FALSE;
300     }
301 
302     //________________________________________________________________________________
childLeaveNotifyEvent(GtkWidget * widget,GdkEventCrossing *,gpointer data)303     gboolean ToolBarStateData::childLeaveNotifyEvent( GtkWidget* widget, GdkEventCrossing*, gpointer data )
304     {
305 
306         #if OXYGEN_DEBUG
307         std::cerr
308             << "Oxygen::ToolBarStateData::childLeaveNotifyEvent -"
309             << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
310             << std::endl;
311         #endif
312 
313         static_cast<ToolBarStateData*>( data )->updateState( widget, false, true );
314         return FALSE;
315 
316     }
317 
318     //________________________________________________________________________________
leaveNotifyEvent(GtkWidget * widget,GdkEventCrossing *,gpointer pointer)319     gboolean ToolBarStateData::leaveNotifyEvent( GtkWidget* widget, GdkEventCrossing*, gpointer pointer )
320     {
321         #if OXYGEN_DEBUG
322         std::cerr
323             << "Oxygen::ToolBarStateData::leaveNotifyEvent -"
324             << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
325             << std::endl;
326         #endif
327 
328         ToolBarStateData& data( *static_cast<ToolBarStateData*>( pointer ) );
329         if( data._current.isValid() ) data.updateState( data._current._widget, false, false );
330         return FALSE;
331     }
332 
333     //_____________________________________________
delayedUpdate(gpointer pointer)334     gboolean ToolBarStateData::delayedUpdate( gpointer pointer )
335     {
336 
337         ToolBarStateData& data( *static_cast<ToolBarStateData*>( pointer ) );
338 
339         if( data._target )
340         {
341             const GdkRectangle rect( data.dirtyRect() );
342             Gtk::gtk_widget_queue_draw( data._target, &rect );
343             if( data._previous._widget ) Gtk::gtk_widget_queue_draw( data._previous._widget );
344             if( data._current._widget ) Gtk::gtk_widget_queue_draw( data._current._widget );
345         }
346 
347         return FALSE;
348 
349     }
350 
351     //_____________________________________________
followMouseUpdate(gpointer pointer)352     gboolean ToolBarStateData::followMouseUpdate( gpointer pointer )
353     {
354 
355         ToolBarStateData& data( *static_cast<ToolBarStateData*>( pointer ) );
356 
357         if( data._target && data.followMouse() )
358         {
359 
360             data.updateAnimatedRect();
361 
362             GdkRectangle rect( data.dirtyRect() );
363             Gtk::gtk_widget_queue_draw( data._target, &rect );
364             if( data._previous._widget ) Gtk::gtk_widget_queue_draw( data._previous._widget );
365             if( data._current._widget ) Gtk::gtk_widget_queue_draw( data._current._widget );
366 
367         }
368 
369         return FALSE;
370 
371     }
372 
373     //_____________________________________________
delayedAnimate(gpointer pointer)374     gboolean ToolBarStateData::delayedAnimate( gpointer pointer )
375     {
376 
377         ToolBarStateData& data( *static_cast<ToolBarStateData*>( pointer ) );
378         data._previous.copy( data._current );
379         data._current.clear();
380 
381         if( data._previous.isValid() )
382         { data._previous._timeLine.start(); }
383 
384         return FALSE;
385 
386     }
387 }
388