Disable events upon item deletion (just in case) and update selection cache
[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 static void SendEvent( wxCommandEvent &event, wxListBox *listbox, int item )
116 {
117 event.SetInt( item );
118 event.SetString( listbox->GetString( item ) );
119 if ( listbox->HasClientObjectData() )
120 event.SetClientObject( listbox->GetClientObject(item) );
121 else if ( listbox->HasClientUntypedData() )
122 event.SetClientData( listbox->GetClientData(item) );
123 listbox->HandleWindowEvent( event );
124 }
125
126 extern "C" {
127 static void
128 gtk_listitem_changed_callback(GtkTreeSelection * WXUNUSED(selection),
129 wxListBox *listbox )
130 {
131 if (g_blockEventsOnDrag) return;
132
133 wxCommandEvent event(wxEVT_COMMAND_LISTBOX_SELECTED, listbox->GetId() );
134 event.SetEventObject( listbox );
135
136 if (listbox->HasFlag(wxLB_MULTIPLE) || listbox->HasFlag(wxLB_EXTENDED))
137 {
138 wxArrayInt selections;
139 listbox->GetSelections( selections );
140
141 if ((selections.GetCount() == 0) && (listbox->m_oldSelection.GetCount() == 0))
142 {
143 // nothing changed, just leave
144 return;
145 }
146
147 if (selections.GetCount() == listbox->m_oldSelection.GetCount())
148 {
149 bool changed = false;
150 size_t idx;
151 for (idx = 0; idx < selections.GetCount(); idx++)
152 {
153 if (selections[idx] != listbox->m_oldSelection[idx])
154 {
155 changed = true;
156 break;
157 }
158 }
159
160 // nothing changed, just leave
161 if (!changed)
162 return;
163 }
164
165 if (selections.GetCount() == 0)
166 {
167 // indicate that this is a deselection
168 event.SetExtraLong( 0 );
169 int item = listbox->m_oldSelection[0];
170 listbox->m_oldSelection = selections;
171 SendEvent( event, listbox, item );
172 return;
173 }
174
175 int item;
176 // Now test if any new item is selected
177 bool any_new_selected = false;
178 size_t idx;
179 for (idx = 0; idx < selections.GetCount(); idx++)
180 {
181 item = selections[idx];
182 if (listbox->m_oldSelection.Index(item) == wxNOT_FOUND)
183 {
184 any_new_selected = true;
185 break;
186 }
187 }
188
189 if (any_new_selected)
190 {
191 // indicate that this is a selection
192 event.SetExtraLong( 1 );
193 listbox->m_oldSelection = selections;
194 SendEvent( event, listbox, item );
195 return;
196 }
197
198 // Now test if any new item is deselected
199 bool any_new_deselected = false;
200 for (idx = 0; idx < listbox->m_oldSelection.GetCount(); idx++)
201 {
202 item = listbox->m_oldSelection[idx];
203 if (selections.Index(item) == wxNOT_FOUND)
204 {
205 any_new_deselected = true;
206 break;
207 }
208 }
209
210 if (any_new_deselected)
211 {
212 // indicate that this is a selection
213 event.SetExtraLong( 0 );
214 listbox->m_oldSelection = selections;
215 SendEvent( event, listbox, item );
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 GtkDisableEvents(); // just in case
649
650 InvalidateBestSize();
651
652 gtk_list_store_clear( m_liststore ); /* well, THAT was easy :) */
653
654 GtkEnableEvents();
655 }
656
657 void wxListBox::DoDeleteOneItem(unsigned int n)
658 {
659 wxCHECK_RET( m_treeview != NULL, wxT("invalid listbox") );
660
661 InvalidateBestSize();
662
663 GtkDisableEvents(); // just in case
664
665 GtkTreeIter iter;
666 wxCHECK_RET( GtkGetIteratorFor(n, &iter), wxT("wrong listbox index") );
667
668 // this returns false if iter is invalid (e.g. deleting item at end) but
669 // since we don't use iter, we ignore the return value
670 gtk_list_store_remove(m_liststore, &iter);
671
672 GtkEnableEvents();
673 }
674
675 // ----------------------------------------------------------------------------
676 // helper functions for working with iterators
677 // ----------------------------------------------------------------------------
678
679 bool wxListBox::GtkGetIteratorFor(unsigned pos, GtkTreeIter *iter) const
680 {
681 if ( !gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(m_liststore),
682 iter, NULL, pos) )
683 {
684 wxLogDebug(wxT("gtk_tree_model_iter_nth_child(%u) failed"), pos);
685 return false;
686 }
687
688 return true;
689 }
690
691 int wxListBox::GtkGetIndexFor(GtkTreeIter& iter) const
692 {
693 GtkTreePath *path =
694 gtk_tree_model_get_path(GTK_TREE_MODEL(m_liststore), &iter);
695
696 gint* pIntPath = gtk_tree_path_get_indices(path);
697
698 wxCHECK_MSG( pIntPath, wxNOT_FOUND, _T("failed to get iterator path") );
699
700 int idx = pIntPath[0];
701
702 gtk_tree_path_free( path );
703
704 return idx;
705 }
706
707 // get GtkTreeEntry from position (note: you need to g_unref it if valid)
708 GtkTreeEntry *wxListBox::GtkGetEntry(unsigned n) const
709 {
710 GtkTreeIter iter;
711 if ( !GtkGetIteratorFor(n, &iter) )
712 return NULL;
713
714
715 GtkTreeEntry* entry = NULL;
716 gtk_tree_model_get(GTK_TREE_MODEL(m_liststore), &iter,
717 WXLISTBOX_DATACOLUMN, &entry, -1);
718
719 return entry;
720 }
721
722 void wxListBox::GtkSetItem(GtkTreeIter& iter, const GtkTreeEntry *entry)
723 {
724 #if wxUSE_CHECKLISTBOX
725 if ( m_hasCheckBoxes )
726 {
727 gtk_list_store_set(m_liststore, &iter,
728 0, FALSE, // FALSE == not toggled
729 1, entry,
730 -1);
731 }
732 else
733 #endif // wxUSE_CHECKLISTBOX
734 {
735 gtk_list_store_set(m_liststore, &iter, 0, entry, -1);
736 }
737 }
738
739 // ----------------------------------------------------------------------------
740 // client data
741 // ----------------------------------------------------------------------------
742
743 void* wxListBox::DoGetItemClientData(unsigned int n) const
744 {
745 wxCHECK_MSG( IsValid(n), NULL,
746 wxT("Invalid index passed to GetItemClientData") );
747
748 GtkTreeEntry* entry = GtkGetEntry(n);
749 wxCHECK_MSG(entry, NULL, wxT("could not get entry"));
750
751 void* userdata = gtk_tree_entry_get_userdata( entry );
752 g_object_unref (entry);
753 return userdata;
754 }
755
756 void wxListBox::DoSetItemClientData(unsigned int n, void* clientData)
757 {
758 wxCHECK_RET( IsValid(n),
759 wxT("Invalid index passed to SetItemClientData") );
760
761 GtkTreeEntry* entry = GtkGetEntry(n);
762 wxCHECK_RET(entry, wxT("could not get entry"));
763
764 gtk_tree_entry_set_userdata( entry, clientData );
765 g_object_unref (entry);
766 }
767
768 // ----------------------------------------------------------------------------
769 // string list access
770 // ----------------------------------------------------------------------------
771
772 void wxListBox::SetString(unsigned int n, const wxString& label)
773 {
774 wxCHECK_RET( IsValid(n), wxT("invalid index in wxListBox::SetString") );
775 wxCHECK_RET( m_treeview != NULL, wxT("invalid listbox") );
776
777 GtkTreeEntry* entry = GtkGetEntry(n);
778 wxCHECK_RET( entry, wxT("wrong listbox index") );
779
780 // update the item itself
781 gtk_tree_entry_set_label(entry, wxGTK_CONV(label));
782
783 // and update the model which will refresh the tree too
784 GtkTreeIter iter;
785 wxCHECK_RET( GtkGetIteratorFor(n, &iter), _T("failed to get iterator") );
786
787 // FIXME: this resets the checked status of a wxCheckListBox item
788
789 GtkSetItem(iter, entry);
790 }
791
792 wxString wxListBox::GetString(unsigned int n) const
793 {
794 wxCHECK_MSG( m_treeview != NULL, wxEmptyString, wxT("invalid listbox") );
795
796 GtkTreeEntry* entry = GtkGetEntry(n);
797 wxCHECK_MSG( entry, wxEmptyString, wxT("wrong listbox index") );
798
799 wxString label = wxGTK_CONV_BACK( gtk_tree_entry_get_label(entry) );
800
801 g_object_unref (entry);
802 return label;
803 }
804
805 unsigned int wxListBox::GetCount() const
806 {
807 wxCHECK_MSG( m_treeview != NULL, 0, wxT("invalid listbox") );
808
809 return (unsigned int)gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_liststore), NULL);
810 }
811
812 int wxListBox::FindString( const wxString &item, bool bCase ) const
813 {
814 wxCHECK_MSG( m_treeview != NULL, wxNOT_FOUND, wxT("invalid listbox") );
815
816 //Sort of hackish - maybe there is a faster way
817 unsigned int nCount = wxListBox::GetCount();
818
819 for(unsigned int i = 0; i < nCount; ++i)
820 {
821 if( item.IsSameAs( wxListBox::GetString(i), bCase ) )
822 return (int)i;
823 }
824
825
826 // it's not an error if the string is not found -> no wxCHECK
827 return wxNOT_FOUND;
828 }
829
830 // ----------------------------------------------------------------------------
831 // selection
832 // ----------------------------------------------------------------------------
833
834 int wxListBox::GetSelection() const
835 {
836 wxCHECK_MSG( m_treeview != NULL, wxNOT_FOUND, wxT("invalid listbox"));
837 wxCHECK_MSG( HasFlag(wxLB_SINGLE), wxNOT_FOUND,
838 wxT("must be single selection listbox"));
839
840 GtkTreeIter iter;
841 GtkTreeSelection* selection = gtk_tree_view_get_selection(m_treeview);
842
843 // only works on single-sel
844 if (!gtk_tree_selection_get_selected(selection, NULL, &iter))
845 return wxNOT_FOUND;
846
847 return GtkGetIndexFor(iter);
848 }
849
850 int wxListBox::GetSelections( wxArrayInt& aSelections ) const
851 {
852 wxCHECK_MSG( m_treeview != NULL, wxNOT_FOUND, wxT("invalid listbox") );
853
854 aSelections.Empty();
855
856 int i = 0;
857 GtkTreeIter iter;
858 GtkTreeSelection* selection = gtk_tree_view_get_selection(m_treeview);
859
860 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(m_liststore), &iter))
861 { //gtk_tree_selection_get_selected_rows is GTK 2.2+ so iter instead
862 do
863 {
864 if (gtk_tree_selection_iter_is_selected(selection, &iter))
865 aSelections.Add(i);
866
867 i++;
868 } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(m_liststore), &iter));
869 }
870
871 return aSelections.GetCount();
872 }
873
874 bool wxListBox::IsSelected( int n ) const
875 {
876 wxCHECK_MSG( m_treeview != NULL, false, wxT("invalid listbox") );
877
878 GtkTreeSelection* selection = gtk_tree_view_get_selection(m_treeview);
879
880 GtkTreeIter iter;
881 wxCHECK_MSG( GtkGetIteratorFor(n, &iter), false, wxT("Invalid index") );
882
883 return gtk_tree_selection_iter_is_selected(selection, &iter);
884 }
885
886 void wxListBox::DoSetSelection( int n, bool select )
887 {
888 wxCHECK_RET( m_treeview != NULL, wxT("invalid listbox") );
889
890 GtkDisableEvents();
891
892 GtkTreeSelection* selection = gtk_tree_view_get_selection(m_treeview);
893
894 // passing -1 to SetSelection() is documented to deselect all items
895 if ( n == wxNOT_FOUND )
896 {
897 gtk_tree_selection_unselect_all(selection);
898 GtkEnableEvents();
899 return;
900 }
901
902 wxCHECK_RET( IsValid(n), wxT("invalid index in wxListBox::SetSelection") );
903
904
905 GtkTreeIter iter;
906 wxCHECK_RET( GtkGetIteratorFor(n, &iter), wxT("Invalid index") );
907
908 if (select)
909 gtk_tree_selection_select_iter(selection, &iter);
910 else
911 gtk_tree_selection_unselect_iter(selection, &iter);
912
913 GtkTreePath* path = gtk_tree_model_get_path(
914 GTK_TREE_MODEL(m_liststore), &iter);
915
916 gtk_tree_view_scroll_to_cell(m_treeview, path, NULL, FALSE, 0.0f, 0.0f);
917
918 gtk_tree_path_free(path);
919
920 GtkEnableEvents();
921 }
922
923 void wxListBox::GtkUpdateOldSelection()
924 {
925 if (HasFlag(wxLB_MULTIPLE) || HasFlag(wxLB_EXTENDED))
926 GetSelections( m_oldSelection );
927 }
928
929 void wxListBox::DoScrollToCell(int n, float alignY, float alignX)
930 {
931 wxCHECK_RET( m_treeview, wxT("invalid listbox") );
932 wxCHECK_RET( IsValid(n), wxT("invalid index"));
933
934 //RN: I have no idea why this line is needed...
935 if (gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (m_treeview))
936 return;
937
938 GtkTreeIter iter;
939 if ( !GtkGetIteratorFor(n, &iter) )
940 return;
941
942 GtkTreePath* path = gtk_tree_model_get_path(
943 GTK_TREE_MODEL(m_liststore), &iter);
944
945 // Scroll to the desired cell (0.0 == topleft alignment)
946 gtk_tree_view_scroll_to_cell(m_treeview, path, NULL,
947 TRUE, alignY, alignX);
948
949 gtk_tree_path_free(path);
950 }
951
952 void wxListBox::DoSetFirstItem(int n)
953 {
954 DoScrollToCell(n, 0, 0);
955 }
956
957 void wxListBox::EnsureVisible(int n)
958 {
959 DoScrollToCell(n, 0.5, 0);
960 }
961
962 // ----------------------------------------------------------------------------
963 // hittest
964 // ----------------------------------------------------------------------------
965
966 int wxListBox::DoListHitTest(const wxPoint& point) const
967 {
968 // gtk_tree_view_get_path_at_pos() also gets items that are not visible and
969 // we only want visible items we need to check for it manually here
970 if ( !GetClientRect().Contains(point) )
971 return wxNOT_FOUND;
972
973 // need to translate from master window since it is in client coords
974 gint binx, biny;
975 gdk_window_get_geometry(gtk_tree_view_get_bin_window(m_treeview),
976 &binx, &biny, NULL, NULL, NULL);
977
978 GtkTreePath* path;
979 if ( !gtk_tree_view_get_path_at_pos
980 (
981 m_treeview,
982 point.x - binx,
983 point.y - biny,
984 &path,
985 NULL, // [out] column (always 0 here)
986 NULL, // [out] x-coord relative to the cell (not interested)
987 NULL // [out] y-coord relative to the cell
988 ) )
989 {
990 return wxNOT_FOUND;
991 }
992
993 int index = gtk_tree_path_get_indices(path)[0];
994 gtk_tree_path_free(path);
995
996 return index;
997 }
998
999 // ----------------------------------------------------------------------------
1000 // helpers
1001 // ----------------------------------------------------------------------------
1002
1003 #if wxUSE_TOOLTIPS
1004 void wxListBox::ApplyToolTip( GtkTooltips *tips, const gchar *tip )
1005 {
1006 // RN: Is this needed anymore?
1007 gtk_tooltips_set_tip( tips, GTK_WIDGET( m_treeview ), tip, (gchar*) NULL );
1008 }
1009 #endif // wxUSE_TOOLTIPS
1010
1011 GtkWidget *wxListBox::GetConnectWidget()
1012 {
1013 // the correct widget for listbox events (such as mouse clicks for example)
1014 // is m_treeview, not the parent scrolled window
1015 return GTK_WIDGET(m_treeview);
1016 }
1017
1018 GdkWindow *wxListBox::GTKGetWindow(wxArrayGdkWindows& WXUNUSED(windows)) const
1019 {
1020 return gtk_tree_view_get_bin_window(m_treeview);
1021 }
1022
1023 void wxListBox::DoApplyWidgetStyle(GtkRcStyle *style)
1024 {
1025 if (m_hasBgCol && m_backgroundColour.Ok())
1026 {
1027 GdkWindow *window = gtk_tree_view_get_bin_window(m_treeview);
1028 if (window)
1029 {
1030 m_backgroundColour.CalcPixel( gdk_drawable_get_colormap( window ) );
1031 gdk_window_set_background( window, m_backgroundColour.GetColor() );
1032 gdk_window_clear( window );
1033 }
1034 }
1035
1036 gtk_widget_modify_style( GTK_WIDGET(m_treeview), style );
1037 }
1038
1039 wxSize wxListBox::DoGetBestSize() const
1040 {
1041 wxCHECK_MSG(m_treeview, wxDefaultSize, wxT("invalid tree view"));
1042
1043 // Start with a minimum size that's not too small
1044 int cx, cy;
1045 GetTextExtent( wxT("X"), &cx, &cy);
1046 int lbWidth = 0;
1047 int lbHeight = 10;
1048
1049 // Find the widest string.
1050 const unsigned int count = GetCount();
1051 if ( count )
1052 {
1053 int wLine;
1054 for ( unsigned int i = 0; i < count; i++ )
1055 {
1056 GetTextExtent(GetString(i), &wLine, NULL);
1057 if ( wLine > lbWidth )
1058 lbWidth = wLine;
1059 }
1060 }
1061
1062 lbWidth += 3 * cx;
1063
1064 // And just a bit more for the checkbox if present and then some
1065 // (these are rough guesses)
1066 #if wxUSE_CHECKLISTBOX
1067 if ( m_hasCheckBoxes )
1068 {
1069 lbWidth += 35;
1070 cy = cy > 25 ? cy : 25; // rough height of checkbox
1071 }
1072 #endif
1073
1074 // Add room for the scrollbar
1075 lbWidth += wxSystemSettings::GetMetric(wxSYS_VSCROLL_X);
1076
1077 // Don't make the listbox too tall but don't make it too small neither
1078 lbHeight = (cy+4) * wxMin(wxMax(count, 3), 10);
1079
1080 wxSize best(lbWidth, lbHeight);
1081 CacheBestSize(best);
1082 return best;
1083 }
1084
1085 // static
1086 wxVisualAttributes
1087 wxListBox::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
1088 {
1089 return GetDefaultAttributesFromGTKWidget(gtk_tree_view_new, true);
1090 }
1091
1092 #endif // wxUSE_LISTBOX