gtk-hl-tree: Support for other CellRenderers #45

bonanza opened this Issue Jul 21, 2012 · 8 comments


None yet
3 participants

bonanza commented Jul 21, 2012

It will be nice to have support for other CellRenderers than GtkCellRendererText in gtk-hl-tree - e.g. toggles and combos can be very useful in treeviews. Maybe this can be realized with an additional optional argument in hl_gtk_tree_new. However, then also the data handling for the callback routines have to be adapted.


jtappin commented Jul 23, 2012

An interesting idea, but definitely non-trivial. The levels of abstraction in treeview widgets mean that it is necessary to hold several different parts in mind at one time while designing the code.

From taking a quick look at the documentation, I think that the non-editable pixbufs and progress bars (which are the 2 that would be most useful to me) might be the easiest to implement, while toggles would probably need the most new coding (comboboxes and spinbuttons are both derived from text renderers, and so might be able to use the same default handlers.).

I think that possibly some higher-level glib and gobject tools may be the first step as an awful lot of the code in the list/tree high-level source code is just concerned with matching types and setting up gvalues.


bonanza commented Jul 23, 2012

I don't think that it is very difficult to implement this. However, higher-level glib and gobject tools will be very helpful in general. I created a modified hl_gtk_tree_new routine and modified your hl-tree example to support toggles in the tree - it was not really difficult:

! gfortran hl_tree_toggle.f90 -o hltreet `pkg-config --cflags --libs gtk-2-fortran`

module hl_tree_modified
  use gtk_hl
  use gtk, only: gtk_cell_renderer_toggle_new
  implicit none
  function hl_gtk_tree_new_modified(scroll, ncols, types, changed, data, multiple,&
       & width, titles, height, swidth, align, ixpad, iypad, sensitive, &
       & tooltip, sortable, editable, colnos, edited, data_edited, renderers, toggled) result(tree)
    type(c_ptr) :: tree
    type(c_ptr), intent(out) :: scroll
    integer(kind=c_int), intent(in), optional :: ncols
    integer(kind=type_kind), dimension(:), intent(in), optional :: types
    type(c_funptr), optional :: changed
    type(c_ptr), intent(in), optional :: data
    integer(kind=c_int), intent(in), optional :: multiple
    integer(kind=c_int), intent(in), optional, dimension(:) :: width
    character(len=*), dimension(:), intent(in), optional :: titles
    integer(kind=c_int), intent(in), optional :: height, swidth
    real(kind=c_float), intent(in), optional, dimension(:) :: align
    integer(kind=c_int), intent(in), optional, dimension(:) :: ixpad, iypad
    integer(kind=c_int), intent(in), optional :: sensitive
    character(kind=c_char), dimension(*), intent(in), optional :: tooltip
    integer(kind=c_int), intent(in), optional, dimension(:) :: sortable, editable
    integer(kind=c_int), dimension(:), allocatable, intent(out), optional, target :: colnos
    type(c_funptr), optional :: edited
    type(c_ptr), optional, intent(in) :: data_edited
    character(len=*), dimension(:), intent(in), optional :: renderers !types of renderers
    type(c_funptr), optional :: toggled !pointer to toggled callback
    integer(kind=c_int) :: ncols_all, nc, i
    integer(kind=type_kind), dimension(:), allocatable, target :: types_all

    type(c_ptr) :: model, renderer, column, select
    type(gvalue), target :: isedit
    type(c_ptr) :: pisedit

    ! First find how many columns there are.

    if (present(ncols)) then
       ncols_all = ncols
    else if (present(types)) then
       ncols_all = size(types)
    else if (present(titles)) then
       ncols_all = size(titles)
    else if (present(align)) then
       ncols_all = size(align)
    else if (present(width)) then
       ncols_all = size(width)
    else if (present(sortable)) then
       ncols_all = size(sortable)
    else if (present(ixpad)) then
       ncols_all = size(ixpad)
    else if (present(iypad)) then
       ncols_all = size(iypad)
    else if (present(editable)) then
       ncols_all = size(editable)
       write(error_unit,*) "hl_gtk_tree_new: Cannot determine the number of columns"
       tree = C_NULL_PTR
    end if

    ! Now determine the column types.
    if (present(types)) then
       types_all = types
       types_all = (/ (ncols_all-1)*g_type_string /)
    end if

    ! If editable is present, initialize the GValue
    if (present(editable)) then
       if (.not. present(colnos)) then
          write(error_unit,*) "hl_gtk_listn_new: EDITABLE requires COLNOS"
       end if
       pisedit = c_loc(isedit)
       pisedit = g_value_init(pisedit, G_TYPE_BOOLEAN)
       colnos = (/ (i-1, i=1, ncols_all) /)
    end if

    ! Create the storage model
    model = gtk_tree_store_newv(ncols_all, c_loc(types_all))

    ! Create the tree in the scroll box
    scroll = gtk_scrolled_window_new(C_NULL_PTR, C_NULL_PTR)
    call gtk_scrolled_window_set_policy(scroll, GTK_POLICY_AUTOMATIC, &
    tree = gtk_tree_view_new_with_model(model)
    call gtk_container_add(scroll, tree)
    if (present(height) .and. present(swidth)) then
       call gtk_widget_set_size_request(scroll,swidth,height)
    else if (present(height)) then
       call gtk_widget_set_size_request(scroll,0,height)
    else if (present(swidth)) then
       call gtk_widget_set_size_request(scroll,swidth,0)
    end if

    ! Set up the columns
    do i = 1, ncols_all
      if (present(renderers)) then
        select case (renderers(i))
            renderer = gtk_cell_renderer_text_new()
            renderer = gtk_cell_renderer_toggle_new()
          !further renderers here ....
          case default
            renderer = gtk_cell_renderer_text_new()
        end select
        renderer = gtk_cell_renderer_text_new()
       if (present(ixpad) .and. present(iypad)) then
          call gtk_cell_renderer_set_padding(renderer, &
               & ixpad(i), iypad(i))
       else if (present(ixpad)) then
          call gtk_cell_renderer_set_padding(renderer, &
               & ixpad(i), 0)
       else if (present(iypad)) then
          call gtk_cell_renderer_set_padding(renderer, &
               & 0, iypad(i))
       end if
       if (present(align)) then
          call gtk_cell_renderer_set_alignment(renderer, align(i), 0.)
       else if (types_all(i) == G_TYPE_STRING) then
          call gtk_cell_renderer_set_alignment(renderer, 0., 0.)
          call gtk_cell_renderer_set_alignment(renderer, 1., 0.)
       end if
       if (present(editable)) then
          call g_value_set_boolean(pisedit, editable(i))
          if (present(renderers)) then
            select case (renderers(i))
                call g_object_set_property(renderer, "editable"//c_null_char, pisedit)
                call g_object_set_property(renderer, "activatable"//c_null_char, pisedit)
              !further renderers here ....
              case default
                call g_object_set_property(renderer, "editable"//c_null_char, pisedit)
            end select
            call g_object_set_property(renderer, "editable"//c_null_char, pisedit)
          if (editable(i) == TRUE) then
             call g_object_set_data(renderer, "column-number"//c_null_char, &
                  & c_loc(colnos(i)))
             call g_object_set_data(renderer, "view"//c_null_char, tree)
            if (present(renderers)) then
              select case (renderers(i))
                  if (present(edited)) then
                    if (present(data_edited)) then
                      call g_signal_connect(renderer, signal_edited, &
                        & edited, data_edited)
                      call g_signal_connect(renderer, signal_edited, &
                        & edited)
                    end if
                    call g_signal_connect(renderer, signal_edited, &
                     & c_funloc(hl_gtk_tree_edit_cb))
                  if (present(toggled)) then
                    call g_signal_connect(renderer, signal_edited, &
                      & toggled)
                !further renderers here ....
                case default
              end select
          end if
       end if

       column = gtk_tree_view_column_new()
       call gtk_tree_view_column_pack_start(column, renderer, FALSE)
       if (present(titles)) call gtk_tree_view_column_set_title(column, &
      if (present(renderers)) then
        select case (renderers(i))
            call gtk_tree_view_column_add_attribute(column, renderer, &
              & "text"//C_NULL_CHAR, i-1)
            call gtk_tree_view_column_add_attribute(column, renderer, &
              & "active"//C_NULL_CHAR, i-1)
          !further renderers here ....
          case default
            call gtk_tree_view_column_add_attribute(column, renderer, &
              & "text"//C_NULL_CHAR, i-1)
        end select
        call gtk_tree_view_column_add_attribute(column, renderer, &
           & "text"//C_NULL_CHAR, i-1)
       nc = gtk_tree_view_append_column(tree, column)
       if (present(sortable)) then
          if (sortable(i) == TRUE) then
             call gtk_tree_view_column_set_sort_column_id(column, i-1)
             call gtk_tree_view_column_set_sort_indicator(column, TRUE)
          end if
       end if
       if (present(width)) then
          call gtk_tree_view_column_set_sizing (column, &
               & GTK_TREE_VIEW_COLUMN_FIXED)
          call gtk_tree_view_column_set_fixed_width(column, width(i))
       end if
       call gtk_tree_view_column_set_resizable(column,TRUE)
    end do

    ! The event handler is attached to the selection object, as is
    ! the multiple selection property.

    select = gtk_tree_view_get_selection(tree)

    if (present(multiple)) then
       if (multiple == TRUE) &
            & call gtk_tree_selection_set_mode(select, GTK_SELECTION_MULTIPLE)
    end if

    if (present(changed)) then
       if (present(data)) then
          call g_signal_connect(select, "changed"//c_null_char, changed, data)
          call g_signal_connect(select, "changed"//c_null_char, changed)
       end if
    end if

    if (present(tooltip)) call gtk_widget_set_tooltip_text(tree, tooltip)

    if (present(sensitive)) &
         & call gtk_widget_set_sensitive(tree, sensitive)

  end function hl_gtk_tree_new_modified

end module hl_tree_modified
module tr_handlers
  use gtk_hl
  use hl_tree_modified
  use gtk, only: gtk_button_new, gtk_check_button_new, gtk_container_add, gtk_ent&
       &ry_get_text, gtk_entry_get_text_length, gtk_entry_new, gtk_entry_set_text, gtk&
       &_main, gtk_main_quit, gtk_widget_destroy, gtk_toggle_button_get_active, gtk_to&
       &ggle_button_set_active, gtk_widget_show, gtk_widget_show_all, gtk_window_new, &
       & gtk_init
  use g, only: alloca

  implicit none

  ! The widgets. (Strictly only those that need to be accessed
  ! by the handlers need to go here).

  type(c_ptr) :: ihwin,ihscrollcontain,ihlist, base, &
       &  qbut, dbut, lbl

  subroutine my_destroy(widget, gdata) bind(c)
    type(c_ptr), value :: widget, gdata
    print *, "Exit called"
    call gtk_widget_destroy(ihwin)
    call gtk_main_quit ()
  end subroutine my_destroy

  subroutine del_row(but, gdata) bind(c)
    type(c_ptr), value :: but, gdata

    integer(kind=c_int), dimension(:,:), allocatable :: selections
    integer(kind=c_int), dimension(:), allocatable :: dep
    integer(kind=c_int) :: nsel

    nsel = hl_gtk_tree_get_selections(ihlist, selections, &
         & depths=dep)

    if (nsel /= 1) then
       print *, "Not a single selection"
    end if

    call hl_gtk_tree_rem(ihlist, selections(:dep(1),1))

    call gtk_widget_set_sensitive(but, FALSE)

  end subroutine del_row

  subroutine list_select(list, gdata) bind(c)
    type(c_ptr), value :: list, gdata

    integer(kind=c_int) :: nsel
    integer(kind=c_int), dimension(:,:), allocatable :: selections
    integer(kind=c_int), dimension(:), allocatable :: dep
    integer(kind=c_int) :: n, n3
    integer(kind=c_int64_t) :: n4
    real(kind=c_float) :: nlog
    character(len=30) :: name
    character(len=10) :: nodd
    integer :: i
    nsel = hl_gtk_tree_get_selections(C_NULL_PTR, selections, selection=list, &
         & depths=dep)
    if (nsel == 0) then
       print *, "No selection"
    end if

    ! Find and print the selected row(s)
    print *, nsel,"Rows selected"
    print *, "Depths", dep
    print *, "Rows"
    do i = 1, nsel
       print *, selections(:dep(i),i)
    end do

    if (nsel == 1) then
       call hl_gtk_tree_get_cell(ihlist, selections(:dep(1),1), 0, svalue=name)
       call hl_gtk_tree_get_cell(ihlist, selections(:dep(1),1), 1, ivalue=n)
       call hl_gtk_tree_get_cell(ihlist, selections(:dep(1),1), 2, ivalue=n3)
       call hl_gtk_tree_get_cell(ihlist, selections(:dep(1),1), 4, l64value=n4)
       call hl_gtk_tree_get_cell(ihlist, selections(:dep(1),1), 3, fvalue=nlog)
       call hl_gtk_tree_get_cell(ihlist, selections(:dep(1),1), 5, svalue=nodd)
       print "('Name: ',a,' N:',I3,' 3N:',I4,' N**4:',I7,&
            &' log(n):',F7.5,' Odd?: ',a)", trim(name), &
            & n, n3, n4, nlog, nodd
       call gtk_widget_set_sensitive(dbut, TRUE)
       call gtk_widget_set_sensitive(dbut, FALSE)
    end if

  end subroutine list_select

  subroutine list_toggled(renderer, path, text, gdata) bind(c)
    type(c_ptr), value :: renderer, path, text, gdata
    ! RENDERER: c_ptr: required: The renderer which sent the signal
    ! PATH: c_ptr: required: The path at which to insert
    ! TEXT: c_ptr: required: The text to insert
    ! GDATA: c_ptr: required: User data, not used.
    ! The column number is passed via the "column-number" gobject data value.
    ! The treeview containing the cell is passed via the "view" gobject
    ! data value.
    ! The row number is passed as a string in the PATH argument.

    character(len=200) :: fpath
    integer(kind=c_int), allocatable, dimension(:) :: irow
    integer(kind=c_int), pointer :: icol
    integer :: ios, i, n, bool
    type(c_ptr) :: tree, pcol

    call convert_c_string(path, 200, fpath)
    pcol = g_object_get_data(renderer, "column-number"//c_null_char)
    call c_f_pointer(pcol, icol)

    n = 0
    do i = 1, len_trim(fpath)
       if (fpath(i:i) == ":") then
          n = n+1
          fpath(i:i) = ' '   ! : is not a separator for a Fortran read
       end if
    end do
    read(fpath, *) irow
    tree = g_object_get_data(renderer, "view"//c_null_char)

    call hl_gtk_tree_get_cell(tree, irow, icol, ivalue=bool)
    if (bool.eq.TRUE) then
    call hl_gtk_tree_set_cell(tree, irow, icol, ivalue=bool)      


    print*,"element toggled"
  end subroutine list_toggled
end module tr_handlers

program tree_toggle
  ! TREE
  ! Demo of a tree with cellrenderer toggle

  use tr_handlers

  implicit none

  character(len=35) :: line
  integer :: i, ltr, j
  integer(kind=type_kind), dimension(6) :: ctypes
  character(len=20), dimension(6) :: titles, renderers
  integer(kind=c_int), dimension(6) :: sortable, editable
  integer(kind=c_int), allocatable, dimension(:) :: colnos
  ! Initialize GTK+
  call gtk_init()

  ! Create a window that will hold the widget system
  ihwin=hl_gtk_window_new('Tree view demo'//c_null_char, destroy=c_funloc(my_destroy))

  ! Now make a column box & put it into the window
  base = hl_gtk_box_new()
  call gtk_container_add(ihwin, base)

  ! Now make a multi column list with multiple selections enabled
  sortable = (/ FALSE, TRUE, FALSE, FALSE, FALSE, TRUE /)
  editable = (/ TRUE, FALSE, TRUE, FALSE, FALSE, TRUE /)

  titles(1) = "Name"
  titles(2) = "N"
  titles(3) = "3N"
  titles(4) = "Log(n)"
  titles(5) = "N**4"
  titles(6) = "Odd?"
  renderers(1) = "text"
  renderers(2) = "text"
  renderers(3) = "text"
  renderers(4) = "text"
  renderers(5) = "text"
  renderers(6) = "toggle"

  ihlist = hl_gtk_tree_new_modified(ihscrollcontain, types=ctypes, &
       & changed=c_funloc(list_select),&
       &  multiple=TRUE, height=250, swidth=400, titles=titles, &
       & sortable=sortable, editable=editable, colnos=colnos,&
       & renderers=renderers, toggled=c_funloc(list_toggled))

  ! Now put 10 top level rows into it
  do i=1,10
     call hl_gtk_tree_ins(ihlist, row = (/ -1 /))
     write(line,"('List entry number ',I0)") i
     call hl_gtk_tree_set_cell(ihlist, absrow=i-1, col=0, svalue=line)
     call hl_gtk_tree_set_cell(ihlist, absrow=i-1, col=1, ivalue=i)
     call hl_gtk_tree_set_cell(ihlist, absrow=i-1, col=2, ivalue=3*i)
     call hl_gtk_tree_set_cell(ihlist, absrow=i-1, col=3, fvalue=log10(real(i)))
     call hl_gtk_tree_set_cell(ihlist, absrow=i-1, col=4, l64value=int(i,c_int64_t)**4)
     call hl_gtk_tree_set_cell(ihlist, absrow=i-1, col=5, ivalue=mod(i,2))
  end do

  ! Add some child rows
  do j = 2, 6, 2
     do i = 1, 5
        call hl_gtk_tree_ins(ihlist, row = (/ j, -1 /))
        write(line,"('List entry number',I0,':',I0)") j,i
        call hl_gtk_tree_set_cell(ihlist, row=(/ j, i-1/), col=0, svalue=line)
        call hl_gtk_tree_set_cell(ihlist, row=(/ j, i-1/), col=1, ivalue=i)
        call hl_gtk_tree_set_cell(ihlist, row=(/ j, i-1/), col=2, ivalue=3*i)
        call hl_gtk_tree_set_cell(ihlist, row=(/ j, i-1/), col=3, fvalue=log10(real(i)))
        call hl_gtk_tree_set_cell(ihlist, row=(/ j, i-1/), col=4, l64value=int(i,c_int64_t)**4)
        call hl_gtk_tree_set_cell(ihlist, row=(/ j, i-1/), col=5, ivalue=mod(i,2))
     end do
  end do

  ! It is the scrollcontainer that is placed into the box.
  call hl_gtk_box_pack(base, ihscrollcontain)

  ! Add a note about editable columns
  lbl = gtk_label_new("The ""Name"", ""3N"" and ""Odd?"" columns are editable"//c_null_char)
  call hl_gtk_box_pack(base, lbl)

  ! Delete selected row
  dbut = hl_gtk_button_new("Delete selected row"//c_null_char, clicked=c_funloc(del_row), &
       & tooltip="Delete the selected row"//c_null_char, sensitive=FALSE)

  call hl_gtk_box_pack(base, dbut)

! Also a quit button
  qbut = hl_gtk_button_new("Quit"//c_null_char, clicked=c_funloc(my_destroy))
  call hl_gtk_box_pack(base,qbut)

  ! realize the window

  call gtk_widget_show_all(ihwin)

  ! Event loop

  call gtk_main()

end program tree_toggle

jtappin commented Jul 25, 2012

I think the first thing to do is to clean up the existing code. There's a HUGE amount of duplication between the list and tree routines that really should be consolidated as there's way too many opportunities to get them out of sync. I think that it should be possible to reduce most of the hl_gtk_listn_* routines to stubs that call the corresponding hl_gtk_tree_* routines mapping the ROW argument to ABSROW. It will also be necessary to add an optional IS_FLAT_LIST argument to hl_gtk_tree_new that defaults to FALSE and is set to TRUE by the hl_gtk_listn_new stub. Does that sound sensible?

The other question to settle is whether to use strings or integers (with an enumeration or a set of parameters defined to give mnemonic names) to define the renderer classes. I think that in general gtk tends to use strings.


jtappin commented Jul 28, 2012

I now have a working draft that supports:

  • text (as before).
  • spin box.
  • toggle button (though I've not been able to get radio buttons to be exclusive).
  • progress bar.

pixbuf looks do-able, but right now I haven't been able to find enough documentation on comboboxes to figure it out as they require a full tree model combo definition (no convenient text-combo simplification).


bonanza commented Jul 29, 2012

I think, your plan concerning consolidation of hl_gtk listn and tree routines sounds reasonable and in some sense natural.
For the renderer class definition maybe strings are more gtk-like, but initialization of varying length string arrays is a bit cumbrous with fortran. Therefore I would prefer integers, which can be done with parameters having mnemonic names.

Concerning the combo tree model the question for me is whether this could be handled with an additional simple list using hl_gtk_listn routines or as some kind of subtree of the main model tree.


jtappin commented Jul 29, 2012

Actually I think the best may be to have fixed-length mnemonic string parameters (hl_gtk_cell_text = "text" etc.) -- that will allow easier diagnostics of unimplemented or non-applicable features (e.g. giving a meaningful message if a progress bar is set to editable, it's more obvious where the issue is if the message says 'progress' rather than (say) '7').


bonanza commented Jul 30, 2012

You are right, fixed-length mnemonic string parameters are maybe the best solution.


jtappin commented Jul 30, 2012

I've committed changes to add support for toggles, spin buttons, progress bars and pixbufs to all branches.

Definitions for spinners, combo boxes and radio toggles are included but these just give a warning that they are not yet implemented.

There is a quick & dirty example in examples/hl_list_renderers.f90.

vmagnin closed this Feb 17, 2013

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment