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