Many changes:
[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 <gdk/gdk.h>
36 #include <gtk/gtk.h>
37 #include <gdk/gdkkeysyms.h>
38
39 //-----------------------------------------------------------------------------
40 // data
41 //-----------------------------------------------------------------------------
42
43 extern bool g_blockEventsOnDrag;
44 extern bool g_blockEventsOnScroll;
45
46
47
48 //-----------------------------------------------------------------------------
49 // Macro to tell which row the strings are in (1 if native checklist, 0 if not)
50 //-----------------------------------------------------------------------------
51
52 #if wxUSE_CHECKLISTBOX && wxUSE_NATIVEGTKCHECKLIST
53 # define WXLISTBOX_DATACOLUMN_ARG(x) (x->m_hasCheckBoxes ? 1 : 0)
54 #else
55 # define WXLISTBOX_DATACOLUMN_ARG(x) (0)
56 #endif // wxUSE_CHECKLISTBOX && wxUSE_NATIVEGTKCHECKLIST
57
58 #define WXLISTBOX_DATACOLUMN WXLISTBOX_DATACOLUMN_ARG(this)
59
60 //-----------------------------------------------------------------------------
61 // "row-activated"
62 //-----------------------------------------------------------------------------
63
64 extern "C" {
65 static void
66 gtk_listbox_row_activated_callback(GtkTreeView *treeview,
67 GtkTreePath *path,
68 GtkTreeViewColumn *col,
69 wxListBox *listbox)
70 {
71 if (g_isIdle) wxapp_install_idle_handler();
72
73 if (g_blockEventsOnDrag) return;
74 if (g_blockEventsOnScroll) return;
75
76 if (!listbox->m_hasVMT) return;
77
78 //Notes:
79 //1) This is triggered by either a double-click or a space press
80 //2) We handle both here because
81 //2a) in the case of a space/keypress we can't really know
82 // which item was pressed on because we can't get coords
83 // from a keyevent
84 //2b) It seems more correct
85
86 int sel = gtk_tree_path_get_indices(path)[0];
87
88 if(!listbox->m_spacePressed)
89 {
90 //Assume it was double-click
91 wxCommandEvent event(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, listbox->GetId() );
92 event.SetEventObject( listbox );
93
94 if(listbox->IsSelected(sel))
95 {
96 GtkTreeEntry* entry = listbox->GtkGetEntry(sel);
97
98 if(entry)
99 {
100 event.SetInt(sel);
101 event.SetString(wxConvUTF8.cMB2WX(gtk_tree_entry_get_label(entry)));
102
103 if ( listbox->HasClientObjectData() )
104 event.SetClientObject(
105 (wxClientData*) gtk_tree_entry_get_userdata(entry) );
106 else if ( listbox->HasClientUntypedData() )
107 event.SetClientData( gtk_tree_entry_get_userdata(entry) );
108 g_object_unref (entry);
109 }
110 else
111 {
112 wxLogSysError(wxT("Internal error - could not get entry for double-click"));
113 event.SetInt(-1);
114 }
115 }
116 else
117 event.SetInt(-1);
118
119 listbox->GetEventHandler()->ProcessEvent( event );
120 }
121 else
122 {
123 listbox->m_spacePressed = false; //don't block selection behaviour anymore
124
125 //Space was pressed - toggle the appropriate checkbox and the selection
126 #if wxUSE_CHECKLISTBOX //Do it for both native and non-native
127 if (listbox->m_hasCheckBoxes)
128 {
129 wxCheckListBox *clb = (wxCheckListBox *)listbox;
130
131 clb->Check( sel, !clb->IsChecked(sel) );
132
133 wxCommandEvent new_event( wxEVT_COMMAND_CHECKLISTBOX_TOGGLED, listbox->GetId() );
134 new_event.SetEventObject( listbox );
135 new_event.SetInt( sel );
136 listbox->GetEventHandler()->ProcessEvent( new_event );
137 }
138 #endif // wxUSE_CHECKLISTBOX
139
140 if( (((listbox->GetWindowStyleFlag() & wxLB_MULTIPLE) != 0) ||
141 ((listbox->GetWindowStyleFlag() & wxLB_EXTENDED) != 0)) )
142 {
143 //toggle the selection + send event
144 listbox->GtkSetSelection(sel, !listbox->IsSelected( sel ), false);
145 }
146 }
147 }
148 }
149
150 //-----------------------------------------------------------------------------
151 // "button_press_event"
152 //-----------------------------------------------------------------------------
153
154 extern "C" {
155 static gint
156 gtk_listbox_button_press_callback( GtkWidget *widget,
157 GdkEventButton *gdk_event,
158 wxListBox *listbox )
159 {
160 if (g_isIdle) wxapp_install_idle_handler();
161
162 if (g_blockEventsOnDrag) return FALSE;
163 if (g_blockEventsOnScroll) return FALSE;
164
165 if (!listbox->m_hasVMT) return FALSE;
166
167 //Just to be on the safe side - it should be unset in the activate callback
168 //but we don't want any obscure bugs if it doesn't get called somehow...
169 listbox->m_spacePressed = false;
170
171 #if wxUSE_CHECKLISTBOX && !wxUSE_NATIVEGTKCHECKLIST
172 if ((listbox->m_hasCheckBoxes) && (gdk_event->x < 15) && (gdk_event->type != GDK_2BUTTON_PRESS))
173 {
174 GtkTreePath* path;
175 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
176 (gint)gdk_event->x, (gint)gdk_event->y,
177 &path, NULL, NULL, NULL);
178 int sel = gtk_tree_path_get_indices(path)[0];
179 gtk_tree_path_free(path);
180
181 wxCheckListBox *clb = (wxCheckListBox *)listbox;
182
183 clb->Check( sel, !clb->IsChecked(sel) );
184
185 wxCommandEvent event( wxEVT_COMMAND_CHECKLISTBOX_TOGGLED, listbox->GetId() );
186 event.SetEventObject( listbox );
187 event.SetInt( sel );
188 listbox->GetEventHandler()->ProcessEvent( event );
189 }
190 #endif // wxUSE_CHECKLISTBOX && !wxUSE_NATIVEGTKCHECKLIST
191
192 return FALSE;
193 }
194 }
195
196 //-----------------------------------------------------------------------------
197 // "key_press_event"
198 //-----------------------------------------------------------------------------
199
200 extern "C" {
201 static gint
202 gtk_listbox_key_press_callback( GtkWidget *widget,
203 GdkEventKey *gdk_event,
204 wxListBox *listbox )
205 {
206 if (g_isIdle) wxapp_install_idle_handler();
207
208 if (g_blockEventsOnDrag) return FALSE;
209
210
211 bool ret = false;
212
213 if ((gdk_event->keyval == GDK_Tab) || (gdk_event->keyval == GDK_ISO_Left_Tab))
214 {
215 wxNavigationKeyEvent new_event;
216 /* GDK reports GDK_ISO_Left_Tab for SHIFT-TAB */
217 new_event.SetDirection( (gdk_event->keyval == GDK_Tab) );
218 /* CTRL-TAB changes the (parent) window, i.e. switch notebook page */
219 new_event.SetWindowChange( (gdk_event->state & GDK_CONTROL_MASK) );
220 new_event.SetCurrentFocus( listbox );
221 ret = listbox->GetEventHandler()->ProcessEvent( new_event );
222 }
223
224 if ((gdk_event->keyval == GDK_Return) && (!ret))
225 {
226 // eat return in all modes (RN:WHY?)
227 ret = true;
228 }
229
230 // Check or uncheck item with SPACE
231 if (gdk_event->keyval == ' ')
232 {
233 //In the keyevent we don't know the index of the item
234 //and the activated event gets called anyway...
235 //
236 //Also, activating with the space causes the treeview to
237 //unselect all the items and then select the item in question
238 //wx's behaviour is to just toggle the item's selection state
239 //and leave the others alone
240 listbox->m_spacePressed = true;
241 }
242
243 if (ret)
244 {
245 g_signal_stop_emission_by_name (widget, "key_press_event");
246 return TRUE;
247 }
248
249 return FALSE;
250 }
251 }
252
253 //-----------------------------------------------------------------------------
254 // "select" and "deselect"
255 //-----------------------------------------------------------------------------
256
257 extern "C" {
258 static gboolean gtk_listitem_select_cb( GtkTreeSelection* selection,
259 GtkTreeModel* model,
260 GtkTreePath* path,
261 gboolean is_selected,
262 wxListBox *listbox )
263 {
264 if (g_isIdle) wxapp_install_idle_handler();
265
266 if (!listbox->m_hasVMT) return TRUE;
267 if (g_blockEventsOnDrag) return TRUE;
268
269 if (listbox->m_spacePressed) return FALSE; //see keyevent callback
270 if (listbox->m_blockEvent) return TRUE;
271
272 // NB: wxdocs explicitly say that this event only gets sent when
273 // something is actually selected, plus the controls example
274 // assumes so and passes -1 to the dogetclientdata funcs if not
275
276 // OK, so basically we need to do a bit of a run-around here as
277 // 1) is_selected says whether the item(s?) are CURRENTLY selected -
278 // i.e. if is_selected is FALSE then the item is going to be
279 // selected right now!
280 // 2) However, since it is not already selected and the user
281 // will expect it to be we need to manually select it and
282 // return FALSE telling GTK we handled the selection
283 if (is_selected) return TRUE;
284
285 int nIndex = gtk_tree_path_get_indices(path)[0];
286 GtkTreeEntry* entry = listbox->GtkGetEntry(nIndex);
287
288 if(entry)
289 {
290 //Now, as mentioned above, we manually select the row that is/was going
291 //to be selected anyway by GTK
292 listbox->m_blockEvent = true; //if we don't block events we will lock the
293 //app due to recursion!!
294
295 GtkTreeSelection* selection =
296 gtk_tree_view_get_selection(listbox->m_treeview);
297 GtkTreeIter iter;
298 gtk_tree_model_get_iter(GTK_TREE_MODEL(listbox->m_liststore), &iter, path);
299 gtk_tree_selection_select_iter(selection, &iter);
300
301 listbox->m_blockEvent = false;
302
303 //Finally, send the wx event
304 wxCommandEvent event(wxEVT_COMMAND_LISTBOX_SELECTED, listbox->GetId() );
305 event.SetEventObject( listbox );
306
307 // indicate whether this is a selection or a deselection
308 event.SetExtraLong( 1 );
309
310 event.SetInt(nIndex);
311 event.SetString(wxConvUTF8.cMB2WX(gtk_tree_entry_get_label(entry)));
312
313 if ( listbox->HasClientObjectData() )
314 event.SetClientObject(
315 (wxClientData*) gtk_tree_entry_get_userdata(entry)
316 );
317 else if ( listbox->HasClientUntypedData() )
318 event.SetClientData( gtk_tree_entry_get_userdata(entry) );
319
320 listbox->GetEventHandler()->ProcessEvent( event );
321
322 g_object_unref (entry);
323 return FALSE; //We handled it/did it manually
324 }
325
326 return TRUE; //allow selection to change
327 }
328 }
329
330 //-----------------------------------------------------------------------------
331 // GtkTreeEntry destruction (to destroy client data)
332 //-----------------------------------------------------------------------------
333
334 extern "C" {
335 static void gtk_tree_entry_destroy_cb(GtkTreeEntry* entry,
336 wxListBox* listbox)
337 {
338 if(listbox->HasClientObjectData())
339 {
340 gpointer userdata = gtk_tree_entry_get_userdata(entry);
341 if(userdata)
342 delete (wxClientData *)userdata;
343 }
344 }
345 }
346
347 //-----------------------------------------------------------------------------
348 // Sorting callback (standard CmpNoCase return value)
349 //-----------------------------------------------------------------------------
350
351 extern "C" {
352 static gint gtk_listbox_sort_callback(GtkTreeModel *model,
353 GtkTreeIter *a,
354 GtkTreeIter *b,
355 wxListBox *listbox)
356 {
357 GtkTreeEntry* entry;
358 GtkTreeEntry* entry2;
359
360 gtk_tree_model_get(GTK_TREE_MODEL(listbox->m_liststore),
361 a,
362 WXLISTBOX_DATACOLUMN_ARG(listbox),
363 &entry, -1);
364 gtk_tree_model_get(GTK_TREE_MODEL(listbox->m_liststore),
365 b,
366 WXLISTBOX_DATACOLUMN_ARG(listbox),
367 &entry2, -1);
368 wxCHECK_MSG(entry, 0, wxT("Could not get entry"));
369 wxCHECK_MSG(entry2, 0, wxT("Could not get entry2"));
370
371 //We compare collate keys here instead of calling g_utf8_collate
372 //as it is rather slow (and even the docs reccommend this)
373 int ret = strcasecmp(gtk_tree_entry_get_collate_key(entry),
374 gtk_tree_entry_get_collate_key(entry2));
375
376 g_object_unref (entry);
377 g_object_unref (entry2);
378
379 return ret;
380 }
381 }
382
383 //-----------------------------------------------------------------------------
384 // Searching callback (TRUE == not equal, FALSE == equal)
385 //-----------------------------------------------------------------------------
386
387 extern "C" {
388 static gboolean gtk_listbox_searchequal_callback(GtkTreeModel* model,
389 gint column,
390 const gchar* key,
391 GtkTreeIter* iter,
392 wxListBox* listbox)
393 {
394 GtkTreeEntry* entry;
395
396 gtk_tree_model_get(GTK_TREE_MODEL(listbox->m_liststore),
397 iter,
398 WXLISTBOX_DATACOLUMN_ARG(listbox),
399 &entry, -1);
400 wxCHECK_MSG(entry, 0, wxT("Could not get entry"));
401 gchar* keycollatekey = g_utf8_collate_key(key, -1);
402
403 int ret = strcasecmp(keycollatekey,
404 gtk_tree_entry_get_collate_key(entry));
405
406 g_free(keycollatekey);
407 g_object_unref (entry);
408
409 return ret != 0;
410 }
411 }
412
413 //-----------------------------------------------------------------------------
414 // wxListBox
415 //-----------------------------------------------------------------------------
416
417 IMPLEMENT_DYNAMIC_CLASS(wxListBox, wxControl)
418
419 // ----------------------------------------------------------------------------
420 // construction
421 // ----------------------------------------------------------------------------
422
423 void wxListBox::Init()
424 {
425 m_treeview = (GtkTreeView*) NULL;
426 #if wxUSE_CHECKLISTBOX
427 m_hasCheckBoxes = false;
428 #endif // wxUSE_CHECKLISTBOX
429 m_spacePressed = false;
430 }
431
432 bool wxListBox::Create( wxWindow *parent, wxWindowID id,
433 const wxPoint &pos, const wxSize &size,
434 const wxArrayString& choices,
435 long style, const wxValidator& validator,
436 const wxString &name )
437 {
438 wxCArrayString chs(choices);
439
440 return Create( parent, id, pos, size, chs.GetCount(), chs.GetStrings(),
441 style, validator, name );
442 }
443
444 bool wxListBox::Create( wxWindow *parent, wxWindowID id,
445 const wxPoint &pos, const wxSize &size,
446 int n, const wxString choices[],
447 long style, const wxValidator& validator,
448 const wxString &name )
449 {
450 m_needParent = true;
451 m_acceptsFocus = true;
452 m_blockEvent = false;
453
454 if (!PreCreation( parent, pos, size ) ||
455 !CreateBase( parent, id, pos, size, style, validator, name ))
456 {
457 wxFAIL_MSG( wxT("wxListBox creation failed") );
458 return false;
459 }
460
461 m_widget = gtk_scrolled_window_new( (GtkAdjustment*) NULL, (GtkAdjustment*) NULL );
462 if (style & wxLB_ALWAYS_SB)
463 {
464 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(m_widget),
465 GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS );
466 }
467 else
468 {
469 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(m_widget),
470 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
471 }
472
473
474 GtkScrolledWindowSetBorder(m_widget, style);
475
476 m_treeview = GTK_TREE_VIEW( gtk_tree_view_new( ) );
477
478 //wxListBox doesn't have a header :)
479 //NB: If enabled SetFirstItem doesn't work correctly
480 gtk_tree_view_set_headers_visible(m_treeview, FALSE);
481
482 #if wxUSE_CHECKLISTBOX && wxUSE_NATIVEGTKCHECKLIST
483 if(m_hasCheckBoxes)
484 ((wxCheckListBox*)this)->DoCreateCheckList();
485 #endif // wxUSE_CHECKLISTBOX && wxUSE_NATIVEGTKCHECKLIST
486
487 // Create the data column
488 gtk_tree_view_insert_column_with_attributes(m_treeview, -1, "",
489 gtk_cell_renderer_text_new(),
490 "text",
491 WXLISTBOX_DATACOLUMN, NULL);
492
493 // Now create+set the model (GtkListStore) - first argument # of columns
494 #if wxUSE_CHECKLISTBOX && wxUSE_NATIVEGTKCHECKLIST
495 if(m_hasCheckBoxes)
496 m_liststore = gtk_list_store_new(2, G_TYPE_BOOLEAN,
497 GTK_TYPE_TREE_ENTRY);
498 else
499 #endif
500 m_liststore = gtk_list_store_new(1, GTK_TYPE_TREE_ENTRY);
501
502 gtk_tree_view_set_model(m_treeview, GTK_TREE_MODEL(m_liststore));
503
504 g_object_unref (m_liststore); //free on treeview destruction
505
506 // Disable the pop-up textctrl that enables searching - note that
507 // the docs specify that even if this disabled (which we are doing)
508 // the user can still have it through the start-interactive-search
509 // key binding...either way we want to provide a searchequal callback
510 // NB: If this is enabled a doubleclick event (activate) gets sent
511 // on a successful search
512 gtk_tree_view_set_search_column(m_treeview, WXLISTBOX_DATACOLUMN);
513 gtk_tree_view_set_search_equal_func(m_treeview,
514 (GtkTreeViewSearchEqualFunc) gtk_listbox_searchequal_callback,
515 this,
516 NULL);
517
518 gtk_tree_view_set_enable_search(m_treeview, FALSE);
519
520
521 GtkTreeSelection* selection = gtk_tree_view_get_selection( m_treeview );
522 gtk_tree_selection_set_select_function(selection,
523 (GtkTreeSelectionFunc)gtk_listitem_select_cb,
524 this, NULL); //NULL == destroycb
525
526 GtkSelectionMode mode;
527 if (style & wxLB_MULTIPLE)
528 {
529 mode = GTK_SELECTION_MULTIPLE;
530 }
531 else if (style & wxLB_EXTENDED)
532 {
533 mode = GTK_SELECTION_EXTENDED;
534 }
535 else
536 {
537 // if style was 0 set single mode
538 m_windowStyle |= wxLB_SINGLE;
539 mode = GTK_SELECTION_SINGLE;
540 }
541
542 gtk_tree_selection_set_mode( selection, mode );
543
544 //Handle sortable stuff
545 if(style & wxLB_SORT)
546 {
547 //Setup sorting in ascending (wx) order
548 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(m_liststore),
549 WXLISTBOX_DATACOLUMN,
550 GTK_SORT_ASCENDING);
551
552 //Set the sort callback
553 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(m_liststore),
554 WXLISTBOX_DATACOLUMN,
555 (GtkTreeIterCompareFunc) gtk_listbox_sort_callback,
556 this, //userdata
557 NULL //"destroy notifier"
558 );
559 }
560
561
562 gtk_container_add (GTK_CONTAINER (m_widget), GTK_WIDGET(m_treeview) );
563
564 gtk_widget_show( GTK_WIDGET(m_treeview) );
565
566 wxListBox::DoInsertItems(wxArrayString(n, choices), 0); // insert initial items
567
568 //treeview-specific events
569 g_signal_connect(m_treeview, "row-activated",
570 G_CALLBACK(gtk_listbox_row_activated_callback), this);
571
572 // other events
573 g_signal_connect (m_treeview, "button_press_event",
574 G_CALLBACK (gtk_listbox_button_press_callback),
575 this);
576 g_signal_connect (m_treeview, "key_press_event",
577 G_CALLBACK (gtk_listbox_key_press_callback),
578 this);
579
580 m_parent->DoAddChild( this );
581
582 PostCreation(size);
583 SetBestSize(size); // need this too because this is a wxControlWithItems
584
585 return true;
586 }
587
588 wxListBox::~wxListBox()
589 {
590 m_hasVMT = false;
591
592 Clear();
593 }
594
595 // ----------------------------------------------------------------------------
596 // adding items
597 // ----------------------------------------------------------------------------
598
599 void wxListBox::GtkInsertItems(const wxArrayString& items,
600 void** clientData, unsigned int pos)
601 {
602 wxCHECK_RET( m_treeview != NULL, wxT("invalid listbox") );
603
604 InvalidateBestSize();
605
606 // Create and set column ids and GValues
607
608 unsigned int nNum = items.GetCount();
609 unsigned int nCurCount = wxListBox::GetCount();
610 wxASSERT_MSG(pos <= nCurCount, wxT("Invalid index passed to wxListBox"));
611
612 GtkTreeIter* pIter = NULL; // append by default
613 GtkTreeIter iter;
614 if (pos != nCurCount)
615 {
616 gboolean res = gtk_tree_model_iter_nth_child(
617 GTK_TREE_MODEL(m_liststore),
618 &iter, NULL, //NULL = parent = get first
619 (int)pos );
620 if(!res)
621 {
622 wxLogSysError(wxT("internal wxListBox error in insertion"));
623 return;
624 }
625
626 pIter = &iter;
627 }
628
629 for (unsigned int i = 0; i < nNum; ++i)
630 {
631 wxString label = items[i];
632
633 #if wxUSE_CHECKLISTBOX && !wxUSE_NATIVEGTKCHECKLIST
634 if (m_hasCheckBoxes)
635 {
636 label.Prepend(wxCHECKLBOX_STRING);
637 }
638 #endif // wxUSE_CHECKLISTBOX
639
640
641 GtkTreeEntry* entry = gtk_tree_entry_new();
642 gtk_tree_entry_set_label(entry, wxConvUTF8.cWX2MB(label));
643 gtk_tree_entry_set_destroy_func(entry,
644 (GtkTreeEntryDestroy)gtk_tree_entry_destroy_cb,
645 this);
646
647 if (clientData)
648 gtk_tree_entry_set_userdata(entry, clientData[i]);
649
650 GtkTreeIter itercur;
651 gtk_list_store_insert_before(m_liststore, &itercur, pIter);
652
653 #if wxUSE_CHECKLISTBOX && wxUSE_NATIVEGTKCHECKLIST
654 if (m_hasCheckBoxes)
655 {
656 gtk_list_store_set(m_liststore, &itercur,
657 0, FALSE, //FALSE == not toggled
658 1, entry, -1);
659 }
660 else
661 #endif
662 gtk_list_store_set(m_liststore, &itercur,
663 0, entry, -1);
664
665 g_object_unref (entry); //liststore always refs :)
666 }
667 }
668
669 void wxListBox::DoInsertItems(const wxArrayString& items, unsigned int pos)
670 {
671 wxCHECK_RET( IsValidInsert(pos), wxT("invalid index in wxListBox::InsertItems") );
672
673 GtkInsertItems(items, NULL, pos);
674 }
675
676 int wxListBox::DoAppend( const wxString& item )
677 {
678 // Call DoInsertItems
679 unsigned int nWhere = wxListBox::GetCount();
680 wxArrayString aItems;
681 aItems.Add(item);
682 wxListBox::DoInsertItems(aItems, nWhere);
683 return nWhere;
684 }
685
686 void wxListBox::DoSetItems( const wxArrayString& items,
687 void **clientData)
688 {
689 Clear();
690 GtkInsertItems(items, clientData, 0);
691 }
692
693 // ----------------------------------------------------------------------------
694 // deleting items
695 // ----------------------------------------------------------------------------
696
697 void wxListBox::Clear()
698 {
699 wxCHECK_RET( m_treeview != NULL, wxT("invalid listbox") );
700
701 InvalidateBestSize();
702
703 gtk_list_store_clear( m_liststore ); /* well, THAT was easy :) */
704 }
705
706 void wxListBox::Delete(unsigned int n)
707 {
708 wxCHECK_RET( m_treeview != NULL, wxT("invalid listbox") );
709
710 InvalidateBestSize();
711
712 GtkTreeIter iter;
713 gboolean res = gtk_tree_model_iter_nth_child(
714 GTK_TREE_MODEL(m_liststore),
715 &iter, NULL, //NULL = parent = get first
716 n
717 );
718
719 wxCHECK_RET( res, wxT("wrong listbox index") );
720
721 //this returns false if iter is invalid (i.e. deleting item
722 //at end) but since we don't use iter, well... :)
723 gtk_list_store_remove(m_liststore, &iter);
724 }
725
726 // ----------------------------------------------------------------------------
727 // get GtkTreeEntry from position (note: you need to g_unref it if valid)
728 // ----------------------------------------------------------------------------
729
730 struct _GtkTreeEntry* wxListBox::GtkGetEntry(int n) const
731 {
732 GtkTreeIter iter;
733 gboolean res = gtk_tree_model_iter_nth_child(
734 GTK_TREE_MODEL(m_liststore),
735 &iter, NULL, //NULL = parent = get first
736 n );
737
738 if (!res)
739 {
740 wxLogDebug(wxT("gtk_tree_model_iter_nth_child failed\n")
741 wxT("Passed in value was:[%i] List size:[%u]"),
742 n, wxListBox::GetCount() );
743 return NULL;
744 }
745
746
747 GtkTreeEntry* entry = NULL;
748 gtk_tree_model_get(GTK_TREE_MODEL(m_liststore), &iter,
749 WXLISTBOX_DATACOLUMN, &entry, -1);
750
751 return entry;
752 }
753
754 // ----------------------------------------------------------------------------
755 // client data
756 // ----------------------------------------------------------------------------
757
758 void* wxListBox::DoGetItemClientData(unsigned int n) const
759 {
760 wxCHECK_MSG( IsValid(n), NULL,
761 wxT("Invalid index passed to GetItemClientData") );
762
763 GtkTreeEntry* entry = GtkGetEntry(n);
764 wxCHECK_MSG(entry, NULL, wxT("could not get entry"));
765
766 void* userdata = gtk_tree_entry_get_userdata( entry );
767 g_object_unref (entry);
768 return userdata;
769 }
770
771 wxClientData* wxListBox::DoGetItemClientObject(unsigned int n) const
772 {
773 return (wxClientData*) wxListBox::DoGetItemClientData(n);
774 }
775
776 void wxListBox::DoSetItemClientData(unsigned int n, void* clientData)
777 {
778 wxCHECK_RET( IsValid(n),
779 wxT("Invalid index passed to SetItemClientData") );
780
781 GtkTreeEntry* entry = GtkGetEntry(n);
782 wxCHECK_RET(entry, wxT("could not get entry"));
783
784 gtk_tree_entry_set_userdata( entry, clientData );
785 g_object_unref (entry);
786 }
787
788 void wxListBox::DoSetItemClientObject(unsigned int n, wxClientData* clientData)
789 {
790 // wxItemContainer already deletes data for us
791 wxListBox::DoSetItemClientData(n, (void*) clientData);
792 }
793
794 // ----------------------------------------------------------------------------
795 // string list access
796 // ----------------------------------------------------------------------------
797
798 void wxListBox::SetString(unsigned int n, const wxString &string)
799 {
800 wxCHECK_RET( IsValid(n), wxT("invalid index in wxListBox::SetString") );
801 wxCHECK_RET( m_treeview != NULL, wxT("invalid listbox") );
802
803 GtkTreeEntry* entry = GtkGetEntry(n);
804 wxCHECK_RET( entry, wxT("wrong listbox index") );
805
806 wxString label = string;
807
808 #if wxUSE_CHECKLISTBOX && !wxUSE_NATIVEGTKCHECKLIST
809 if (m_hasCheckBoxes)
810 label.Prepend(wxCHECKLBOX_STRING);
811 #endif // wxUSE_CHECKLISTBOX
812
813 // RN: This may look wierd but the problem is that the TreeView
814 // doesn't resort or update when changed above and there is no real
815 // notification function...
816 void* userdata = gtk_tree_entry_get_userdata(entry);
817 gtk_tree_entry_set_userdata(entry, NULL); //don't delete on destroy
818 g_object_unref (entry);
819
820 bool bWasSelected = wxListBox::IsSelected(n);
821 wxListBox::Delete(n);
822
823 wxArrayString aItems;
824 aItems.Add(label);
825 GtkInsertItems(aItems, &userdata, n);
826 if (bWasSelected)
827 wxListBox::GtkSetSelection(n, true, true);
828 }
829
830 wxString wxListBox::GetString(unsigned int n) const
831 {
832 wxCHECK_MSG( m_treeview != NULL, wxEmptyString, wxT("invalid listbox") );
833
834 GtkTreeEntry* entry = GtkGetEntry(n);
835 wxCHECK_MSG( entry, wxEmptyString, wxT("wrong listbox index") );
836
837 wxString label = wxGTK_CONV_BACK( gtk_tree_entry_get_label(entry) );
838
839 #if wxUSE_CHECKLISTBOX && !wxUSE_NATIVEGTKCHECKLIST
840 // checklistboxes have "[±] " prepended to their lables, remove it
841 //
842 // NB: 4 below is the length of wxCHECKLBOX_STRING from wx/gtk/checklst.h
843 if ( m_hasCheckBoxes )
844 label.erase(0, 4);
845 #endif // wxUSE_CHECKLISTBOX
846
847 g_object_unref (entry);
848 return label;
849 }
850
851 unsigned int wxListBox::GetCount() const
852 {
853 wxCHECK_MSG( m_treeview != NULL, 0, wxT("invalid listbox") );
854
855 return (unsigned int)gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_liststore), NULL);
856 }
857
858 int wxListBox::FindString( const wxString &item, bool bCase ) const
859 {
860 wxCHECK_MSG( m_treeview != NULL, wxNOT_FOUND, wxT("invalid listbox") );
861
862 //Sort of hackish - maybe there is a faster way
863 unsigned int nCount = wxListBox::GetCount();
864
865 for(unsigned int i = 0; i < nCount; ++i)
866 {
867 if( item.IsSameAs( wxListBox::GetString(i), bCase ) )
868 return (int)i;
869 }
870
871
872 // it's not an error if the string is not found -> no wxCHECK
873 return wxNOT_FOUND;
874 }
875
876 // ----------------------------------------------------------------------------
877 // selection
878 // ----------------------------------------------------------------------------
879
880 int wxListBox::GetSelection() const
881 {
882 wxCHECK_MSG( m_treeview != NULL, wxNOT_FOUND, wxT("invalid listbox"));
883 wxCHECK_MSG( HasFlag(wxLB_SINGLE), wxNOT_FOUND,
884 wxT("must be single selection listbox"));
885
886 GtkTreeIter iter;
887 GtkTreeSelection* selection = gtk_tree_view_get_selection(m_treeview);
888
889 // only works on single-sel
890 if (!gtk_tree_selection_get_selected(selection, NULL, &iter))
891 return wxNOT_FOUND;
892
893 GtkTreePath* path =
894 gtk_tree_model_get_path(GTK_TREE_MODEL(m_liststore), &iter);
895
896 int sel = gtk_tree_path_get_indices(path)[0];
897
898 gtk_tree_path_free(path);
899
900 return sel;
901 }
902
903 int wxListBox::GetSelections( wxArrayInt& aSelections ) const
904 {
905 wxCHECK_MSG( m_treeview != NULL, wxNOT_FOUND, wxT("invalid listbox") );
906
907 aSelections.Empty();
908
909 int i = 0;
910 GtkTreeIter iter;
911 GtkTreeSelection* selection = gtk_tree_view_get_selection(m_treeview);
912
913 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(m_liststore), &iter))
914 { //gtk_tree_selection_get_selected_rows is GTK 2.2+ so iter instead
915 do
916 {
917 if (gtk_tree_selection_iter_is_selected(selection, &iter))
918 aSelections.Add(i);
919
920 i++;
921 } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(m_liststore), &iter));
922 }
923
924 return aSelections.GetCount();
925 }
926
927 bool wxListBox::IsSelected( int n ) const
928 {
929 wxCHECK_MSG( m_treeview != NULL, false, wxT("invalid listbox") );
930
931 GtkTreeSelection* selection = gtk_tree_view_get_selection(m_treeview);
932
933 GtkTreeIter iter;
934 gboolean res = gtk_tree_model_iter_nth_child(
935 GTK_TREE_MODEL(m_liststore),
936 &iter, NULL, //NULL = parent = get first
937 n );
938
939 wxCHECK_MSG( res, false, wxT("Invalid index") );
940
941 return gtk_tree_selection_iter_is_selected(selection, &iter);
942 }
943
944 void wxListBox::DoSetSelection( int n, bool select )
945 {
946 // passing -1 to SetSelection() is documented to deselect all items
947 if ( n == wxNOT_FOUND )
948 {
949 // ... and not generate any events in the process
950 GtkDeselectAll();
951 }
952
953 wxCHECK_RET( IsValid(n), wxT("invalid index in wxListBox::SetSelection") );
954
955 // don't generate the selection event
956 GtkSetSelection(n, select, true);
957 }
958
959 void wxListBox::GtkDeselectAll()
960 {
961 wxCHECK_RET( m_treeview != NULL, wxT("invalid listbox") );
962
963 GtkTreeSelection* selection = gtk_tree_view_get_selection(m_treeview);
964
965 m_blockEvent = true;
966
967 gtk_tree_selection_unselect_all(selection);
968
969 m_blockEvent = false;
970 }
971
972 void wxListBox::GtkSetSelection(int n, const bool select, const bool blockEvent)
973 {
974 wxCHECK_RET( m_treeview != NULL, wxT("invalid listbox") );
975
976 GtkTreeSelection* selection = gtk_tree_view_get_selection(m_treeview);
977
978 GtkTreeIter iter;
979 gboolean res = gtk_tree_model_iter_nth_child(
980 GTK_TREE_MODEL(m_liststore),
981 &iter, NULL, //NULL = parent = get first
982 n
983 );
984 wxCHECK_RET( res, wxT("Invalid index") );
985
986 m_blockEvent = blockEvent;
987
988 if (select)
989 gtk_tree_selection_select_iter(selection, &iter);
990 else
991 gtk_tree_selection_unselect_iter(selection, &iter);
992
993 m_blockEvent = false;
994 }
995
996 void wxListBox::DoSetFirstItem( int n )
997 {
998 wxCHECK_RET( m_treeview, wxT("invalid listbox") );
999 wxCHECK_RET( IsValid(n), wxT("invalid index"));
1000
1001 //RN: I have no idea why this line is needed...
1002 if (gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (m_treeview))
1003 return;
1004
1005 GtkTreeIter iter;
1006 gtk_tree_model_iter_nth_child(
1007 GTK_TREE_MODEL(m_liststore),
1008 &iter,
1009 NULL, //NULL = parent = get first
1010 n
1011 );
1012
1013 GtkTreePath* path = gtk_tree_model_get_path(
1014 GTK_TREE_MODEL(m_liststore), &iter);
1015
1016 // Scroll to the desired cell (0.0 == topleft alignment)
1017 gtk_tree_view_scroll_to_cell(m_treeview, path, NULL,
1018 TRUE, 0.0f, 0.0f);
1019
1020 gtk_tree_path_free(path);
1021 }
1022
1023 // ----------------------------------------------------------------------------
1024 // hittest
1025 // ----------------------------------------------------------------------------
1026
1027 int wxListBox::DoListHitTest(const wxPoint& point) const
1028 {
1029 // gtk_tree_view_get_path_at_pos() also gets items that are not visible and
1030 // we only want visible items we need to check for it manually here
1031 if ( !GetClientRect().Inside(point) )
1032 return wxNOT_FOUND;
1033
1034 // need to translate from master window since it is in client coords
1035 gint binx, biny;
1036 gdk_window_get_geometry(gtk_tree_view_get_bin_window(m_treeview),
1037 &binx, &biny, NULL, NULL, NULL);
1038
1039 GtkTreePath* path;
1040 if ( !gtk_tree_view_get_path_at_pos
1041 (
1042 m_treeview,
1043 point.x - binx,
1044 point.y - biny,
1045 &path,
1046 NULL, // [out] column (always 0 here)
1047 NULL, // [out] x-coord relative to the cell (not interested)
1048 NULL // [out] y-coord relative to the cell
1049 ) )
1050 {
1051 return wxNOT_FOUND;
1052 }
1053
1054 int index = gtk_tree_path_get_indices(path)[0];
1055 gtk_tree_path_free(path);
1056
1057 return index;
1058 }
1059
1060 // ----------------------------------------------------------------------------
1061 // helpers
1062 // ----------------------------------------------------------------------------
1063
1064 #if wxUSE_TOOLTIPS
1065 void wxListBox::ApplyToolTip( GtkTooltips *tips, const wxChar *tip )
1066 {
1067 // RN: Is this needed anymore?
1068 gtk_tooltips_set_tip( tips, GTK_WIDGET( m_treeview ), wxGTK_CONV(tip), (gchar*) NULL );
1069 }
1070 #endif // wxUSE_TOOLTIPS
1071
1072 GtkWidget *wxListBox::GetConnectWidget()
1073 {
1074 // the correct widget for listbox events (such as mouse clicks for example)
1075 // is m_treeview, not the parent scrolled window
1076 return GTK_WIDGET(m_treeview);
1077 }
1078
1079 GdkWindow *wxListBox::GTKGetWindow(wxArrayGdkWindows& WXUNUSED(windows)) const
1080 {
1081 return gtk_tree_view_get_bin_window(m_treeview);
1082 }
1083
1084 void wxListBox::DoApplyWidgetStyle(GtkRcStyle *style)
1085 {
1086 if (m_hasBgCol && m_backgroundColour.Ok())
1087 {
1088 GdkWindow *window = gtk_tree_view_get_bin_window(m_treeview);
1089 if (window)
1090 {
1091 m_backgroundColour.CalcPixel( gdk_drawable_get_colormap( window ) );
1092 gdk_window_set_background( window, m_backgroundColour.GetColor() );
1093 gdk_window_clear( window );
1094 }
1095 }
1096
1097 gtk_widget_modify_style( GTK_WIDGET(m_treeview), style );
1098 }
1099
1100 wxSize wxListBox::DoGetBestSize() const
1101 {
1102 wxCHECK_MSG(m_treeview, wxDefaultSize, wxT("invalid tree view"));
1103
1104 // Start with a minimum size that's not too small
1105 int cx, cy;
1106 GetTextExtent( wxT("X"), &cx, &cy);
1107 int lbWidth = 3 * cx;
1108 int lbHeight = 10;
1109
1110 // Get the visible area of the tree view (limit to the 10th item
1111 // so that it isn't too big)
1112 unsigned int count = GetCount();
1113 if (count)
1114 {
1115 int wLine;
1116
1117 // Find the widest line
1118 for(unsigned int i = 0; i < count; i++) {
1119 wxString str(GetString(i));
1120 GetTextExtent(str, &wLine, NULL);
1121 lbWidth = wxMax(lbWidth, wLine);
1122 }
1123
1124 lbWidth += 3 * cx;
1125
1126 // And just a bit more for the checkbox if present and then some
1127 // (these are rough guesses)
1128 #if wxUSE_CHECKLISTBOX && wxUSE_NATIVEGTKCHECKLIST
1129 if ( m_hasCheckBoxes )
1130 {
1131 lbWidth += 35;
1132 cy = cy > 25 ? cy : 25; // rough height of checkbox
1133 }
1134 #endif
1135
1136 // don't make the listbox too tall (limit height to around 10 items) but don't
1137 // make it too small neither
1138 lbHeight = (cy+4) * wxMin(wxMax(count, 3), 10);
1139 }
1140
1141 // Add room for the scrollbar
1142 lbWidth += wxSystemSettings::GetMetric(wxSYS_VSCROLL_X);
1143
1144 wxSize best(lbWidth, lbHeight);
1145 CacheBestSize(best);
1146 return best;
1147 }
1148
1149 // static
1150 wxVisualAttributes
1151 wxListBox::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
1152 {
1153 return GetDefaultAttributesFromGTKWidget(gtk_tree_view_new, true);
1154 }
1155
1156 #endif // wxUSE_LISTBOX