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