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 "oxygencomboboxdata.h"
22 #include "../oxygengtkutils.h"
23 #include "../config.h"
24 
25 #include <gtk/gtk.h>
26 #include <iostream>
27 #include <cassert>
28 #include <algorithm>
29 namespace Oxygen
30 {
31 
32     //________________________________________________________________________________
connect(GtkWidget * widget)33     void ComboBoxData::connect( GtkWidget* widget )
34     {
35         #if OXYGEN_DEBUG
36         std::cerr << "Oxygen::ComboBoxData::connect - widget: " << widget << std::endl;
37         #endif
38 
39         // set pointers to widgets
40         _target = widget;
41         _list = 0L;
42 
43         // connect signals
44         _stateChangeId.connect( G_OBJECT(widget), "state-changed", G_CALLBACK( stateChangeEvent ), this );
45         _styleSetId.connect( G_OBJECT(widget), "style-set", G_CALLBACK( styleSetEvent ), this );
46 
47         // initialize cell view
48         initializeCellView( widget );
49 
50         /*
51         need to force the wrap-width property to 0,
52         otherwise the "appears-as-list" flag is not respected, which additionally breaks the widget rendering.
53         */
54         gtk_combo_box_set_wrap_width( GTK_COMBO_BOX( widget ), 0 );
55 
56     }
57 
58     //________________________________________________________________________________
disconnect(GtkWidget * widget)59     void ComboBoxData::disconnect( GtkWidget* widget )
60     {
61         #if OXYGEN_DEBUG
62         std::cerr << "Oxygen::ComboBoxData::disconnect - widget: " << widget << std::endl;
63         #endif
64 
65         _stateChangeId.disconnect();
66         _styleSetId.disconnect();
67 
68         // clear pointers to widgets
69         _target = 0L;
70         _list = 0L;
71 
72         _button.disconnect();
73         _cell.disconnect();
74 
75         // disconnect all children
76         for( HoverDataMap::iterator iter = _hoverData.begin(); iter != _hoverData.end(); ++iter )
77         { iter->second.disconnect(); }
78 
79         _hoverData.clear();
80 
81     }
82 
83     //________________________________________________________________________________
setButton(GtkWidget * widget)84     void ComboBoxData::setButton( GtkWidget* widget )
85     {
86         if( _button._widget == widget ) return;
87 
88         if( _button._widget )
89         {
90             std::cerr << "Oxygen::WindowManager::wmButtonPress - warning: a button was already set for this combobox" << std::endl;
91             _button._toggledId.disconnect();
92             _button._sizeAllocateId.disconnect();
93         }
94 
95         _button._toggledId.connect( G_OBJECT(widget), "toggled", G_CALLBACK( childToggledEvent ), this );
96         _button._sizeAllocateId.connect( G_OBJECT(widget), "size-allocate", G_CALLBACK( childSizeAllocateEvent ), this );
97         _button._widget = widget;
98         registerChild( widget, false );
99 
100         updateButtonEventWindow();
101         gtk_widget_queue_draw( widget );
102 
103     }
104 
105     //________________________________________________________________________________
initializeCellView(GtkWidget * widget)106     void ComboBoxData::initializeCellView( GtkWidget* widget )
107     {
108 
109         GList* children( gtk_container_get_children( GTK_CONTAINER( widget ) ) );
110         for( GList* child = g_list_first(children); child; child = g_list_next(child) )
111         {
112 
113             if( !GTK_IS_CELL_VIEW( child->data ) ) continue;
114 
115             // convert to widget and store
116             GtkWidget* widget( GTK_WIDGET( child->data ) );
117             if( _cell._widget == widget ) return;
118             assert( !_cell._widget );
119 
120             _cell._widget = GTK_WIDGET( child->data );
121             _cell._destroyId.connect( G_OBJECT(widget), "destroy", G_CALLBACK( childDestroyNotifyEvent ), this );
122 
123             updateCellViewColor();
124 
125         }
126 
127         if( children ) g_list_free( children );
128         return;
129 
130     }
131 
132     //________________________________________________________________________________
updateCellViewColor(void) const133     void ComboBoxData::updateCellViewColor( void ) const
134     {
135         // change background color
136         if( _cell._widget )
137         { gtk_cell_view_set_background_color( GTK_CELL_VIEW( _cell._widget ), 0L ); }
138     }
139 
140     //________________________________________________________________________________
updateButtonEventWindow(void) const141     void ComboBoxData::updateButtonEventWindow( void ) const
142     {
143 
144         // store local pointer to relevant widget
145         GtkWidget* widget( _button._widget );
146 
147         // check validity and type
148         if( !( widget && GTK_IS_BUTTON( widget ) ) ) return;
149 
150         // get window
151         #if GTK_CHECK_VERSION(2, 22, 0)
152         GdkWindow* window( gtk_button_get_event_window( GTK_BUTTON( widget ) ) );
153         #else
154         GdkWindow* window( GTK_BUTTON( widget )->event_window ) ;
155         #endif
156 
157         if( !window ) return;
158 
159         // offset
160         /* TODO: we should get it from the x-thickness property of the GtkFrame for this combobox */
161         const int offset = 4;
162 
163         // get allocation
164         const GtkAllocation allocation( Gtk::gtk_widget_get_allocation( widget ) );
165         gdk_window_move_resize( window, allocation.x-offset, allocation.y, allocation.width+offset, allocation.height );
166 
167     }
168 
169     //________________________________________________________________________________
setPressed(GtkWidget * widget,bool value)170     void ComboBoxData::setPressed( GtkWidget* widget, bool value )
171     {
172         const bool oldPressed( pressed() );
173         if( widget == _button._widget ) _button._pressed = value;
174         else return;
175 
176         if( oldPressed != pressed() && _target ) gtk_widget_queue_draw( _target );
177 
178     }
179 
180     //________________________________________________________________________________
setHovered(GtkWidget * widget,bool value)181     void ComboBoxData::setHovered( GtkWidget* widget, bool value )
182     {
183 
184         bool oldHover( hovered() );
185         HoverDataMap::iterator iter( _hoverData.find( widget ) );
186         if( iter != _hoverData.end() ) iter->second._hovered = value;
187         else return;
188 
189         // need to schedule repaint of the whole widget
190         if( oldHover != hovered() && _target ) gtk_widget_queue_draw( _target );
191 
192     }
193 
194     //________________________________________________________________________________
registerChild(GtkWidget * widget,bool recursive)195     void ComboBoxData::registerChild( GtkWidget* widget, bool recursive )
196     {
197 
198         // make sure widget is not already in map
199         if( _hoverData.find( widget ) == _hoverData.end() )
200         {
201 
202             #if OXYGEN_DEBUG
203             std::cerr
204                 << "Oxygen::ComboBoxData::registerChild -"
205                 << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
206                 << std::endl;
207             #endif
208 
209             // allocate new Hover data
210             HoverData data;
211             data._widget = widget;
212             data._destroyId.connect( G_OBJECT(widget), "destroy", G_CALLBACK( childDestroyNotifyEvent ), this );
213             data._enterId.connect( G_OBJECT(widget), "enter-notify-event", G_CALLBACK( enterNotifyEvent ), this );
214             data._leaveId.connect( G_OBJECT(widget), "leave-notify-event", G_CALLBACK( leaveNotifyEvent ), this );
215 
216             // and insert in map
217             _hoverData.insert( std::make_pair( widget, data ) );
218 
219         }
220 
221         /*
222         also insert widget's children, recursively.
223         that should take care of buttons in tabs and other fancy stuff that applications mght do
224         */
225         if( recursive && GTK_IS_CONTAINER( widget ) )
226         {
227 
228             GList *children( gtk_container_get_children( GTK_CONTAINER(widget) ) );
229             for( GList* child = g_list_first(children); child; child = g_list_next(child) )
230             { registerChild( GTK_WIDGET( child->data ) ); }
231 
232             if( children ) g_list_free( children );
233         }
234 
235     }
236 
237     //________________________________________________________________________________
unregisterChild(GtkWidget * widget)238     void ComboBoxData::unregisterChild( GtkWidget* widget )
239     {
240 
241         #if OXYGEN_DEBUG
242         std::cerr
243             << "Oxygen::ComboBoxData::unregisterChild -"
244             << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
245             << std::endl;
246         #endif
247 
248         // see if widget is button or cell
249         if( widget == _button._widget ) _button.disconnect();
250         if( widget == _cell._widget ) _cell.disconnect();
251 
252         // loopup in hover map
253         HoverDataMap::iterator iter( _hoverData.find( widget ) );
254         if( iter != _hoverData.end() )
255         {
256             iter->second.disconnect();
257             _hoverData.erase( iter );
258         }
259 
260     }
261 
262     //________________________________________________________________________________
disconnect(void)263     void ComboBoxData::ChildData::disconnect( void )
264     {
265 
266         if( !_widget ) return;
267 
268         #if OXYGEN_DEBUG
269         std::cerr
270             << "Oxygen::ComboBoxData::ChildData::disconnect -"
271             << " " << _widget << " (" << G_OBJECT_TYPE_NAME( _widget ) << ")"
272             << std::endl;
273         #endif
274 
275         _destroyId.disconnect();
276         _widget = 0L;
277     }
278 
279     //________________________________________________________________________________
disconnect(void)280     void ComboBoxData::ButtonData::disconnect( void )
281     {
282         if( !_widget ) return;
283         _toggledId.disconnect();
284         _sizeAllocateId.disconnect();
285         _pressed = false;
286         _focus = false;
287 
288         // base class
289         ChildData::disconnect();
290     }
291 
292     //________________________________________________________________________________
disconnect(void)293     void ComboBoxData::HoverData::disconnect( void )
294     {
295         if( !_widget ) return;
296 
297         #if OXYGEN_DEBUG
298         std::cerr << "Oxygen::ComboBoxData::HoverData::disconnect -"
299             << " " << _widget << " (" << G_OBJECT_TYPE_NAME( _widget ) << ")"
300             << std::endl;
301         #endif
302 
303         _enterId.disconnect();
304         _leaveId.disconnect();
305         _hovered = false;
306 
307         // base class
308         ChildData::disconnect();
309     }
310 
311     //____________________________________________________________________________________________
childDestroyNotifyEvent(GtkWidget * widget,gpointer data)312     gboolean ComboBoxData::childDestroyNotifyEvent( GtkWidget* widget, gpointer data )
313     {
314         #if OXYGEN_DEBUG
315         std::cerr
316             << "Oxygen::ComboBoxData::childDestroyNotifyEvent -"
317             << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
318             << std::endl;
319         #endif
320         static_cast<ComboBoxData*>(data)->unregisterChild( widget );
321         return FALSE;
322     }
323 
324     //____________________________________________________________________________________________
childToggledEvent(GtkWidget * widget,gpointer data)325     void ComboBoxData::childToggledEvent( GtkWidget* widget, gpointer data)
326     {
327         if( GTK_IS_TOGGLE_BUTTON( widget ) )
328         { static_cast<ComboBoxData*>(data)->setPressed( widget, gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( widget ) ) ); }
329         return;
330     }
331 
332     //____________________________________________________________________________________________
childSizeAllocateEvent(GtkWidget * widget,GtkAllocation * allocation,gpointer data)333     void ComboBoxData::childSizeAllocateEvent( GtkWidget* widget, GtkAllocation* allocation, gpointer data)
334     {
335 
336         static_cast<ComboBoxData*>(data)->updateButtonEventWindow();
337         return;
338     }
339 
340     //________________________________________________________________________________
enterNotifyEvent(GtkWidget * widget,GdkEventCrossing *,gpointer data)341     gboolean ComboBoxData::enterNotifyEvent( GtkWidget* widget, GdkEventCrossing*, gpointer data )
342     {
343 
344         #if OXYGEN_DEBUG
345         std::cerr << "Oxygen::ComboBoxData::enterNotifyEvent -"
346             << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
347             << std::endl;
348         #endif
349 
350         static_cast<ComboBoxData*>( data )->setHovered( widget, true );
351         return FALSE;
352     }
353 
354     //________________________________________________________________________________
leaveNotifyEvent(GtkWidget * widget,GdkEventCrossing *,gpointer data)355     gboolean ComboBoxData::leaveNotifyEvent( GtkWidget* widget, GdkEventCrossing*, gpointer data )
356     {
357 
358         #if OXYGEN_DEBUG
359         std::cerr << "Oxygen::ComboBoxData::leaveNotifyEvent -"
360             << " " << widget << " (" << G_OBJECT_TYPE_NAME( widget ) << ")"
361             << std::endl;
362         #endif
363 
364         static_cast<ComboBoxData*>( data )->setHovered( widget, false );
365         return FALSE;
366     }
367 
368     //________________________________________________________________________________
stateChangeEvent(GtkWidget *,GtkStateType,gpointer data)369     void ComboBoxData::stateChangeEvent( GtkWidget*, GtkStateType, gpointer data )
370     { static_cast<ComboBoxData*>( data )->updateCellViewColor(); }
371 
372     //________________________________________________________________________________
styleSetEvent(GtkWidget *,GtkStyle *,gpointer data)373     void ComboBoxData::styleSetEvent( GtkWidget*, GtkStyle*, gpointer data )
374     { static_cast<ComboBoxData*>( data )->updateCellViewColor(); }
375 
376 }
377