Make events match better with MSW
[wxWidgets.git] / src / gtk / listbox.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/gtk/listbox.cpp
3 // Purpose:
4 // Author: Robert Roebling
5 // Modified By: Ryan Norton (GtkTreeView implementation)
6 // Id: $Id$
7 // Copyright: (c) 1998 Robert Roebling
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10
11 // For compilers that support precompilation, includes "wx.h".
12 #include "wx/wxprec.h"
13
14 #if wxUSE_LISTBOX
15
16 #include "wx/listbox.h"
17
18 #ifndef WX_PRECOMP
19 #include "wx/dynarray.h"
20 #include "wx/intl.h"
21 #include "wx/log.h"
22 #include "wx/utils.h"
23 #include "wx/settings.h"
24 #include "wx/checklst.h"
25 #include "wx/arrstr.h"
26 #endif
27
28 #include "wx/gtk/private.h"
29 #include "wx/gtk/treeentry_gtk.h"
30
31 #if wxUSE_TOOLTIPS
32 #include "wx/tooltip.h"
33 #endif
34
35 #include <gtk/gtk.h>
36 #include <gdk/gdkkeysyms.h>
37
38 //-----------------------------------------------------------------------------
39 // data
40 //-----------------------------------------------------------------------------
41
42 extern bool g_blockEventsOnDrag;
43 extern bool g_blockEventsOnScroll;
44
45
46
47 //-----------------------------------------------------------------------------
48 // Macro to tell which row the strings are in (1 if native checklist, 0 if not)
49 //-----------------------------------------------------------------------------
50
51 #if wxUSE_CHECKLISTBOX
52 # define WXLISTBOX_DATACOLUMN_ARG(x) (x->m_hasCheckBoxes ? 1 : 0)
53 #else
54 # define WXLISTBOX_DATACOLUMN_ARG(x) (0)
55 #endif // wxUSE_CHECKLISTBOX
56
57 #define WXLISTBOX_DATACOLUMN WXLISTBOX_DATACOLUMN_ARG(this)
58
59 //-----------------------------------------------------------------------------
60 // "row-activated"
61 //-----------------------------------------------------------------------------
62
63 extern "C" {
64 static void
65 gtk_listbox_row_activated_callback(GtkTreeView * WXUNUSED(treeview),
66 GtkTreePath *path,
67 GtkTreeViewColumn * WXUNUSED(col),
68 wxListBox *listbox)
69 {
70 if (g_blockEventsOnDrag) return;
71 if (g_blockEventsOnScroll) return;
72
73 // This is triggered by either a double-click or a space press
74
75 int sel = gtk_tree_path_get_indices(path)[0];
76
77 wxCommandEvent event(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, listbox->GetId() );
78 event.SetEventObject( listbox );
79
80 if (listbox->IsSelected(sel))
81 {
82 GtkTreeEntry* entry = listbox->GtkGetEntry(sel);
83
84 if (entry)
85 {
86 event.SetInt(sel);
87 event.SetString(wxConvUTF8.cMB2WX(gtk_tree_entry_get_label(entry)));
88
89 if ( listbox->HasClientObjectData() )
90 event.SetClientObject( (wxClientData*) gtk_tree_entry_get_userdata(entry) );
91 else if ( listbox->HasClientUntypedData() )
92 event.SetClientData( gtk_tree_entry_get_userdata(entry) );
93
94 g_object_unref (entry);
95 }
96 else
97 {
98 wxLogSysError(wxT("Internal error - could not get entry for double-click"));
99 event.SetInt(-1);
100 }
101 }
102 else
103 {
104 event.SetInt(-1);
105 }
106
107 listbox->HandleWindowEvent( event );
108 }
109 }
110
111 //-----------------------------------------------------------------------------
112 // "changed"
113 //-----------------------------------------------------------------------------
114
115 extern "C" {
116 static void
117 gtk_listitem_changed_callback(GtkTreeSelection * WXUNUSED(selection),
118 wxListBox *listbox )
119 {
120 if (g_blockEventsOnDrag) return;
121
122 wxCommandEvent event(wxEVT_COMMAND_LISTBOX_SELECTED, listbox->GetId() );
123 event.SetEventObject( listbox );
124
125 if (listbox->HasFlag(wxLB_MULTIPLE) || listbox->HasFlag(wxLB_EXTENDED))
126 {
127 wxArrayInt selections;
128 listbox->GetSelections( selections );
129
130 if ((selections.GetCount() == 0) && (listbox->m_oldSelection.GetCount() == 0))
131 {
132 // nothing changed, just leave
133 return;
134 }
135
136 if (selections.GetCount() == listbox->m_oldSelection.GetCount())
137 {
138 bool changed = false;
139 size_t idx;
140 for (idx = 0; idx < selections.GetCount(); idx++)
141 {
142 if (selections[idx] != listbox->m_oldSelection[idx])
143 {
144 changed = true;
145 break;
146 }
147 }
148
149 // nothing changed, just leave
150 if (!changed)
151 return;
152 }
153
154 if (selections.GetCount() == 0)
155 {
156 // indicate that this is a deselection
157 event.SetExtraLong( 0 );
158
159 // take first item in old selection
160 event.SetInt( listbox->m_oldSelection[0] );
161 event.SetString( listbox->GetString( listbox->m_oldSelection[0] ) );
162
163 listbox->m_oldSelection = selections;
164
165 listbox->HandleWindowEvent( event );
166
167 return;
168 }
169
170 // Now test if any new item is selected
171 bool any_new_selected = false;
172 size_t idx;
173 for (idx = 0; idx < selections.GetCount(); idx++)
174 {
175 int item = selections[idx];
176 if (listbox->m_oldSelection.Index(item) == wxNOT_FOUND)
177 {
178 event.SetInt( item );
179 event.SetString( listbox->GetString( item ) );
180 any_new_selected = true;
181 break;
182 }
183 }
184
185 if (any_new_selected)
186 {
187 // indicate that this is a selection
188 event.SetExtraLong( 1 );
189
190 listbox->m_oldSelection = selections;
191 listbox->HandleWindowEvent( event );
192 return;
193 }
194
195 // Now test if any new item is deselected
196 bool any_new_deselected = false;
197 for (idx = 0; idx < listbox->m_oldSelection.GetCount(); idx++)
198 {
199 int item = listbox->m_oldSelection[idx];
200 if (selections.Index(item) == wxNOT_FOUND)
201 {
202 event.SetInt( item );
203 event.SetString( listbox->GetString( item ) );
204 any_new_deselected = true;
205 break;
206 }
207 }
208
209 if (any_new_deselected)
210 {
211 // indicate that this is a selection
212 event.SetExtraLong( 0 );
213
214 listbox->m_oldSelection = selections;
215 listbox->HandleWindowEvent( event );
216 return;
217 }
218
219 wxLogError( wxT("Wrong wxListBox selection") );
220 }
221 else
222 {
223 int index = listbox->GetSelection();
224 if (index == wxNOT_FOUND)
225 {
226 // indicate that this is a deselection
227 event.SetExtraLong( 0 );
228 event.SetInt( -1 );
229
230 listbox->HandleWindowEvent( event );
231
232 return;
233 }
234 else
235 {
236 GtkTreeEntry* entry = listbox->GtkGetEntry( index );
237
238 // indicate that this is a selection
239 event.SetExtraLong( 1 );
240
241 event.SetInt( index );
242 event.SetString(wxConvUTF8.cMB2WX(gtk_tree_entry_get_label(entry)));
243
244 if ( listbox->HasClientObjectData() )
245 event.SetClientObject(
246 (wxClientData*) gtk_tree_entry_get_userdata(entry)
247 );
248 else if ( listbox->HasClientUntypedData() )
249 event.SetClientData( gtk_tree_entry_get_userdata(entry) );
250
251 listbox->HandleWindowEvent( event );
252
253 g_object_unref (entry);
254 }
255 }
256 }
257 }
258
259 //-----------------------------------------------------------------------------
260 // "key_press_event"
261 //-----------------------------------------------------------------------------
262
263 extern "C" {
264 static gint
265 gtk_listbox_key_press_callback( GtkWidget *WXUNUSED(widget),
266 GdkEventKey *gdk_event,
267 wxListBox *listbox )
268 {
269 if ((gdk_event->keyval == GDK_Return) ||
270 (gdk_event->keyval == GDK_ISO_Enter) ||
271 (gdk_event->keyval == GDK_KP_Enter))
272 {
273 int index = listbox->GetSelection();
274 if (index != wxNOT_FOUND)
275 {
276
277 wxCommandEvent event(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, listbox->GetId() );
278 event.SetEventObject( listbox );
279
280 GtkTreeEntry* entry = listbox->GtkGetEntry( index );
281
282 // indicate that this is a selection
283 event.SetExtraLong( 1 );
284
285 event.SetInt( index );
286 event.SetString(wxConvUTF8.cMB2WX(gtk_tree_entry_get_label(entry)));
287
288 if ( listbox->HasClientObjectData() )
289 event.SetClientObject(
290 (wxClientData*) gtk_tree_entry_get_userdata(entry)
291 );
292 else if ( listbox->HasClientUntypedData() )
293 event.SetClientData( gtk_tree_entry_get_userdata(entry) );
294
295 /* bool ret = */ listbox->HandleWindowEvent( event );
296
297 g_object_unref (entry);
298
299 // wxMac and wxMSW always invoke default action
300 // if (!ret)
301 {
302 // DClick not handled -> invoke default action
303 wxWindow *tlw = wxGetTopLevelParent( listbox );
304 if (tlw)
305 {
306 GtkWindow *gtk_window = GTK_WINDOW( tlw->GetHandle() );
307 if (gtk_window)
308 gtk_window_activate_default( gtk_window );
309 }
310 }
311
312 // Always intercept, otherwise we'd get another dclick
313 // event from row_activated
314 return TRUE;
315 }
316 }
317
318 return FALSE;
319 }
320 }
321
322 //-----------------------------------------------------------------------------
323 // GtkTreeEntry destruction (to destroy client data)
324 //-----------------------------------------------------------------------------
325
326 extern "C" {
327 static void gtk_tree_entry_destroy_cb(GtkTreeEntry* entry,
328 wxListBox* listbox)
329 {
330 if (listbox->HasClientObjectData())
331 {
332 gpointer userdata = gtk_tree_entry_get_userdata(entry);
333 if (userdata)
334 delete (wxClientData *)userdata;
335 }
336 }
337 }
338
339 //-----------------------------------------------------------------------------
340 // Sorting callback (standard CmpNoCase return value)
341 //-----------------------------------------------------------------------------
342
343 extern "C" {
344 static gint gtk_listbox_sort_callback(GtkTreeModel * WXUNUSED(model),
345 GtkTreeIter *a,
346 GtkTreeIter *b,
347 wxListBox *listbox)
348 {
349 GtkTreeEntry* entry;
350 GtkTreeEntry* entry2;
351
352 gtk_tree_model_get(GTK_TREE_MODEL(listbox->m_liststore),
353 a,
354 WXLISTBOX_DATACOLUMN_ARG(listbox),
355 &entry, -1);
356 gtk_tree_model_get(GTK_TREE_MODEL(listbox->m_liststore),
357 b,
358 WXLISTBOX_DATACOLUMN_ARG(listbox),
359 &entry2, -1);
360 wxCHECK_MSG(entry, 0, wxT("Could not get entry"));
361 wxCHECK_MSG(entry2, 0, wxT("Could not get entry2"));
362
363 //We compare collate keys here instead of calling g_utf8_collate
364 //as it is rather slow (and even the docs reccommend this)
365 int ret = strcmp(gtk_tree_entry_get_collate_key(entry),
366 gtk_tree_entry_get_collate_key(entry2));
367
368 g_object_unref (entry);
369 g_object_unref (entry2);
370
371 return ret;
372 }
373 }
374
375 //-----------------------------------------------------------------------------
376 // Searching callback (TRUE == not equal, FALSE == equal)
377 //-----------------------------------------------------------------------------
378
379 extern "C" {
380 static gboolean gtk_listbox_searchequal_callback(GtkTreeModel * WXUNUSED(model),
381 gint WXUNUSED(column),
382 const gchar* key,
383 GtkTreeIter* iter,
384 wxListBox* listbox)
385 {
386 GtkTreeEntry* entry;
387
388 gtk_tree_model_get(GTK_TREE_MODEL(listbox->m_liststore),
389 iter,
390 WXLISTBOX_DATACOLUMN_ARG(listbox),
391 &entry, -1);
392 wxCHECK_MSG(entry, 0, wxT("Could not get entry"));
393 wxGtkString keycollatekey(g_utf8_collate_key(key, -1));
394
395 int ret = strcmp(keycollatekey,
396 gtk_tree_entry_get_collate_key(entry));
397
398 g_object_unref (entry);
399
400 return ret != 0;
401 }
402 }
403
404 //-----------------------------------------------------------------------------
405 // wxListBox
406 //-----------------------------------------------------------------------------
407
408 IMPLEMENT_DYNAMIC_CLASS(wxListBox, wxControlWithItems)
409
410 // ----------------------------------------------------------------------------
411 // construction
412 // ----------------------------------------------------------------------------
413
414 void wxListBox::Init()
415 {
416 m_treeview = (GtkTreeView*) NULL;
417 #if wxUSE_CHECKLISTBOX
418 m_hasCheckBoxes = false;
419 #endif // wxUSE_CHECKLISTBOX
420 }
421
422 bool wxListBox::Create( wxWindow *parent, wxWindowID id,
423 const wxPoint &pos, const wxSize &size,
424 const wxArrayString& choices,
425 long style, const wxValidator& validator,
426 const wxString &name )
427 {
428 wxCArrayString chs(choices);
429
430 return Create( parent, id, pos, size, chs.GetCount(), chs.GetStrings(),
431 style, validator, name );
432 }
433
434 bool wxListBox::Create( wxWindow *parent, wxWindowID id,
435 const wxPoint &pos, const wxSize &size,
436 int n, const wxString choices[],
437 long style, const wxValidator& validator,
438 const wxString &name )
439 {
440 if (!PreCreation( parent, pos, size ) ||
441 !CreateBase( parent, id, pos, size, style, validator, name ))
442 {
443 wxFAIL_MSG( wxT("wxListBox creation failed") );
444 return false;
445 }
446
447 m_widget = gtk_scrolled_window_new( (GtkAdjustment*) NULL, (GtkAdjustment*) NULL );
448 if (style & wxLB_ALWAYS_SB)
449 {
450 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(m_widget),
451 GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS );
452 }
453 else
454 {
455 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(m_widget),
456 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
457 }
458
459
460 GtkScrolledWindowSetBorder(m_widget, style);
461
462 m_treeview = GTK_TREE_VIEW( gtk_tree_view_new( ) );
463
464 //wxListBox doesn't have a header :)
465 //NB: If enabled SetFirstItem doesn't work correctly
466 gtk_tree_view_set_headers_visible(m_treeview, FALSE);
467
468 #if wxUSE_CHECKLISTBOX
469 if(m_hasCheckBoxes)
470 ((wxCheckListBox*)this)->DoCreateCheckList();
471 #endif // wxUSE_CHECKLISTBOX
472
473 // Create the data column
474 gtk_tree_view_insert_column_with_attributes(m_treeview, -1, "",
475 gtk_cell_renderer_text_new(),
476 "text",
477 WXLISTBOX_DATACOLUMN, NULL);
478
479 // Now create+set the model (GtkListStore) - first argument # of columns
480 #if wxUSE_CHECKLISTBOX
481 if(m_hasCheckBoxes)
482 m_liststore = gtk_list_store_new(2, G_TYPE_BOOLEAN,
483 GTK_TYPE_TREE_ENTRY);
484 else
485 #endif
486 m_liststore = gtk_list_store_new(1, GTK_TYPE_TREE_ENTRY);
487
488 gtk_tree_view_set_model(m_treeview, GTK_TREE_MODEL(m_liststore));
489
490 g_object_unref (m_liststore); //free on treeview destruction
491
492 // Disable the pop-up textctrl that enables searching - note that
493 // the docs specify that even if this disabled (which we are doing)
494 // the user can still have it through the start-interactive-search
495 // key binding...either way we want to provide a searchequal callback
496 // NB: If this is enabled a doubleclick event (activate) gets sent
497 // on a successful search
498 gtk_tree_view_set_search_column(m_treeview, WXLISTBOX_DATACOLUMN);
499 gtk_tree_view_set_search_equal_func(m_treeview,
500 (GtkTreeViewSearchEqualFunc) gtk_listbox_searchequal_callback,
501 this,
502 NULL);
503
504 gtk_tree_view_set_enable_search(m_treeview, FALSE);
505
506 GtkSelectionMode mode;
507 if (style & wxLB_MULTIPLE)
508 {
509 mode = GTK_SELECTION_MULTIPLE;
510 }
511 else if (style & wxLB_EXTENDED)
512 {
513 mode = GTK_SELECTION_EXTENDED;
514 }
515 else
516 {
517 // if style was 0 set single mode
518 m_windowStyle |= wxLB_SINGLE;
519 mode = GTK_SELECTION_SINGLE;
520 }
521
522 GtkTreeSelection* selection = gtk_tree_view_get_selection( m_treeview );
523 gtk_tree_selection_set_mode( selection, mode );
524
525 // Handle sortable stuff
526 if(HasFlag(wxLB_SORT))
527 {
528 // Setup sorting in ascending (wx) order
529 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(m_liststore),
530 WXLISTBOX_DATACOLUMN,
531 GTK_SORT_ASCENDING);
532
533 // Set the sort callback
534 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(m_liststore),
535 WXLISTBOX_DATACOLUMN,
536 (GtkTreeIterCompareFunc) gtk_listbox_sort_callback,
537 this, //userdata
538 NULL //"destroy notifier"
539 );
540 }
541
542
543 gtk_container_add (GTK_CONTAINER (m_widget), GTK_WIDGET(m_treeview) );
544
545 gtk_widget_show( GTK_WIDGET(m_treeview) );
546 m_focusWidget = GTK_WIDGET(m_treeview);
547
548 Append(n, choices); // insert initial items
549
550 // generate dclick events
551 g_signal_connect_after(m_treeview, "row-activated",
552 G_CALLBACK(gtk_listbox_row_activated_callback), this);
553
554 // for intercepting dclick generation by <ENTER>
555 g_signal_connect (m_treeview, "key_press_event",
556 G_CALLBACK (gtk_listbox_key_press_callback),
557 this);
558 m_parent->DoAddChild( this );
559
560 PostCreation(size);
561 SetInitialSize(size); // need this too because this is a wxControlWithItems
562
563 g_signal_connect_after (selection, "changed",
564 G_CALLBACK (gtk_listitem_changed_callback), this);
565
566 return true;
567 }
568
569 wxListBox::~wxListBox()
570 {
571 m_hasVMT = false;
572
573 Clear();
574 }
575
576 void wxListBox::GtkDisableEvents()
577 {
578 GtkTreeSelection* selection = gtk_tree_view_get_selection( m_treeview );
579
580 g_signal_handlers_block_by_func(selection,
581 (gpointer) gtk_listitem_changed_callback, this);
582 }
583
584 void wxListBox::GtkEnableEvents()
585 {
586 GtkTreeSelection* selection = gtk_tree_view_get_selection( m_treeview );
587
588 g_signal_handlers_unblock_by_func(selection,
589 (gpointer) gtk_listitem_changed_callback, this);
590
591 GtkUpdateOldSelection();
592 }
593
594 // ----------------------------------------------------------------------------
595 // adding items
596 // ----------------------------------------------------------------------------
597
598 int wxListBox::DoInsertItems(const wxArrayStringsAdapter& items,
599 unsigned int pos,
600 void **clientData,
601 wxClientDataType type)
602 {
603 wxCHECK_MSG( m_treeview != NULL, wxNOT_FOUND, wxT("invalid listbox") );
604
605 InvalidateBestSize();
606
607 GtkTreeIter* pIter = NULL; // append by default
608 GtkTreeIter iter;
609 if ( pos != GetCount() )
610 {
611 wxCHECK_MSG( GtkGetIteratorFor(pos, &iter), wxNOT_FOUND,
612 wxT("internal wxListBox error in insertion") );
613
614 pIter = &iter;
615 }
616
617 const unsigned int numItems = items.GetCount();
618 for ( unsigned int i = 0; i < numItems; ++i )
619 {
620 GtkTreeEntry* entry = gtk_tree_entry_new();
621 gtk_tree_entry_set_label(entry, wxGTK_CONV(items[i]));
622 gtk_tree_entry_set_destroy_func(entry,
623 (GtkTreeEntryDestroy)gtk_tree_entry_destroy_cb,
624 this);
625
626 GtkTreeIter itercur;
627 gtk_list_store_insert_before(m_liststore, &itercur, pIter);
628
629 GtkSetItem(itercur, entry);
630
631 g_object_unref (entry);
632
633 if (clientData)
634 AssignNewItemClientData(GtkGetIndexFor(itercur), clientData, i, type);
635 }
636
637 return pos + numItems - 1;
638 }
639
640 // ----------------------------------------------------------------------------
641 // deleting items
642 // ----------------------------------------------------------------------------
643
644 void wxListBox::DoClear()
645 {
646 wxCHECK_RET( m_treeview != NULL, wxT("invalid listbox") );
647
648 InvalidateBestSize();
649
650 gtk_list_store_clear( m_liststore ); /* well, THAT was easy :) */
651 }
652
653 void wxListBox::DoDeleteOneItem(unsigned int n)
654 {
655 wxCHECK_RET( m_treeview != NULL, wxT("invalid listbox") );
656
657 InvalidateBestSize();
658
659 GtkTreeIter iter;
660 wxCHECK_RET( GtkGetIteratorFor(n, &iter), wxT("wrong listbox index") );
661
662 // this returns false if iter is invalid (e.g. deleting item at end) but
663 // since we don't use iter, we ignore the return value
664 gtk_list_store_remove(m_liststore, &iter);
665 }
666
667 // ----------------------------------------------------------------------------
668 // helper functions for working with iterators
669 // ----------------------------------------------------------------------------
670
671 bool wxListBox::GtkGetIteratorFor(unsigned pos, GtkTreeIter *iter) const
672 {
673 if ( !gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(m_liststore),
674 iter, NULL, pos) )
675 {
676 wxLogDebug(wxT("gtk_tree_model_iter_nth_child(%u) failed"), pos);
677 return false;
678 }
679
680 return true;
681 }
682
683 int wxListBox::GtkGetIndexFor(GtkTreeIter& iter) const
684 {
685 GtkTreePath *path =
686 gtk_tree_model_get_path(GTK_TREE_MODEL(m_liststore), &iter);
687
688 gint* pIntPath = gtk_tree_path_get_indices(path);
689
690 wxCHECK_MSG( pIntPath, wxNOT_FOUND, _T("failed to get iterator path") );
691
692 int idx = pIntPath[0];
693
694 gtk_tree_path_free( path );
695
696 return idx;
697 }
698
699 // get GtkTreeEntry from position (note: you need to g_unref it if valid)
700 GtkTreeEntry *wxListBox::GtkGetEntry(unsigned n) const
701 {
702 GtkTreeIter iter;
703 if ( !GtkGetIteratorFor(n, &iter) )
704 return NULL;
705
706
707 GtkTreeEntry* entry = NULL;
708 gtk_tree_model_get(GTK_TREE_MODEL(m_liststore), &iter,
709 WXLISTBOX_DATACOLUMN, &entry, -1);
710
711 return entry;
712 }
713
714 void wxListBox::GtkSetItem(GtkTreeIter& iter, const GtkTreeEntry *entry)
715 {
716 #if wxUSE_CHECKLISTBOX
717 if ( m_hasCheckBoxes )
718 {
719 gtk_list_store_set(m_liststore, &iter,
720 0, FALSE, // FALSE == not toggled
721 1, entry,
722 -1);
723 }
724 else
725 #endif // wxUSE_CHECKLISTBOX
726 {
727 gtk_list_store_set(m_liststore, &iter, 0, entry, -1);
728 }
729 }
730
731 // ----------------------------------------------------------------------------
732 // client data
733 // ----------------------------------------------------------------------------
734
735 void* wxListBox::DoGetItemClientData(unsigned int n) const
736 {
737 wxCHECK_MSG( IsValid(n), NULL,
738 wxT("Invalid index passed to GetItemClientData") );
739
740 GtkTreeEntry* entry = GtkGetEntry(n);
741 wxCHECK_MSG(entry, NULL, wxT("could not get entry"));
742
743 void* userdata = gtk_tree_entry_get_userdata( entry );
744 g_object_unref (entry);
745 return userdata;
746 }
747
748 void wxListBox::DoSetItemClientData(unsigned int n, void* clientData)
749 {
750 wxCHECK_RET( IsValid(n),
751 wxT("Invalid index passed to SetItemClientData") );
752
753 GtkTreeEntry* entry = GtkGetEntry(n);
754 wxCHECK_RET(entry, wxT("could not get entry"));
755
756 gtk_tree_entry_set_userdata( entry, clientData );
757 g_object_unref (entry);
758 }
759
760 // ----------------------------------------------------------------------------
761 // string list access
762 // ----------------------------------------------------------------------------
763
764 void wxListBox::SetString(unsigned int n, const wxString& label)
765 {
766 wxCHECK_RET( IsValid(n), wxT("invalid index in wxListBox::SetString") );
767 wxCHECK_RET( m_treeview != NULL, wxT("invalid listbox") );
768
769 GtkTreeEntry* entry = GtkGetEntry(n);
770 wxCHECK_RET( entry, wxT("wrong listbox index") );
771
772 // update the item itself
773 gtk_tree_entry_set_label(entry, wxGTK_CONV(label));
774
775 // and update the model which will refresh the tree too
776 GtkTreeIter iter;
777 wxCHECK_RET( GtkGetIteratorFor(n, &iter), _T("failed to get iterator") );
778
779 // FIXME: this resets the checked status of a wxCheckListBox item
780
781 GtkSetItem(iter, entry);
782 }
783
784 wxString wxListBox::GetString(unsigned int n) const
785 {
786 wxCHECK_MSG( m_treeview != NULL, wxEmptyString, wxT("invalid listbox") );
787
788 GtkTreeEntry* entry = GtkGetEntry(n);
789 wxCHECK_MSG( entry, wxEmptyString, wxT("wrong listbox index") );
790
791 wxString label = wxGTK_CONV_BACK( gtk_tree_entry_get_label(entry) );
792
793 g_object_unref (entry);
794 return label;
795 }
796
797 unsigned int wxListBox::GetCount() const
798 {
799 wxCHECK_MSG( m_treeview != NULL, 0, wxT("invalid listbox") );
800
801 return (unsigned int)gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_liststore), NULL);
802 }
803
804 int wxListBox::FindString( const wxString &item, bool bCase ) const
805 {
806 wxCHECK_MSG( m_treeview != NULL, wxNOT_FOUND, wxT("invalid listbox") );
807
808 //Sort of hackish - maybe there is a faster way
809 unsigned int nCount = wxListBox::GetCount();
810
811 for(unsigned int i = 0; i < nCount; ++i)
812 {
813 if( item.IsSameAs( wxListBox::GetString(i), bCase ) )
814 return (int)i;
815 }
816
817
818 // it's not an error if the string is not found -> no wxCHECK
819 return wxNOT_FOUND;
820 }
821
822 // ----------------------------------------------------------------------------
823 // selection
824 // ----------------------------------------------------------------------------
825
826 int wxListBox::GetSelection() const
827 {
828 wxCHECK_MSG( m_treeview != NULL, wxNOT_FOUND, wxT("invalid listbox"));
829 wxCHECK_MSG( HasFlag(wxLB_SINGLE), wxNOT_FOUND,
830 wxT("must be single selection listbox"));
831
832 GtkTreeIter iter;
833 GtkTreeSelection* selection = gtk_tree_view_get_selection(m_treeview);
834
835 // only works on single-sel
836 if (!gtk_tree_selection_get_selected(selection, NULL, &iter))
837 return wxNOT_FOUND;
838
839 return GtkGetIndexFor(iter);
840 }
841
842 int wxListBox::GetSelections( wxArrayInt& aSelections ) const
843 {
844 wxCHECK_MSG( m_treeview != NULL, wxNOT_FOUND, wxT("invalid listbox") );
845
846 aSelections.Empty();
847
848 int i = 0;
849 GtkTreeIter iter;
850 GtkTreeSelection* selection = gtk_tree_view_get_selection(m_treeview);
851
852 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(m_liststore), &iter))
853 { //gtk_tree_selection_get_selected_rows is GTK 2.2+ so iter instead
854 do
855 {
856 if (gtk_tree_selection_iter_is_selected(selection, &iter))
857 aSelections.Add(i);
858
859 i++;
860 } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(m_liststore), &iter));
861 }
862
863 return aSelections.GetCount();
864 }
865
866 bool wxListBox::IsSelected( int n ) const
867 {
868 wxCHECK_MSG( m_treeview != NULL, false, wxT("invalid listbox") );
869
870 GtkTreeSelection* selection = gtk_tree_view_get_selection(m_treeview);
871
872 GtkTreeIter iter;
873 wxCHECK_MSG( GtkGetIteratorFor(n, &iter), false, wxT("Invalid index") );
874
875 return gtk_tree_selection_iter_is_selected(selection, &iter);
876 }
877
878 void wxListBox::DoSetSelection( int n, bool select )
879 {
880 wxCHECK_RET( m_treeview != NULL, wxT("invalid listbox") );
881
882 GtkDisableEvents();
883
884 GtkTreeSelection* selection = gtk_tree_view_get_selection(m_treeview);
885
886 // passing -1 to SetSelection() is documented to deselect all items
887 if ( n == wxNOT_FOUND )
888 {
889 gtk_tree_selection_unselect_all(selection);
890 GtkEnableEvents();
891 return;
892 }
893
894 wxCHECK_RET( IsValid(n), wxT("invalid index in wxListBox::SetSelection") );
895
896
897 GtkTreeIter iter;
898 wxCHECK_RET( GtkGetIteratorFor(n, &iter), wxT("Invalid index") );
899
900 if (select)
901 gtk_tree_selection_select_iter(selection, &iter);
902 else
903 gtk_tree_selection_unselect_iter(selection, &iter);
904
905 GtkTreePath* path = gtk_tree_model_get_path(
906 GTK_TREE_MODEL(m_liststore), &iter);
907
908 gtk_tree_view_scroll_to_cell(m_treeview, path, NULL, FALSE, 0.0f, 0.0f);
909
910 gtk_tree_path_free(path);
911
912 GtkEnableEvents();
913 }
914
915 void wxListBox::GtkUpdateOldSelection()
916 {
917 if (HasFlag(wxLB_MULTIPLE) || HasFlag(wxLB_EXTENDED))
918 GetSelections( m_oldSelection );
919 }
920
921 void wxListBox::DoScrollToCell(int n, float alignY, float alignX)
922 {
923 wxCHECK_RET( m_treeview, wxT("invalid listbox") );
924 wxCHECK_RET( IsValid(n), wxT("invalid index"));
925
926 //RN: I have no idea why this line is needed...
927 if (gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (m_treeview))
928 return;
929
930 GtkTreeIter iter;
931 if ( !GtkGetIteratorFor(n, &iter) )
932 return;
933
934 GtkTreePath* path = gtk_tree_model_get_path(
935 GTK_TREE_MODEL(m_liststore), &iter);
936
937 // Scroll to the desired cell (0.0 == topleft alignment)
938 gtk_tree_view_scroll_to_cell(m_treeview, path, NULL,
939 TRUE, alignY, alignX);
940
941 gtk_tree_path_free(path);
942 }
943
944 void wxListBox::DoSetFirstItem(int n)
945 {
946 DoScrollToCell(n, 0, 0);
947 }
948
949 void wxListBox::EnsureVisible(int n)
950 {
951 DoScrollToCell(n, 0.5, 0);
952 }
953
954 // ----------------------------------------------------------------------------
955 // hittest
956 // ----------------------------------------------------------------------------
957
958 int wxListBox::DoListHitTest(const wxPoint& point) const
959 {
960 // gtk_tree_view_get_path_at_pos() also gets items that are not visible and
961 // we only want visible items we need to check for it manually here
962 if ( !GetClientRect().Contains(point) )
963 return wxNOT_FOUND;
964
965 // need to translate from master window since it is in client coords
966 gint binx, biny;
967 gdk_window_get_geometry(gtk_tree_view_get_bin_window(m_treeview),
968 &binx, &biny, NULL, NULL, NULL);
969
970 GtkTreePath* path;
971 if ( !gtk_tree_view_get_path_at_pos
972 (
973 m_treeview,
974 point.x - binx,
975 point.y - biny,
976 &path,
977 NULL, // [out] column (always 0 here)
978 NULL, // [out] x-coord relative to the cell (not interested)
979 NULL // [out] y-coord relative to the cell
980 ) )
981 {
982 return wxNOT_FOUND;
983 }
984
985 int index = gtk_tree_path_get_indices(path)[0];
986 gtk_tree_path_free(path);
987
988 return index;
989 }
990
991 // ----------------------------------------------------------------------------
992 // helpers
993 // ----------------------------------------------------------------------------
994
995 #if wxUSE_TOOLTIPS
996 void wxListBox::ApplyToolTip( GtkTooltips *tips, const gchar *tip )
997 {
998 // RN: Is this needed anymore?
999 gtk_tooltips_set_tip( tips, GTK_WIDGET( m_treeview ), tip, (gchar*) NULL );
1000 }
1001 #endif // wxUSE_TOOLTIPS
1002
1003 GtkWidget *wxListBox::GetConnectWidget()
1004 {
1005 // the correct widget for listbox events (such as mouse clicks for example)
1006 // is m_treeview, not the parent scrolled window
1007 return GTK_WIDGET(m_treeview);
1008 }
1009
1010 GdkWindow *wxListBox::GTKGetWindow(wxArrayGdkWindows& WXUNUSED(windows)) const
1011 {
1012 return gtk_tree_view_get_bin_window(m_treeview);
1013 }
1014
1015 void wxListBox::DoApplyWidgetStyle(GtkRcStyle *style)
1016 {
1017 if (m_hasBgCol && m_backgroundColour.Ok())
1018 {
1019 GdkWindow *window = gtk_tree_view_get_bin_window(m_treeview);
1020 if (window)
1021 {
1022 m_backgroundColour.CalcPixel( gdk_drawable_get_colormap( window ) );
1023 gdk_window_set_background( window, m_backgroundColour.GetColor() );
1024 gdk_window_clear( window );
1025 }
1026 }
1027
1028 gtk_widget_modify_style( GTK_WIDGET(m_treeview), style );
1029 }
1030
1031 wxSize wxListBox::DoGetBestSize() const
1032 {
1033 wxCHECK_MSG(m_treeview, wxDefaultSize, wxT("invalid tree view"));
1034
1035 // Start with a minimum size that's not too small
1036 int cx, cy;
1037 GetTextExtent( wxT("X"), &cx, &cy);
1038 int lbWidth = 0;
1039 int lbHeight = 10;
1040
1041 // Find the widest string.
1042 const unsigned int count = GetCount();
1043 if ( count )
1044 {
1045 int wLine;
1046 for ( unsigned int i = 0; i < count; i++ )
1047 {
1048 GetTextExtent(GetString(i), &wLine, NULL);
1049 if ( wLine > lbWidth )
1050 lbWidth = wLine;
1051 }
1052 }
1053
1054 lbWidth += 3 * cx;
1055
1056 // And just a bit more for the checkbox if present and then some
1057 // (these are rough guesses)
1058 #if wxUSE_CHECKLISTBOX
1059 if ( m_hasCheckBoxes )
1060 {
1061 lbWidth += 35;
1062 cy = cy > 25 ? cy : 25; // rough height of checkbox
1063 }
1064 #endif
1065
1066 // Add room for the scrollbar
1067 lbWidth += wxSystemSettings::GetMetric(wxSYS_VSCROLL_X);
1068
1069 // Don't make the listbox too tall but don't make it too small neither
1070 lbHeight = (cy+4) * wxMin(wxMax(count, 3), 10);
1071
1072 wxSize best(lbWidth, lbHeight);
1073 CacheBestSize(best);
1074 return best;
1075 }
1076
1077 // static
1078 wxVisualAttributes
1079 wxListBox::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
1080 {
1081 return GetDefaultAttributesFromGTKWidget(gtk_tree_view_new, true);
1082 }
1083
1084 #endif // wxUSE_LISTBOX