Include wx/arrstr.h according to precompiled headers of wx/wx.h (with other minor...
[wxWidgets.git] / src / gtk / choice.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/gtk/choice.cpp
3 // Purpose:
4 // Author: Robert Roebling
5 // Id: $Id$
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
34 extern bool g_blockEventsOnDrag;
35
36 //-----------------------------------------------------------------------------
37 // "activate"
38 //-----------------------------------------------------------------------------
39
40 extern "C" {
41 static 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
76 IMPLEMENT_DYNAMIC_CLASS(wxChoice,wxControl)
77
78 wxChoice::wxChoice()
79 {
80 m_strings = (wxSortedArrayString *)NULL;
81 }
82
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 )
88 {
89 wxCArrayString chs(choices);
90
91 return Create( parent, id, pos, size, chs.GetCount(), chs.GetStrings(),
92 style, validator, name );
93 }
94
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 )
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 // begin with no selection
122 m_selection_hack = 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 SetBestSize(size); // need this too because this is a wxControlWithItems
137
138 return true;
139 }
140
141 wxChoice::~wxChoice()
142 {
143 Clear();
144
145 delete m_strings;
146 }
147
148 int 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
157 int 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
177 void 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
187 void* 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
197 void 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
209 wxClientData* 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
220 void 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
249 void 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
323 int 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
355 int wxChoice::GetSelection() const
356 {
357 wxCHECK_MSG( m_widget != NULL, wxNOT_FOUND, wxT("invalid choice") );
358
359 return m_selection_hack;
360
361 }
362
363 void 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 return;
386 }
387 child = child->next;
388 count++;
389 }
390 }
391
392 wxString wxChoice::GetString(unsigned int n) const
393 {
394 wxCHECK_MSG( m_widget != NULL, wxEmptyString, wxT("invalid choice") );
395
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;
399 while (child)
400 {
401 GtkBin *bin = GTK_BIN( child->data );
402 if (count == n)
403 {
404 GtkLabel *label = (GtkLabel *) NULL;
405 if (bin->child)
406 label = GTK_LABEL(bin->child);
407 if (!label)
408 label = GTK_LABEL(GTK_BIN(m_widget)->child);
409
410 wxASSERT_MSG( label != NULL , wxT("wxChoice: invalid label") );
411
412 return wxString( wxGTK_CONV_BACK( gtk_label_get_text( label) ) );
413 }
414 child = child->next;
415 count++;
416 }
417
418 wxFAIL_MSG( wxT("wxChoice: invalid index in GetString()") );
419
420 return wxEmptyString;
421 }
422
423 unsigned int wxChoice::GetCount() const
424 {
425 wxCHECK_MSG( m_widget != NULL, 0, wxT("invalid choice") );
426
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;
430 while (child)
431 {
432 count++;
433 child = child->next;
434 }
435 return count;
436 }
437
438 void wxChoice::SetSelection( int n )
439 {
440 wxCHECK_RET( m_widget != NULL, wxT("invalid choice") );
441
442 int tmp = n;
443 gtk_option_menu_set_history( GTK_OPTION_MENU(m_widget), (gint)tmp );
444
445 // set the local selection variable manually
446 if ((n >= 0) && ((unsigned int)n < GetCount()))
447 {
448 // a valid selection has been made
449 m_selection_hack = n;
450 }
451 else if ((n == wxNOT_FOUND) || (GetCount() == 0))
452 {
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;
456 }
457 else
458 {
459 // this selects the first item by default if the selection is out of bounds
460 m_selection_hack = 0;
461 }
462 }
463
464 void wxChoice::DoApplyWidgetStyle(GtkRcStyle *style)
465 {
466 GtkMenuShell *menu_shell = GTK_MENU_SHELL( gtk_option_menu_get_menu( GTK_OPTION_MENU(m_widget) ) );
467
468 gtk_widget_modify_style( m_widget, style );
469 gtk_widget_modify_style( GTK_WIDGET( menu_shell ), style );
470
471 GList *child = menu_shell->children;
472 while (child)
473 {
474 gtk_widget_modify_style( GTK_WIDGET( child->data ), style );
475
476 GtkBin *bin = GTK_BIN( child->data );
477 GtkWidget *label = (GtkWidget *) NULL;
478 if (bin->child)
479 label = bin->child;
480 if (!label)
481 label = GTK_BIN(m_widget)->child;
482
483 gtk_widget_modify_style( label, style );
484
485 child = child->next;
486 }
487 }
488
489 int wxChoice::GtkAddHelper(GtkWidget *menu, unsigned int pos, const wxString& item)
490 {
491 wxCHECK_MSG(pos<=m_clientList.GetCount(), -1, wxT("invalid index"));
492
493 GtkWidget *menu_item = gtk_menu_item_new_with_label( wxGTK_CONV( item ) );
494
495 unsigned int index;
496 if ( m_strings )
497 {
498 // sorted control, need to insert at the correct index
499 index = m_strings->Add(item);
500
501 gtk_menu_shell_insert( GTK_MENU_SHELL(menu), menu_item, index );
502
503 if ( index )
504 {
505 m_clientList.Insert( m_clientList.Item(index - 1),
506 (wxObject*) NULL );
507 }
508 else
509 {
510 m_clientList.Insert( (wxObject*) NULL );
511 }
512 }
513 else
514 {
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)
517
518 // normal control, just append
519 if (pos == m_clientList.GetCount())
520 {
521 gtk_menu_shell_append( GTK_MENU_SHELL(menu), menu_item );
522 m_clientList.Append( (wxObject*) NULL );
523 index = m_clientList.GetCount() - 1;
524 }
525 else
526 {
527 gtk_menu_shell_insert( GTK_MENU_SHELL(menu), menu_item, pos );
528 m_clientList.Insert( pos, (wxObject*) NULL );
529 index = pos;
530 }
531 }
532
533 if (GTK_WIDGET_REALIZED(m_widget))
534 {
535 gtk_widget_realize( menu_item );
536 gtk_widget_realize( GTK_BIN(menu_item)->child );
537
538 ApplyWidgetStyle();
539 }
540
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();
546
547 g_signal_connect_after (menu_item, "activate",
548 G_CALLBACK (gtk_choice_clicked_callback),
549 this);
550
551 gtk_widget_show( menu_item );
552
553 // return the index of the item in the control
554 return index;
555 }
556
557 wxSize wxChoice::DoGetBestSize() const
558 {
559 wxSize ret( wxControl::DoGetBestSize() );
560
561 // we know better our horizontal extent: it depends on the longest string
562 // we have
563 ret.x = 0;
564 if ( m_widget )
565 {
566 int width;
567 unsigned int count = GetCount();
568 for ( unsigned int n = 0; n < count; n++ )
569 {
570 GetTextExtent( GetString(n), &width, NULL, NULL, NULL );
571 if ( width > ret.x )
572 ret.x = width;
573 }
574
575 // add extra for the choice "=" button
576
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
580 // somehow?
581 //
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;
586 }
587
588 // but not less than the minimal width
589 if ( ret.x < 80 )
590 ret.x = 80;
591
592 // If this request_size is called with no entries then
593 // the returned height is wrong. Give it a reasonable
594 // default value.
595 if (ret.y <= 18)
596 ret.y = 8 + GetCharHeight();
597
598 CacheBestSize(ret);
599 return ret;
600 }
601
602 bool wxChoice::IsOwnGtkWindow( GdkWindow *window )
603 {
604 return GTK_BUTTON(m_widget)->event_window;
605 }
606
607 // static
608 wxVisualAttributes
609 wxChoice::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
610 {
611 return GetDefaultAttributesFromGTKWidget(gtk_option_menu_new);
612 }
613
614
615 #endif // wxUSE_CHOICE