1 ///////////////////////////////////////////////////////////////////////////// 
   2 // Name:        src/gtk/choice.cpp 
   4 // Author:      Robert Roebling 
   6 // Copyright:   (c) 1998 Robert Roebling 
   7 // Licence:     wxWindows licence 
   8 ///////////////////////////////////////////////////////////////////////////// 
  10 #include "wx/wxprec.h" 
  14 #include "wx/choice.h" 
  15 #include "wx/arrstr.h" 
  17 // FIXME: We use GtkOptionMenu which has been deprecated since GTK+ 2.3.0 in 
  18 // favour of GtkComboBox. 
  19 // Later use GtkComboBox if GTK+ runtime version is new enough. 
  20 #include <gtk/gtkversion.h> 
  21 #if defined(GTK_DISABLE_DEPRECATED) && GTK_CHECK_VERSION(2,3,0) 
  22 #undef GTK_DISABLE_DEPRECATED 
  25 #include "wx/gtk/private.h" 
  27 //----------------------------------------------------------------------------- 
  29 //----------------------------------------------------------------------------- 
  31 extern bool   g_blockEventsOnDrag
; 
  33 //----------------------------------------------------------------------------- 
  35 //----------------------------------------------------------------------------- 
  38 static void gtk_choice_clicked_callback( GtkWidget 
*WXUNUSED(widget
), wxChoice 
*choice 
) 
  41       wxapp_install_idle_handler(); 
  43     if (!choice
->m_hasVMT
) return; 
  45     if (g_blockEventsOnDrag
) return; 
  47     int selection 
= wxNOT_FOUND
; 
  49     selection 
= gtk_option_menu_get_history( GTK_OPTION_MENU(choice
->GetHandle()) ); 
  51     choice
->m_selection_hack 
= selection
; 
  53     wxCommandEvent 
event(wxEVT_COMMAND_CHOICE_SELECTED
, choice
->GetId() ); 
  54     int n 
= choice
->GetSelection(); 
  57     event
.SetString( choice
->GetStringSelection() ); 
  58     event
.SetEventObject(choice
); 
  60     if ( choice
->HasClientObjectData() ) 
  61         event
.SetClientObject( choice
->GetClientObject(n
) ); 
  62     else if ( choice
->HasClientUntypedData() ) 
  63         event
.SetClientData( choice
->GetClientData(n
) ); 
  65     choice
->GetEventHandler()->ProcessEvent(event
); 
  69 //----------------------------------------------------------------------------- 
  71 //----------------------------------------------------------------------------- 
  73 IMPLEMENT_DYNAMIC_CLASS(wxChoice
,wxControl
) 
  77     m_strings 
= (wxSortedArrayString 
*)NULL
; 
  80 bool wxChoice::Create( wxWindow 
*parent
, wxWindowID id
, 
  81                        const wxPoint 
&pos
, const wxSize 
&size
, 
  82                        const wxArrayString
& choices
, 
  83                        long style
, const wxValidator
& validator
, 
  84                        const wxString 
&name 
) 
  86     wxCArrayString 
chs(choices
); 
  88     return Create( parent
, id
, pos
, size
, chs
.GetCount(), chs
.GetStrings(), 
  89                    style
, validator
, name 
); 
  92 bool wxChoice::Create( wxWindow 
*parent
, wxWindowID id
, 
  93                        const wxPoint 
&pos
, const wxSize 
&size
, 
  94                        int n
, const wxString choices
[], 
  95                        long style
, const wxValidator
& validator
, const wxString 
&name 
) 
  98 #if (GTK_MINOR_VERSION > 0) 
  99     m_acceptsFocus 
= true; 
 102     if (!PreCreation( parent
, pos
, size 
) || 
 103         !CreateBase( parent
, id
, pos
, size
, style
, validator
, name 
)) 
 105         wxFAIL_MSG( wxT("wxChoice creation failed") ); 
 109     m_widget 
= gtk_option_menu_new(); 
 111     if ( style 
& wxCB_SORT 
) 
 113         // if our m_strings != NULL, DoAppend() will check for it and insert 
 114         // items in the correct order 
 115         m_strings 
= new wxSortedArrayString
; 
 118     // begin with no selection 
 119     m_selection_hack 
= wxNOT_FOUND
; 
 121     GtkWidget 
*menu 
= gtk_menu_new(); 
 123     for (unsigned int i 
= 0; i 
< (unsigned int)n
; i
++) 
 125         GtkAddHelper(menu
, i
, choices
[i
]); 
 128     gtk_option_menu_set_menu( GTK_OPTION_MENU(m_widget
), menu 
); 
 130     m_parent
->DoAddChild( this ); 
 133     SetBestSize(size
); // need this too because this is a wxControlWithItems 
 138 wxChoice::~wxChoice() 
 145 int wxChoice::DoAppend( const wxString 
&item 
) 
 147     wxCHECK_MSG( m_widget 
!= NULL
, -1, wxT("invalid choice control") ); 
 149     GtkWidget 
*menu 
= gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget
) ); 
 151     return GtkAddHelper(menu
, GetCount(), item
); 
 154 int wxChoice::DoInsert(const wxString 
&item
, unsigned int pos
) 
 156     wxCHECK_MSG( m_widget 
!= NULL
, -1, wxT("invalid choice control") ); 
 157     wxCHECK_MSG( IsValidInsert(pos
), -1, wxT("invalid index")); 
 159     if (pos 
== GetCount()) 
 160         return DoAppend(item
); 
 162     GtkWidget 
*menu 
= gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget
) ); 
 164     // if the item to insert is at or before the selection, and the selection is valid 
 165     if (((int)pos 
<= m_selection_hack
) && (m_selection_hack 
!= wxNOT_FOUND
)) 
 167         // move the selection forward one 
 171     return GtkAddHelper(menu
, pos
, item
); 
 174 void wxChoice::DoSetItemClientData(unsigned int n
, void* clientData
) 
 176     wxCHECK_RET( m_widget 
!= NULL
, wxT("invalid choice control") ); 
 178     wxList::compatibility_iterator node 
= m_clientList
.Item( n 
); 
 179     wxCHECK_RET( node
, wxT("invalid index in wxChoice::DoSetItemClientData") ); 
 181     node
->SetData( (wxObject
*) clientData 
); 
 184 void* wxChoice::DoGetItemClientData(unsigned int n
) const 
 186     wxCHECK_MSG( m_widget 
!= NULL
, NULL
, wxT("invalid choice control") ); 
 188     wxList::compatibility_iterator node 
= m_clientList
.Item( n 
); 
 189     wxCHECK_MSG( node
, NULL
, wxT("invalid index in wxChoice::DoGetItemClientData") ); 
 191     return node
->GetData(); 
 194 void wxChoice::DoSetItemClientObject(unsigned int n
, wxClientData
* clientData
) 
 196     wxCHECK_RET( m_widget 
!= NULL
, wxT("invalid choice control") ); 
 198     wxList::compatibility_iterator node 
= m_clientList
.Item( n 
); 
 199     wxCHECK_RET( node
, wxT("invalid index in wxChoice::DoSetItemClientObject") ); 
 201     // wxItemContainer already deletes data for us 
 203     node
->SetData( (wxObject
*) clientData 
); 
 206 wxClientData
* wxChoice::DoGetItemClientObject(unsigned int n
) const 
 208     wxCHECK_MSG( m_widget 
!= NULL
, (wxClientData
*) NULL
, wxT("invalid choice control") ); 
 210     wxList::compatibility_iterator node 
= m_clientList
.Item( n 
); 
 211     wxCHECK_MSG( node
, (wxClientData 
*)NULL
, 
 212                  wxT("invalid index in wxChoice::DoGetItemClientObject") ); 
 214     return (wxClientData
*) node
->GetData(); 
 217 void wxChoice::Clear() 
 219     wxCHECK_RET( m_widget 
!= NULL
, wxT("invalid choice") ); 
 221     gtk_option_menu_remove_menu( GTK_OPTION_MENU(m_widget
) ); 
 222     GtkWidget 
*menu 
= gtk_menu_new(); 
 223     gtk_option_menu_set_menu( GTK_OPTION_MENU(m_widget
), menu 
); 
 225     if ( HasClientObjectData() ) 
 227         // destroy the data (due to Robert's idea of using wxList<wxObject> 
 228         // and not wxList<wxClientData> we can't just say 
 229         // m_clientList.DeleteContents(true) - this would crash! 
 230         wxList::compatibility_iterator node 
= m_clientList
.GetFirst(); 
 233             delete (wxClientData 
*)node
->GetData(); 
 234             node 
= node
->GetNext(); 
 237     m_clientList
.Clear(); 
 242     // begin with no selection 
 243     m_selection_hack 
= wxNOT_FOUND
; 
 246 void wxChoice::Delete(unsigned int n
) 
 248     wxCHECK_RET( m_widget 
!= NULL
, wxT("invalid choice") ); 
 249     wxCHECK_RET( IsValid(n
), _T("invalid index in wxChoice::Delete") ); 
 251     // VZ: apparently GTK+ doesn't have a built-in function to do it (not even 
 252     //     in 2.0), hence this dumb implementation -- still better than nothing 
 254     const unsigned int count 
= GetCount(); 
 256     // if the item to delete is before the selection, and the selection is valid 
 257     if (((int)n 
< m_selection_hack
) && (m_selection_hack 
!= wxNOT_FOUND
)) 
 259         // move the selection back one 
 262     else if ((int)n 
== m_selection_hack
) 
 264         // invalidate the selection 
 265         m_selection_hack 
= wxNOT_FOUND
; 
 268     const bool hasClientData 
= m_clientDataItemsType 
!= wxClientData_None
; 
 269     const bool hasObjectData 
= m_clientDataItemsType 
== wxClientData_Object
; 
 271     wxList::compatibility_iterator node 
= m_clientList
.GetFirst(); 
 274     wxArrayPtrVoid itemsData
; 
 276     for ( i 
= 0; i 
< count
; i
++ ) 
 280             items
.Add(GetString(i
)); 
 283                 // also save the client data 
 284                 itemsData
.Add(node
->GetData()); 
 287         else // need to delete the client object too 
 291                 delete (wxClientData 
*)node
->GetData(); 
 297             node 
= node
->GetNext(); 
 303         // prevent Clear() from destroying all client data 
 304         m_clientDataItemsType 
= wxClientData_None
; 
 309     for ( i 
= 0; i 
< count 
- 1; i
++ ) 
 314             SetClientObject(i
, (wxClientData 
*)itemsData
[i
]); 
 315         else if ( hasClientData 
) 
 316             SetClientData(i
, itemsData
[i
]); 
 320 int wxChoice::FindString( const wxString 
&string
, bool bCase 
) const 
 322     wxCHECK_MSG( m_widget 
!= NULL
, wxNOT_FOUND
, wxT("invalid choice") ); 
 324     // If you read this code once and you think you understand 
 325     // it, then you are very wrong. Robert Roebling. 
 327     GtkMenuShell 
*menu_shell 
= GTK_MENU_SHELL( gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget
) ) ); 
 329     GList 
*child 
= menu_shell
->children
; 
 332         GtkBin 
*bin 
= GTK_BIN( child
->data 
); 
 333         GtkLabel 
*label 
= (GtkLabel 
*) NULL
; 
 335             label 
= GTK_LABEL(bin
->child
); 
 337             label 
= GTK_LABEL(GTK_BIN(m_widget
)->child
); 
 339         wxASSERT_MSG( label 
!= NULL 
, wxT("wxChoice: invalid label") ); 
 341          wxString 
tmp( wxGTK_CONV_BACK( gtk_label_get_text( label
) ) ); 
 342         if (string
.IsSameAs( tmp
, bCase 
)) 
 352 int wxChoice::GetSelection() const 
 354     wxCHECK_MSG( m_widget 
!= NULL
, wxNOT_FOUND
, wxT("invalid choice") ); 
 356     return m_selection_hack
; 
 360 void wxChoice::SetString(unsigned int n
, const wxString
& str
) 
 362     wxCHECK_RET( m_widget 
!= NULL
, wxT("invalid choice") ); 
 364     GtkMenuShell 
*menu_shell 
= GTK_MENU_SHELL( gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget
) ) ); 
 365     unsigned int count 
= 0; 
 366     GList 
*child 
= menu_shell
->children
; 
 369         GtkBin 
*bin 
= GTK_BIN( child
->data 
); 
 372             GtkLabel 
*label 
= (GtkLabel 
*) NULL
; 
 374                 label 
= GTK_LABEL(bin
->child
); 
 376                 label 
= GTK_LABEL(GTK_BIN(m_widget
)->child
); 
 378             wxASSERT_MSG( label 
!= NULL 
, wxT("wxChoice: invalid label") ); 
 380             gtk_label_set_text( label
, wxGTK_CONV( str 
) ); 
 389 wxString 
wxChoice::GetString(unsigned int n
) const 
 391     wxCHECK_MSG( m_widget 
!= NULL
, wxEmptyString
, wxT("invalid choice") ); 
 393     GtkMenuShell 
*menu_shell 
= GTK_MENU_SHELL( gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget
) ) ); 
 394     unsigned int count 
= 0; 
 395     GList 
*child 
= menu_shell
->children
; 
 398         GtkBin 
*bin 
= GTK_BIN( child
->data 
); 
 401             GtkLabel 
*label 
= (GtkLabel 
*) NULL
; 
 403                 label 
= GTK_LABEL(bin
->child
); 
 405                 label 
= GTK_LABEL(GTK_BIN(m_widget
)->child
); 
 407             wxASSERT_MSG( label 
!= NULL 
, wxT("wxChoice: invalid label") ); 
 409             return wxString( wxGTK_CONV_BACK( gtk_label_get_text( label
) ) ); 
 415     wxFAIL_MSG( wxT("wxChoice: invalid index in GetString()") ); 
 417     return wxEmptyString
; 
 420 unsigned int wxChoice::GetCount() const 
 422     wxCHECK_MSG( m_widget 
!= NULL
, 0, wxT("invalid choice") ); 
 424     GtkMenuShell 
*menu_shell 
= GTK_MENU_SHELL( gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget
) ) ); 
 425     unsigned int count 
= 0; 
 426     GList 
*child 
= menu_shell
->children
; 
 435 void wxChoice::SetSelection( int n 
) 
 437     wxCHECK_RET( m_widget 
!= NULL
, wxT("invalid choice") ); 
 440     gtk_option_menu_set_history( GTK_OPTION_MENU(m_widget
), (gint
)tmp 
); 
 442     // set the local selection variable manually 
 443     if ((n 
>= 0) && ((unsigned int)n 
< GetCount())) 
 445         // a valid selection has been made 
 446         m_selection_hack 
= n
; 
 448     else if ((n 
== wxNOT_FOUND
) || (GetCount() == 0)) 
 450         // invalidates the selection if there are no items 
 451         // or if it is specifically set to wxNOT_FOUND 
 452         m_selection_hack 
= wxNOT_FOUND
; 
 456         // this selects the first item by default if the selection is out of bounds 
 457         m_selection_hack 
= 0; 
 461 void wxChoice::DoApplyWidgetStyle(GtkRcStyle 
*style
) 
 463     GtkMenuShell 
*menu_shell 
= GTK_MENU_SHELL( gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget
) ) ); 
 465     gtk_widget_modify_style( m_widget
, style 
); 
 466     gtk_widget_modify_style( GTK_WIDGET( menu_shell 
), style 
); 
 468     GList 
*child 
= menu_shell
->children
; 
 471         gtk_widget_modify_style( GTK_WIDGET( child
->data 
), style 
); 
 473         GtkBin 
*bin 
= GTK_BIN( child
->data 
); 
 474         GtkWidget 
*label 
= (GtkWidget 
*) NULL
; 
 478             label 
= GTK_BIN(m_widget
)->child
; 
 480         gtk_widget_modify_style( label
, style 
); 
 486 int wxChoice::GtkAddHelper(GtkWidget 
*menu
, unsigned int pos
, const wxString
& item
) 
 488     wxCHECK_MSG(pos
<=m_clientList
.GetCount(), -1, wxT("invalid index")); 
 490     GtkWidget 
*menu_item 
= gtk_menu_item_new_with_label( wxGTK_CONV( item 
) ); 
 495         // sorted control, need to insert at the correct index 
 496         index 
= m_strings
->Add(item
); 
 498         gtk_menu_shell_insert( GTK_MENU_SHELL(menu
), menu_item
, index 
); 
 502             m_clientList
.Insert( m_clientList
.Item(index 
- 1), 
 507             m_clientList
.Insert( (wxObject
*) NULL 
); 
 512         // don't call wxChoice::GetCount() from here because it doesn't work 
 513         // if we're called from ctor (and GtkMenuShell is still NULL) 
 515         // normal control, just append 
 516         if (pos 
== m_clientList
.GetCount()) 
 518             gtk_menu_shell_append( GTK_MENU_SHELL(menu
), menu_item 
); 
 519             m_clientList
.Append( (wxObject
*) NULL 
); 
 520             index 
= m_clientList
.GetCount() - 1; 
 524             gtk_menu_shell_insert( GTK_MENU_SHELL(menu
), menu_item
, pos 
); 
 525             m_clientList
.Insert( pos
, (wxObject
*) NULL 
); 
 530     if (GTK_WIDGET_REALIZED(m_widget
)) 
 532         gtk_widget_realize( menu_item 
); 
 533         gtk_widget_realize( GTK_BIN(menu_item
)->child 
); 
 538     // The best size of a wxChoice should probably 
 539     // be changed everytime the control has been 
 540     // changed, but at least after adding an item 
 541     // it has to change. Adapted from Matt Ownby. 
 542     InvalidateBestSize(); 
 544     g_signal_connect_after (menu_item
, "activate", 
 545                             G_CALLBACK (gtk_choice_clicked_callback
), 
 548     gtk_widget_show( menu_item 
); 
 550     // return the index of the item in the control 
 554 wxSize 
wxChoice::DoGetBestSize() const 
 556     wxSize 
ret( wxControl::DoGetBestSize() ); 
 558     // we know better our horizontal extent: it depends on the longest string 
 564         unsigned int count 
= GetCount(); 
 565         for ( unsigned int n 
= 0; n 
< count
; n
++ ) 
 567             GetTextExtent( GetString(n
), &width
, NULL
, NULL
, NULL 
); 
 572         // add extra for the choice "=" button 
 574         // VZ: I don't know how to get the right value, it seems to be in 
 575         //     GtkOptionMenuProps struct from gtkoptionmenu.c but we can't get 
 576         //     to it - maybe we can use gtk_option_menu_size_request() for this 
 579         //     This default value works only for the default GTK+ theme (i.e. 
 580         //     no theme at all) (FIXME) 
 581         static const int widthChoiceIndicator 
= 35; 
 582         ret
.x 
+= widthChoiceIndicator
; 
 585     // but not less than the minimal width 
 589     // If this request_size is called with no entries then 
 590     // the returned height is wrong. Give it a reasonable 
 593         ret
.y 
= 8 + GetCharHeight(); 
 599 bool wxChoice::IsOwnGtkWindow( GdkWindow 
*window 
) 
 601     return GTK_BUTTON(m_widget
)->event_window
; 
 606 wxChoice::GetClassDefaultAttributes(wxWindowVariant 
WXUNUSED(variant
)) 
 608     return GetDefaultAttributesFromGTKWidget(gtk_option_menu_new
); 
 612 #endif // wxUSE_CHOICE