1///////////////////////////////////////////////////////////////////////////// 2// Name: src/gtk/choice.cpp 3// Purpose: 4// Author: Robert Roebling 5// Id: $Id: choice.cpp 54967 2008-08-04 15:33:13Z JS $ 6// Copyright: (c) 1998 Robert Roebling 7// Licence: wxWindows licence 8///////////////////////////////////////////////////////////////////////////// 9 10#include "wx/wxprec.h" 11 12#if wxUSE_CHOICE 13 14#include "wx/choice.h" 15 16#ifndef WX_PRECOMP 17 #include "wx/arrstr.h" 18#endif 19 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 26#endif 27 28#include "wx/gtk/private.h" 29 30//----------------------------------------------------------------------------- 31// data 32//----------------------------------------------------------------------------- 33 34extern bool g_blockEventsOnDrag; 35 36//----------------------------------------------------------------------------- 37// "activate" 38//----------------------------------------------------------------------------- 39 40extern "C" { 41static void gtk_choice_clicked_callback( GtkWidget *WXUNUSED(widget), wxChoice *choice ) 42{ 43 if (g_isIdle) 44 wxapp_install_idle_handler(); 45 46 if (!choice->m_hasVMT) return; 47 48 if (g_blockEventsOnDrag) return; 49 50 int selection = wxNOT_FOUND; 51 52 selection = gtk_option_menu_get_history( GTK_OPTION_MENU(choice->GetHandle()) ); 53 54 choice->m_selection_hack = selection; 55 56 wxCommandEvent event(wxEVT_COMMAND_CHOICE_SELECTED, choice->GetId() ); 57 int n = choice->GetSelection(); 58 59 event.SetInt( n ); 60 event.SetString( choice->GetStringSelection() ); 61 event.SetEventObject(choice); 62 63 if ( choice->HasClientObjectData() ) 64 event.SetClientObject( choice->GetClientObject(n) ); 65 else if ( choice->HasClientUntypedData() ) 66 event.SetClientData( choice->GetClientData(n) ); 67 68 choice->GetEventHandler()->ProcessEvent(event); 69} 70} 71 72//----------------------------------------------------------------------------- 73// wxChoice 74//----------------------------------------------------------------------------- 75 76IMPLEMENT_DYNAMIC_CLASS(wxChoice,wxControl) 77 78wxChoice::wxChoice() 79{ 80 m_strings = (wxSortedArrayString *)NULL; 81} 82 83bool 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 ) 88{ 89 wxCArrayString chs(choices); 90 91 return Create( parent, id, pos, size, chs.GetCount(), chs.GetStrings(), 92 style, validator, name ); 93} 94 95bool 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 ) 99{ 100 m_needParent = true; 101#if (GTK_MINOR_VERSION > 0) 102 m_acceptsFocus = true; 103#endif 104 105 if (!PreCreation( parent, pos, size ) || 106 !CreateBase( parent, id, pos, size, style, validator, name )) 107 { 108 wxFAIL_MSG( wxT("wxChoice creation failed") ); 109 return false; 110 } 111 112 m_widget = gtk_option_menu_new(); 113 114 if ( style & wxCB_SORT ) 115 { 116 // if our m_strings != NULL, DoAppend() will check for it and insert 117 // items in the correct order 118 m_strings = new wxSortedArrayString; 119 } 120 121 // If we have items, GTK will choose the first item by default 122 m_selection_hack = n > 0 ? 0 : wxNOT_FOUND; 123 124 GtkWidget *menu = gtk_menu_new(); 125 126 for (unsigned int i = 0; i < (unsigned int)n; i++) 127 { 128 GtkAddHelper(menu, i, choices[i]); 129 } 130 131 gtk_option_menu_set_menu( GTK_OPTION_MENU(m_widget), menu ); 132 133 m_parent->DoAddChild( this ); 134 135 PostCreation(size); 136 SetInitialSize(size); // need this too because this is a wxControlWithItems 137 138 return true; 139} 140 141wxChoice::~wxChoice() 142{ 143 Clear(); 144 145 delete m_strings; 146} 147 148int wxChoice::DoAppend( const wxString &item ) 149{ 150 wxCHECK_MSG( m_widget != NULL, -1, wxT("invalid choice control") ); 151 152 GtkWidget *menu = gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget) ); 153 154 return GtkAddHelper(menu, GetCount(), item); 155} 156 157int wxChoice::DoInsert(const wxString &item, unsigned int pos) 158{ 159 wxCHECK_MSG( m_widget != NULL, -1, wxT("invalid choice control") ); 160 wxCHECK_MSG( IsValidInsert(pos), -1, wxT("invalid index")); 161 162 if (pos == GetCount()) 163 return DoAppend(item); 164 165 GtkWidget *menu = gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget) ); 166 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)) 169 { 170 // move the selection forward one 171 m_selection_hack++; 172 } 173 174 return GtkAddHelper(menu, pos, item); 175} 176 177void wxChoice::DoSetItemClientData(unsigned int n, void* clientData) 178{ 179 wxCHECK_RET( m_widget != NULL, wxT("invalid choice control") ); 180 181 wxList::compatibility_iterator node = m_clientList.Item( n ); 182 wxCHECK_RET( node, wxT("invalid index in wxChoice::DoSetItemClientData") ); 183 184 node->SetData( (wxObject*) clientData ); 185} 186 187void* wxChoice::DoGetItemClientData(unsigned int n) const 188{ 189 wxCHECK_MSG( m_widget != NULL, NULL, wxT("invalid choice control") ); 190 191 wxList::compatibility_iterator node = m_clientList.Item( n ); 192 wxCHECK_MSG( node, NULL, wxT("invalid index in wxChoice::DoGetItemClientData") ); 193 194 return node->GetData(); 195} 196 197void wxChoice::DoSetItemClientObject(unsigned int n, wxClientData* clientData) 198{ 199 wxCHECK_RET( m_widget != NULL, wxT("invalid choice control") ); 200 201 wxList::compatibility_iterator node = m_clientList.Item( n ); 202 wxCHECK_RET( node, wxT("invalid index in wxChoice::DoSetItemClientObject") ); 203 204 // wxItemContainer already deletes data for us 205 206 node->SetData( (wxObject*) clientData ); 207} 208 209wxClientData* wxChoice::DoGetItemClientObject(unsigned int n) const 210{ 211 wxCHECK_MSG( m_widget != NULL, (wxClientData*) NULL, wxT("invalid choice control") ); 212 213 wxList::compatibility_iterator node = m_clientList.Item( n ); 214 wxCHECK_MSG( node, (wxClientData *)NULL, 215 wxT("invalid index in wxChoice::DoGetItemClientObject") ); 216 217 return (wxClientData*) node->GetData(); 218} 219 220void wxChoice::Clear() 221{ 222 wxCHECK_RET( m_widget != NULL, wxT("invalid choice") ); 223 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 ); 227 228 if ( HasClientObjectData() ) 229 { 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(); 234 while ( node ) 235 { 236 delete (wxClientData *)node->GetData(); 237 node = node->GetNext(); 238 } 239 } 240 m_clientList.Clear(); 241 242 if ( m_strings ) 243 m_strings->Clear(); 244 245 // begin with no selection 246 m_selection_hack = wxNOT_FOUND; 247} 248 249void wxChoice::Delete(unsigned int n) 250{ 251 wxCHECK_RET( m_widget != NULL, wxT("invalid choice") ); 252 wxCHECK_RET( IsValid(n), _T("invalid index in wxChoice::Delete") ); 253 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 256 unsigned int i; 257 const unsigned int count = GetCount(); 258 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)) 261 { 262 // move the selection back one 263 m_selection_hack--; 264 } 265 else if ((int)n == m_selection_hack) 266 { 267 // invalidate the selection 268 m_selection_hack = wxNOT_FOUND; 269 } 270 271 const bool hasClientData = m_clientDataItemsType != wxClientData_None; 272 const bool hasObjectData = m_clientDataItemsType == wxClientData_Object; 273 274 wxList::compatibility_iterator node = m_clientList.GetFirst(); 275 276 wxArrayString items; 277 wxArrayPtrVoid itemsData; 278 items.Alloc(count); 279 for ( i = 0; i < count; i++ ) 280 { 281 if ( i != n ) 282 { 283 items.Add(GetString(i)); 284 if ( hasClientData ) 285 { 286 // also save the client data 287 itemsData.Add(node->GetData()); 288 } 289 } 290 else // need to delete the client object too 291 { 292 if ( hasObjectData ) 293 { 294 delete (wxClientData *)node->GetData(); 295 } 296 } 297 298 if ( hasClientData ) 299 { 300 node = node->GetNext(); 301 } 302 } 303 304 if ( hasObjectData ) 305 { 306 // prevent Clear() from destroying all client data 307 m_clientDataItemsType = wxClientData_None; 308 } 309 310 Clear(); 311 312 for ( i = 0; i < count - 1; i++ ) 313 { 314 Append(items[i]); 315 316 if ( hasObjectData ) 317 SetClientObject(i, (wxClientData *)itemsData[i]); 318 else if ( hasClientData ) 319 SetClientData(i, itemsData[i]); 320 } 321} 322 323int wxChoice::FindString( const wxString &string, bool bCase ) const 324{ 325 wxCHECK_MSG( m_widget != NULL, wxNOT_FOUND, wxT("invalid choice") ); 326 327 // If you read this code once and you think you understand 328 // it, then you are very wrong. Robert Roebling. 329 330 GtkMenuShell *menu_shell = GTK_MENU_SHELL( gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget) ) ); 331 int count = 0; 332 GList *child = menu_shell->children; 333 while (child) 334 { 335 GtkBin *bin = GTK_BIN( child->data ); 336 GtkLabel *label = (GtkLabel *) NULL; 337 if (bin->child) 338 label = GTK_LABEL(bin->child); 339 if (!label) 340 label = GTK_LABEL(GTK_BIN(m_widget)->child); 341 342 wxASSERT_MSG( label != NULL , wxT("wxChoice: invalid label") ); 343 344 wxString tmp( wxGTK_CONV_BACK( gtk_label_get_text( label) ) ); 345 if (string.IsSameAs( tmp, bCase )) 346 return count; 347 348 child = child->next; 349 count++; 350 } 351 352 return wxNOT_FOUND; 353} 354 355int wxChoice::GetSelection() const 356{ 357 wxCHECK_MSG( m_widget != NULL, wxNOT_FOUND, wxT("invalid choice") ); 358 359 return m_selection_hack; 360 361} 362 363void wxChoice::SetString(unsigned int n, const wxString& str) 364{ 365 wxCHECK_RET( m_widget != NULL, wxT("invalid choice") ); 366 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; 370 while (child) 371 { 372 GtkBin *bin = GTK_BIN( child->data ); 373 if (count == n) 374 { 375 GtkLabel *label = (GtkLabel *) NULL; 376 if (bin->child) 377 label = GTK_LABEL(bin->child); 378 if (!label) 379 label = GTK_LABEL(GTK_BIN(m_widget)->child); 380 381 wxASSERT_MSG( label != NULL , wxT("wxChoice: invalid label") ); 382 383 gtk_label_set_text( label, wxGTK_CONV( str ) ); 384 385 InvalidateBestSize(); 386 387 return; 388 } 389 child = child->next; 390 count++; 391 } 392} 393 394wxString wxChoice::GetString(unsigned int n) const 395{ 396 wxCHECK_MSG( m_widget != NULL, wxEmptyString, wxT("invalid choice") ); 397 398 GtkMenuShell *menu_shell = GTK_MENU_SHELL( gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget) ) ); 399 unsigned int count = 0; 400 GList *child = menu_shell->children; 401 while (child) 402 { 403 GtkBin *bin = GTK_BIN( child->data ); 404 if (count == n) 405 { 406 GtkLabel *label = (GtkLabel *) NULL; 407 if (bin->child) 408 label = GTK_LABEL(bin->child); 409 if (!label) 410 label = GTK_LABEL(GTK_BIN(m_widget)->child); 411 412 wxASSERT_MSG( label != NULL , wxT("wxChoice: invalid label") ); 413 414 return wxString( wxGTK_CONV_BACK( gtk_label_get_text( label) ) ); 415 } 416 child = child->next; 417 count++; 418 } 419 420 wxFAIL_MSG( wxT("wxChoice: invalid index in GetString()") ); 421 422 return wxEmptyString; 423} 424 425unsigned int wxChoice::GetCount() const 426{ 427 wxCHECK_MSG( m_widget != NULL, 0, wxT("invalid choice") ); 428 429 GtkMenuShell *menu_shell = GTK_MENU_SHELL( gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget) ) ); 430 unsigned int count = 0; 431 GList *child = menu_shell->children; 432 while (child) 433 { 434 count++; 435 child = child->next; 436 } 437 return count; 438} 439 440void wxChoice::SetSelection( int n ) 441{ 442 wxCHECK_RET( m_widget != NULL, wxT("invalid choice") ); 443 444 int tmp = n; 445 gtk_option_menu_set_history( GTK_OPTION_MENU(m_widget), (gint)tmp ); 446 447 // set the local selection variable manually 448 if ((n >= 0) && ((unsigned int)n < GetCount())) 449 { 450 // a valid selection has been made 451 m_selection_hack = n; 452 } 453 else if ((n == wxNOT_FOUND) || (GetCount() == 0)) 454 { 455 // invalidates the selection if there are no items 456 // or if it is specifically set to wxNOT_FOUND 457 m_selection_hack = wxNOT_FOUND; 458 } 459 else 460 { 461 // this selects the first item by default if the selection is out of bounds 462 m_selection_hack = 0; 463 } 464} 465 466void wxChoice::DoApplyWidgetStyle(GtkRcStyle *style) 467{ 468 GtkMenuShell *menu_shell = GTK_MENU_SHELL( gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget) ) ); 469 470 gtk_widget_modify_style( m_widget, style ); 471 gtk_widget_modify_style( GTK_WIDGET( menu_shell ), style ); 472 473 GList *child = menu_shell->children; 474 while (child) 475 { 476 gtk_widget_modify_style( GTK_WIDGET( child->data ), style ); 477 478 GtkBin *bin = GTK_BIN( child->data ); 479 GtkWidget *label = (GtkWidget *) NULL; 480 if (bin->child) 481 label = bin->child; 482 if (!label) 483 label = GTK_BIN(m_widget)->child; 484 485 gtk_widget_modify_style( label, style ); 486 487 child = child->next; 488 } 489} 490 491int wxChoice::GtkAddHelper(GtkWidget *menu, unsigned int pos, const wxString& item) 492{ 493 wxCHECK_MSG(pos<=m_clientList.GetCount(), -1, wxT("invalid index")); 494 495 GtkWidget *menu_item = gtk_menu_item_new_with_label( wxGTK_CONV( item ) ); 496 497 unsigned int index; 498 if ( m_strings ) 499 { 500 // sorted control, need to insert at the correct index 501 index = m_strings->Add(item); 502 503 gtk_menu_shell_insert( GTK_MENU_SHELL(menu), menu_item, index ); 504 505 if ( index ) 506 { 507 m_clientList.Insert( m_clientList.Item(index - 1), 508 (wxObject*) NULL ); 509 } 510 else 511 { 512 m_clientList.Insert( (wxObject*) NULL ); 513 } 514 } 515 else 516 { 517 // don't call wxChoice::GetCount() from here because it doesn't work 518 // if we're called from ctor (and GtkMenuShell is still NULL) 519 520 // normal control, just append 521 if (pos == m_clientList.GetCount()) 522 { 523 gtk_menu_shell_append( GTK_MENU_SHELL(menu), menu_item ); 524 m_clientList.Append( (wxObject*) NULL ); 525 index = m_clientList.GetCount() - 1; 526 } 527 else 528 { 529 gtk_menu_shell_insert( GTK_MENU_SHELL(menu), menu_item, pos ); 530 m_clientList.Insert( pos, (wxObject*) NULL ); 531 index = pos; 532 } 533 } 534 535 if (GTK_WIDGET_REALIZED(m_widget)) 536 { 537 gtk_widget_realize( menu_item ); 538 gtk_widget_realize( GTK_BIN(menu_item)->child ); 539 540 ApplyWidgetStyle(); 541 } 542 543 // The best size of a wxChoice should probably 544 // be changed everytime the control has been 545 // changed, but at least after adding an item 546 // it has to change. Adapted from Matt Ownby. 547 InvalidateBestSize(); 548 549 g_signal_connect_after (menu_item, "activate", 550 G_CALLBACK (gtk_choice_clicked_callback), 551 this); 552 553 gtk_widget_show( menu_item ); 554 555 // return the index of the item in the control 556 return index; 557} 558 559wxSize wxChoice::DoGetBestSize() const 560{ 561 wxSize ret( wxControl::DoGetBestSize() ); 562 563 // we know better our horizontal extent: it depends on the longest string 564 // we have 565 ret.x = 0; 566 if ( m_widget ) 567 { 568 int width; 569 unsigned int count = GetCount(); 570 for ( unsigned int n = 0; n < count; n++ ) 571 { 572 GetTextExtent( GetString(n), &width, NULL, NULL, NULL ); 573 if ( width > ret.x ) 574 ret.x = width; 575 } 576 577 // add extra for the choice "=" button 578 579 // VZ: I don't know how to get the right value, it seems to be in 580 // GtkOptionMenuProps struct from gtkoptionmenu.c but we can't get 581 // to it - maybe we can use gtk_option_menu_size_request() for this 582 // somehow? 583 // 584 // This default value works only for the default GTK+ theme (i.e. 585 // no theme at all) (FIXME) 586 static const int widthChoiceIndicator = 35; 587 ret.x += widthChoiceIndicator; 588 } 589 590 // but not less than the minimal width 591 if ( GetCount() == 0 && ret.x < 80 ) 592 ret.x = 80; 593 594 // If this request_size is called with no entries then 595 // the returned height is wrong. Give it a reasonable 596 // default value. 597 if (ret.y <= 18) 598 ret.y = 8 + GetCharHeight(); 599 600 CacheBestSize(ret); 601 return ret; 602} 603 604GdkWindow *wxChoice::GTKGetWindow(wxArrayGdkWindows& WXUNUSED(windows)) const 605{ 606 return GTK_BUTTON(m_widget)->event_window; 607} 608 609// static 610wxVisualAttributes 611wxChoice::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant)) 612{ 613 return GetDefaultAttributesFromGTKWidget(gtk_option_menu_new); 614} 615 616 617#endif // wxUSE_CHOICE 618