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" 
  17     #include "wx/arrstr.h" 
  20 // FIXME: We use GtkOptionMenu which has been deprecated since GTK+ 2.3.0 in 
  21 // favour of GtkComboBox. 
  22 // Later use GtkComboBox if GTK+ runtime version is new enough. 
  23 #include <gtk/gtkversion.h> 
  24 #if defined(GTK_DISABLE_DEPRECATED) && GTK_CHECK_VERSION(2,3,0) 
  25 #undef GTK_DISABLE_DEPRECATED 
  28 #include "wx/gtk/private.h" 
  30 //----------------------------------------------------------------------------- 
  32 //----------------------------------------------------------------------------- 
  34 extern bool   g_blockEventsOnDrag
; 
  36 //----------------------------------------------------------------------------- 
  38 //----------------------------------------------------------------------------- 
  41 static void gtk_choice_clicked_callback( GtkWidget 
*WXUNUSED(widget
), wxChoice 
*choice 
) 
  44       wxapp_install_idle_handler(); 
  46     if (!choice
->m_hasVMT
) return; 
  48     if (g_blockEventsOnDrag
) return; 
  50     int selection 
= wxNOT_FOUND
; 
  52     selection 
= gtk_option_menu_get_history( GTK_OPTION_MENU(choice
->GetHandle()) ); 
  54     choice
->m_selection_hack 
= selection
; 
  56     wxCommandEvent 
event(wxEVT_COMMAND_CHOICE_SELECTED
, choice
->GetId() ); 
  57     int n 
= choice
->GetSelection(); 
  60     event
.SetString( choice
->GetStringSelection() ); 
  61     event
.SetEventObject(choice
); 
  63     if ( choice
->HasClientObjectData() ) 
  64         event
.SetClientObject( choice
->GetClientObject(n
) ); 
  65     else if ( choice
->HasClientUntypedData() ) 
  66         event
.SetClientData( choice
->GetClientData(n
) ); 
  68     choice
->GetEventHandler()->ProcessEvent(event
); 
  72 //----------------------------------------------------------------------------- 
  74 //----------------------------------------------------------------------------- 
  76 IMPLEMENT_DYNAMIC_CLASS(wxChoice
,wxControl
) 
  80     m_strings 
= (wxSortedArrayString 
*)NULL
; 
  83 bool wxChoice::Create( wxWindow 
*parent
, wxWindowID id
, 
  84                        const wxPoint 
&pos
, const wxSize 
&size
, 
  85                        const wxArrayString
& choices
, 
  86                        long style
, const wxValidator
& validator
, 
  87                        const wxString 
&name 
) 
  89     wxCArrayString 
chs(choices
); 
  91     return Create( parent
, id
, pos
, size
, chs
.GetCount(), chs
.GetStrings(), 
  92                    style
, validator
, name 
); 
  95 bool wxChoice::Create( wxWindow 
*parent
, wxWindowID id
, 
  96                        const wxPoint 
&pos
, const wxSize 
&size
, 
  97                        int n
, const wxString choices
[], 
  98                        long style
, const wxValidator
& validator
, const wxString 
&name 
) 
 101 #if (GTK_MINOR_VERSION > 0) 
 102     m_acceptsFocus 
= true; 
 105     if (!PreCreation( parent
, pos
, size 
) || 
 106         !CreateBase( parent
, id
, pos
, size
, style
, validator
, name 
)) 
 108         wxFAIL_MSG( wxT("wxChoice creation failed") ); 
 112     m_widget 
= gtk_option_menu_new(); 
 114     if ( style 
& wxCB_SORT 
) 
 116         // if our m_strings != NULL, DoAppend() will check for it and insert 
 117         // items in the correct order 
 118         m_strings 
= new wxSortedArrayString
; 
 121     // begin with no selection 
 122     m_selection_hack 
= wxNOT_FOUND
; 
 124     GtkWidget 
*menu 
= gtk_menu_new(); 
 126     for (unsigned int i 
= 0; i 
< (unsigned int)n
; i
++) 
 128         GtkAddHelper(menu
, i
, choices
[i
]); 
 131     gtk_option_menu_set_menu( GTK_OPTION_MENU(m_widget
), menu 
); 
 133     m_parent
->DoAddChild( this ); 
 136     SetBestSize(size
); // need this too because this is a wxControlWithItems 
 141 wxChoice::~wxChoice() 
 148 int wxChoice::DoAppend( const wxString 
&item 
) 
 150     wxCHECK_MSG( m_widget 
!= NULL
, -1, wxT("invalid choice control") ); 
 152     GtkWidget 
*menu 
= gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget
) ); 
 154     return GtkAddHelper(menu
, GetCount(), item
); 
 157 int wxChoice::DoInsert(const wxString 
&item
, unsigned int pos
) 
 159     wxCHECK_MSG( m_widget 
!= NULL
, -1, wxT("invalid choice control") ); 
 160     wxCHECK_MSG( IsValidInsert(pos
), -1, wxT("invalid index")); 
 162     if (pos 
== GetCount()) 
 163         return DoAppend(item
); 
 165     GtkWidget 
*menu 
= gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget
) ); 
 167     // if the item to insert is at or before the selection, and the selection is valid 
 168     if (((int)pos 
<= m_selection_hack
) && (m_selection_hack 
!= wxNOT_FOUND
)) 
 170         // move the selection forward one 
 174     return GtkAddHelper(menu
, pos
, item
); 
 177 void wxChoice::DoSetItemClientData(unsigned int n
, void* clientData
) 
 179     wxCHECK_RET( m_widget 
!= NULL
, wxT("invalid choice control") ); 
 181     wxList::compatibility_iterator node 
= m_clientList
.Item( n 
); 
 182     wxCHECK_RET( node
, wxT("invalid index in wxChoice::DoSetItemClientData") ); 
 184     node
->SetData( (wxObject
*) clientData 
); 
 187 void* wxChoice::DoGetItemClientData(unsigned int n
) const 
 189     wxCHECK_MSG( m_widget 
!= NULL
, NULL
, wxT("invalid choice control") ); 
 191     wxList::compatibility_iterator node 
= m_clientList
.Item( n 
); 
 192     wxCHECK_MSG( node
, NULL
, wxT("invalid index in wxChoice::DoGetItemClientData") ); 
 194     return node
->GetData(); 
 197 void wxChoice::DoSetItemClientObject(unsigned int n
, wxClientData
* clientData
) 
 199     wxCHECK_RET( m_widget 
!= NULL
, wxT("invalid choice control") ); 
 201     wxList::compatibility_iterator node 
= m_clientList
.Item( n 
); 
 202     wxCHECK_RET( node
, wxT("invalid index in wxChoice::DoSetItemClientObject") ); 
 204     // wxItemContainer already deletes data for us 
 206     node
->SetData( (wxObject
*) clientData 
); 
 209 wxClientData
* wxChoice::DoGetItemClientObject(unsigned int n
) const 
 211     wxCHECK_MSG( m_widget 
!= NULL
, (wxClientData
*) NULL
, wxT("invalid choice control") ); 
 213     wxList::compatibility_iterator node 
= m_clientList
.Item( n 
); 
 214     wxCHECK_MSG( node
, (wxClientData 
*)NULL
, 
 215                  wxT("invalid index in wxChoice::DoGetItemClientObject") ); 
 217     return (wxClientData
*) node
->GetData(); 
 220 void wxChoice::Clear() 
 222     wxCHECK_RET( m_widget 
!= NULL
, wxT("invalid choice") ); 
 224     gtk_option_menu_remove_menu( GTK_OPTION_MENU(m_widget
) ); 
 225     GtkWidget 
*menu 
= gtk_menu_new(); 
 226     gtk_option_menu_set_menu( GTK_OPTION_MENU(m_widget
), menu 
); 
 228     if ( HasClientObjectData() ) 
 230         // destroy the data (due to Robert's idea of using wxList<wxObject> 
 231         // and not wxList<wxClientData> we can't just say 
 232         // m_clientList.DeleteContents(true) - this would crash! 
 233         wxList::compatibility_iterator node 
= m_clientList
.GetFirst(); 
 236             delete (wxClientData 
*)node
->GetData(); 
 237             node 
= node
->GetNext(); 
 240     m_clientList
.Clear(); 
 245     // begin with no selection 
 246     m_selection_hack 
= wxNOT_FOUND
; 
 249 void wxChoice::Delete(unsigned int n
) 
 251     wxCHECK_RET( m_widget 
!= NULL
, wxT("invalid choice") ); 
 252     wxCHECK_RET( IsValid(n
), _T("invalid index in wxChoice::Delete") ); 
 254     // VZ: apparently GTK+ doesn't have a built-in function to do it (not even 
 255     //     in 2.0), hence this dumb implementation -- still better than nothing 
 257     const unsigned int count 
= GetCount(); 
 259     // if the item to delete is before the selection, and the selection is valid 
 260     if (((int)n 
< m_selection_hack
) && (m_selection_hack 
!= wxNOT_FOUND
)) 
 262         // move the selection back one 
 265     else if ((int)n 
== m_selection_hack
) 
 267         // invalidate the selection 
 268         m_selection_hack 
= wxNOT_FOUND
; 
 271     const bool hasClientData 
= m_clientDataItemsType 
!= wxClientData_None
; 
 272     const bool hasObjectData 
= m_clientDataItemsType 
== wxClientData_Object
; 
 274     wxList::compatibility_iterator node 
= m_clientList
.GetFirst(); 
 277     wxArrayPtrVoid itemsData
; 
 279     for ( i 
= 0; i 
< count
; i
++ ) 
 283             items
.Add(GetString(i
)); 
 286                 // also save the client data 
 287                 itemsData
.Add(node
->GetData()); 
 290         else // need to delete the client object too 
 294                 delete (wxClientData 
*)node
->GetData(); 
 300             node 
= node
->GetNext(); 
 306         // prevent Clear() from destroying all client data 
 307         m_clientDataItemsType 
= wxClientData_None
; 
 312     for ( i 
= 0; i 
< count 
- 1; i
++ ) 
 317             SetClientObject(i
, (wxClientData 
*)itemsData
[i
]); 
 318         else if ( hasClientData 
) 
 319             SetClientData(i
, itemsData
[i
]); 
 323 int wxChoice::FindString( const wxString 
&string
, bool bCase 
) const 
 325     wxCHECK_MSG( m_widget 
!= NULL
, wxNOT_FOUND
, wxT("invalid choice") ); 
 327     // If you read this code once and you think you understand 
 328     // it, then you are very wrong. Robert Roebling. 
 330     GtkMenuShell 
*menu_shell 
= GTK_MENU_SHELL( gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget
) ) ); 
 332     GList 
*child 
= menu_shell
->children
; 
 335         GtkBin 
*bin 
= GTK_BIN( child
->data 
); 
 336         GtkLabel 
*label 
= (GtkLabel 
*) NULL
; 
 338             label 
= GTK_LABEL(bin
->child
); 
 340             label 
= GTK_LABEL(GTK_BIN(m_widget
)->child
); 
 342         wxASSERT_MSG( label 
!= NULL 
, wxT("wxChoice: invalid label") ); 
 344          wxString 
tmp( wxGTK_CONV_BACK( gtk_label_get_text( label
) ) ); 
 345         if (string
.IsSameAs( tmp
, bCase 
)) 
 355 int wxChoice::GetSelection() const 
 357     wxCHECK_MSG( m_widget 
!= NULL
, wxNOT_FOUND
, wxT("invalid choice") ); 
 359     return m_selection_hack
; 
 363 void wxChoice::SetString(unsigned int n
, const wxString
& str
) 
 365     wxCHECK_RET( m_widget 
!= NULL
, wxT("invalid choice") ); 
 367     GtkMenuShell 
*menu_shell 
= GTK_MENU_SHELL( gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget
) ) ); 
 368     unsigned int count 
= 0; 
 369     GList 
*child 
= menu_shell
->children
; 
 372         GtkBin 
*bin 
= GTK_BIN( child
->data 
); 
 375             GtkLabel 
*label 
= (GtkLabel 
*) NULL
; 
 377                 label 
= GTK_LABEL(bin
->child
); 
 379                 label 
= GTK_LABEL(GTK_BIN(m_widget
)->child
); 
 381             wxASSERT_MSG( label 
!= NULL 
, wxT("wxChoice: invalid label") ); 
 383             gtk_label_set_text( label
, wxGTK_CONV( str 
) ); 
 392 wxString 
wxChoice::GetString(unsigned int n
) const 
 394     wxCHECK_MSG( m_widget 
!= NULL
, wxEmptyString
, wxT("invalid choice") ); 
 396     GtkMenuShell 
*menu_shell 
= GTK_MENU_SHELL( gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget
) ) ); 
 397     unsigned int count 
= 0; 
 398     GList 
*child 
= menu_shell
->children
; 
 401         GtkBin 
*bin 
= GTK_BIN( child
->data 
); 
 404             GtkLabel 
*label 
= (GtkLabel 
*) NULL
; 
 406                 label 
= GTK_LABEL(bin
->child
); 
 408                 label 
= GTK_LABEL(GTK_BIN(m_widget
)->child
); 
 410             wxASSERT_MSG( label 
!= NULL 
, wxT("wxChoice: invalid label") ); 
 412             return wxString( wxGTK_CONV_BACK( gtk_label_get_text( label
) ) ); 
 418     wxFAIL_MSG( wxT("wxChoice: invalid index in GetString()") ); 
 420     return wxEmptyString
; 
 423 unsigned int wxChoice::GetCount() const 
 425     wxCHECK_MSG( m_widget 
!= NULL
, 0, wxT("invalid choice") ); 
 427     GtkMenuShell 
*menu_shell 
= GTK_MENU_SHELL( gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget
) ) ); 
 428     unsigned int count 
= 0; 
 429     GList 
*child 
= menu_shell
->children
; 
 438 void wxChoice::SetSelection( int n 
) 
 440     wxCHECK_RET( m_widget 
!= NULL
, wxT("invalid choice") ); 
 443     gtk_option_menu_set_history( GTK_OPTION_MENU(m_widget
), (gint
)tmp 
); 
 445     // set the local selection variable manually 
 446     if ((n 
>= 0) && ((unsigned int)n 
< GetCount())) 
 448         // a valid selection has been made 
 449         m_selection_hack 
= n
; 
 451     else if ((n 
== wxNOT_FOUND
) || (GetCount() == 0)) 
 453         // invalidates the selection if there are no items 
 454         // or if it is specifically set to wxNOT_FOUND 
 455         m_selection_hack 
= wxNOT_FOUND
; 
 459         // this selects the first item by default if the selection is out of bounds 
 460         m_selection_hack 
= 0; 
 464 void wxChoice::DoApplyWidgetStyle(GtkRcStyle 
*style
) 
 466     GtkMenuShell 
*menu_shell 
= GTK_MENU_SHELL( gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget
) ) ); 
 468     gtk_widget_modify_style( m_widget
, style 
); 
 469     gtk_widget_modify_style( GTK_WIDGET( menu_shell 
), style 
); 
 471     GList 
*child 
= menu_shell
->children
; 
 474         gtk_widget_modify_style( GTK_WIDGET( child
->data 
), style 
); 
 476         GtkBin 
*bin 
= GTK_BIN( child
->data 
); 
 477         GtkWidget 
*label 
= (GtkWidget 
*) NULL
; 
 481             label 
= GTK_BIN(m_widget
)->child
; 
 483         gtk_widget_modify_style( label
, style 
); 
 489 int wxChoice::GtkAddHelper(GtkWidget 
*menu
, unsigned int pos
, const wxString
& item
) 
 491     wxCHECK_MSG(pos
<=m_clientList
.GetCount(), -1, wxT("invalid index")); 
 493     GtkWidget 
*menu_item 
= gtk_menu_item_new_with_label( wxGTK_CONV( item 
) ); 
 498         // sorted control, need to insert at the correct index 
 499         index 
= m_strings
->Add(item
); 
 501         gtk_menu_shell_insert( GTK_MENU_SHELL(menu
), menu_item
, index 
); 
 505             m_clientList
.Insert( m_clientList
.Item(index 
- 1), 
 510             m_clientList
.Insert( (wxObject
*) NULL 
); 
 515         // don't call wxChoice::GetCount() from here because it doesn't work 
 516         // if we're called from ctor (and GtkMenuShell is still NULL) 
 518         // normal control, just append 
 519         if (pos 
== m_clientList
.GetCount()) 
 521             gtk_menu_shell_append( GTK_MENU_SHELL(menu
), menu_item 
); 
 522             m_clientList
.Append( (wxObject
*) NULL 
); 
 523             index 
= m_clientList
.GetCount() - 1; 
 527             gtk_menu_shell_insert( GTK_MENU_SHELL(menu
), menu_item
, pos 
); 
 528             m_clientList
.Insert( pos
, (wxObject
*) NULL 
); 
 533     if (GTK_WIDGET_REALIZED(m_widget
)) 
 535         gtk_widget_realize( menu_item 
); 
 536         gtk_widget_realize( GTK_BIN(menu_item
)->child 
); 
 541     // The best size of a wxChoice should probably 
 542     // be changed everytime the control has been 
 543     // changed, but at least after adding an item 
 544     // it has to change. Adapted from Matt Ownby. 
 545     InvalidateBestSize(); 
 547     g_signal_connect_after (menu_item
, "activate", 
 548                             G_CALLBACK (gtk_choice_clicked_callback
), 
 551     gtk_widget_show( menu_item 
); 
 553     // return the index of the item in the control 
 557 wxSize 
wxChoice::DoGetBestSize() const 
 559     wxSize 
ret( wxControl::DoGetBestSize() ); 
 561     // we know better our horizontal extent: it depends on the longest string 
 567         unsigned int count 
= GetCount(); 
 568         for ( unsigned int n 
= 0; n 
< count
; n
++ ) 
 570             GetTextExtent( GetString(n
), &width
, NULL
, NULL
, NULL 
); 
 575         // add extra for the choice "=" button 
 577         // VZ: I don't know how to get the right value, it seems to be in 
 578         //     GtkOptionMenuProps struct from gtkoptionmenu.c but we can't get 
 579         //     to it - maybe we can use gtk_option_menu_size_request() for this 
 582         //     This default value works only for the default GTK+ theme (i.e. 
 583         //     no theme at all) (FIXME) 
 584         static const int widthChoiceIndicator 
= 35; 
 585         ret
.x 
+= widthChoiceIndicator
; 
 588     // but not less than the minimal width 
 592     // If this request_size is called with no entries then 
 593     // the returned height is wrong. Give it a reasonable 
 596         ret
.y 
= 8 + GetCharHeight(); 
 602 bool wxChoice::IsOwnGtkWindow( GdkWindow 
*window 
) 
 604     return GTK_BUTTON(m_widget
)->event_window
; 
 609 wxChoice::GetClassDefaultAttributes(wxWindowVariant 
WXUNUSED(variant
)) 
 611     return GetDefaultAttributesFromGTKWidget(gtk_option_menu_new
); 
 615 #endif // wxUSE_CHOICE