# TreeView/ListView

###

ListView/TreeView Overview

Gtk TreeViews and Listviews are used to display one or more columns of data in a scrollable, sortable, reorderable table format.

GTK3 uses the Model/View/Controller scheme for organizing and displaying data.

- In theory, this is more versatile, allowing you to display the same data in various forms simultaneously.
- In reality, you rarely need to do this sort of thing, and when you do, you probably know an easier way.
- In practice, this MVC design makes even simple lists mind-bogglingly difficult to understand and use.

Don't let this deter you, however, because EuGTK handles all the twiddling with iterators and paths, etc. that make using the List/TreeViews so difficult. You can accomplish almost anything you need to do by cutting and pasting from example code, thereby getting some programming done instead of sitting there wondering "what were they thinking?"

###

Getting Started

The first step will be to create a model in which to store your data. The model can be thought of as a table with one or more rows of data, arranged in one or more columns, where each column contains one specific type of data.

A model is not unlike a Euphoria sequence. Take, for example, the following: ``

    sequence students { -- name, age, amt due
    	{"Sam Smith", 15, 19.95},
    	{"Sue Jones", 14, 12.99},
    	$ -- etc...
    	}

In the above, col 1 contains strings, col 2 contains integers, col 3 contains floats. Your model must mirror the number and types of data in the sequence to be used.

###

Models

There are two kinds of model which come pre-built with GTK, plus others you can supposedly devise, if you believe the GTK docs! - Let's stick with the two easy ones:

- GtkListStore
- GtkTreeStore

You'll have noted that, just to make things even more confusing, GTK decided to call the list model a ListStore, and the tree model a TreeStore. So we might as well give our Euphoria object variable the name store.

Creating your model a.k.a. store is easy, you just need to specify a data type for each of the columns to match the type of data to be stored there.

For the example Euphoria sequence shown above, (students) we do this: ``

      constant store = create(GtkListStore,{gSTR,gINT,gFLT}) -- name, age, amt due

Note: by specifying a data type for each column, you are not only indicating what type of data can be stored there, but also describing the way you want the data to look. IOW, you might define the "name" column as gINT, but that would result in the name column displaying the pointers to the students name strings, rather than the names in readable form. Likewise, gINT, gFLT, and gDBL result in different ways to display the same numeric contents. Note: EuGTK implements a 'convenience' here for numbers: see floats.

Next, you need to load the model with your data: This can be done in one swell foop by simply typing:

     set(store,"data",students)

###

Columns

TreeView columns are created as you might expect.
Note: there are no ListView columns, everything uses a TreeViewColumn.

     constant col1 = create(GtkTreeViewColumn) -- no params needed

Each column will display a "vertical slice" of your Eu sequence.

You do not need to define a column for each column of data in your original Eu sequence. Only create a column for each item you want to see in the listview. In addition, the columns do not have to be in the same order as the original Eu sequence. You can specify which column in the store is to provide the values to be displayed in each column. See connecting below.

###

Cell Renderers

In order to be able to display the data, each column must have a cell renderer associated with it. Every cell in a given column displays the same type of data: string, integer, or float, but using individual values which are gotten from the 'model' attached to the view.

Cell renderers are created by:

     constant rend1 = create(GtkCellRendererText) -- no params needed

Where the renderer can be one of several types.

- GtkCellRendererText
- GtkCellRendererPixbuf
- GtkCellRendererProgress
- GtkCellRendererSpin
- GtkCellRendererToggle
- GtkCellRendererCombo

As you can see, the names indicate different ways to display the data from the model. For example, the CellRendererText will display strings, integers, or floats in human-readable form, i.e. alpha-numeric characters, while the CellRendererProgress will display percentages as a variable-length bar. The CellRendererToggle displays boolean values as a check-box, CellRendererSpin displays a potentially changeable numeric value, and a CellRendererPixbuf displays an image when given the handle to a GdkPixbuf.

Some cell renderers, such as text, toggle, and spin, can be made 'editable', so that the user can edit strings or modify values. Be aware however: edits are NOT automatically saved to the underlying model. You have to write routines to do this when necessary. Refer to test33 and test35 for examples of how to make a renderer editable, and how to update the model to reflect the edited data.

You will always create a column and a renderer together, and pack the renderer into the column. It is possible to pack more than one renderer into a single column.

     constant col1 = create(GtkTreeViewColumn)
    	set(col1,"title","Name") -- you may set some properties of the column

     constant rend1 = create(GtkCellRendererText)
    	set(rend1,"font","Courier 8") -- you may set some properties of the renderer, these affect the entire column
    	set(col1,"pack start",rend1) -- and finally, add the renderer to the column!

###

Connecting data to column renderers

Next, you need to associate each column in the view with the column in the model where it will find its data. Of course, it can't be this easy. Instead, you have to tell the column to tell its cell renderer where to find the data:

     set(col1,"add attribute",rend1,"text",1)

Which means: col1 should get the text to be displayed by its cell renderer (rend1) from column #1 of the model. In the example, this would be the name of the student.

Often you may have more data in your Eu sequence - and hence, in your model - than you wish to display, and/or you may want to display the columns in a different order than they appear in the original sequence. This "add attribute" call allows you to control that.

'Text' is only one attribute that the GtkCellRendererText has to offer; some of the others are 'markup', 'background', 'foreground', 'language', 'font', etc. By now, you won't be surprised to find that these aren't called attributes by the GTK docs, instead you'll find them listed under Properties for the various GtkCellRenderers.

But wait, there's more!

Just to keep things from getting boring, there are two ways to set an attribute a.k.a. property for a cell renderer. The first, as shown below, is to tell the column to tell the renderer where to get its background for this particular cell. Let's assume col# 2 of the model contains color names, perhaps a different color for each row in the list:

        set(col1,"add attribute",rend1,"background",2)

On the other hand, suppose you tell the cell renderer directly to set a property. This will affect all cells in the column which contains that renderer. This is done as follows:

        set(rend1,"background","skyblue") -- entire column will have a sky blue background
        set(rend1,"size-points",36) -- and all text in that column will be 36 points

###

Finally

Now you only have to create a GtkTreeView to hold the columns.
(Here again, there is no GtkListView, everything uses a GtkTreeView)

        constant tv = create(GtkTreeView)
    	set(tv,"rules hint",TRUE) -- set some appearance options for the tree view:

Then tell it which model it should use to obtain its data; and append the column(s):

    	set(tv,"model",store)
    	set(tv,"append columns",{col1...})

And, more often than not, you'll add the view to a scrolled window and viewport, which keeps long lists from extending past the bottom of the screen.

###

Signals

The normal signal to connect to is "row-activated", which will select a row when it is double-clicked or a row is highlighted and the <enter> key is pressed.

        connect(tv,"row-activated",call_back(routine_id("Foo")))

If you are using GTK version 3.8 or above, you can choose to activate on a single click by setting the following:

        set(tv,"activate on single click",TRUE)

###

Easy Syntax! ~ new in EuGTK 4.11.2

Below is the code needed to produce a listview; it isn't such a big deal, as you can see. Refer to the simple test3.ex demo, and the overly-fancy test33.ex demo.

``

    constant  store = create(GtkListStore,{gSTR,gSTR,gSTR})  -- describe type of data to be stored in each column;

    set(store,"data",{  -- here's the data;
        {"Apple", "doz",5.00},
        {"Cherry","lb", 3.69}, -- note: 3rd column is numeric,
        {"Lime",  "ea",  .99}, -- but will automatically be converted
        {"Orange","ea", 0.79}, -- to a string by the store
        {"Banana","lb", 1.89}
        })

    constant scroller = create(GtkScrolledWindow)
      pack(panel,scroller,TRUE,TRUE)

    constant  tv = create(GtkTreeView,{
      {"model",store},
      {"connect","row-activated",_("ShowChoice")}}) -- see  function below
      add(scroller,tv)

    constant
     col1 = create(GtkColumn,"title=Name,type=text,text=1,sort_column_id=1"),
     col2 = create(GtkColumn,"title=Quantity,type=text,text=2"),
     col3 = create(GtkColumn,"title=Price,type=text,text=3,sort_column_id=3")

    set(tv,"append columns",{col1,col2,col3})

    constant selection = get(tv,"selection")

    ---------------------
    function ShowChoice() -- our function to handle selections
    ---------------------
    object choice = get(selection,"selected row data")
    Info(,,choice[1],format("Price: $[3] per [2]",choice))
    return 1
    end function

###

Functions ~ new in EuGTK 4.8.7

Please don't bother trying to figure out the GTK docs when it comes to using a list or tree view.
You'll find yourself ... lost in an endless maze of twisty passages...

The stock GtkListView/GtkTreeView scheme is rediculously complex, and nearly impossible to understand. With this version of EuGTK, I have implemented easier ways to deal with these things.

After you create a ListView model, you can load it with an ordinary Euphoria sequence* and manipulate it using familiar calls. Note that there is no direct connection between the Eu data sequence and the list view store, so changing the Eu sequence does not change the list view, or vice-versa. To keep the two in sync, you must update as appropriate. See the hint below.

``

       constant store = create(GtkListStore,{gSTR,gSTR,gFLT})

       sequence customers = {-- first, last, balance due
    	{"Sue","Jones",40.95},
    	{"Ralph","Wiggums",29.44}}

       sequence new_cust = {"Ferd","Merkle",12.50}

- Working with entire contents as Eu Sequence
  set(store,"data",customers) -- store Eu sequence to listview
  get(store,"data") -- retrieve listview data as Eu sequence
  set(store,"clear") -- clear listview, does not affect original Eu sequence
- Working with individual rows
  get(selection,"selected row") -- returns integer row number
  get(selection,"selected rows") -- returns sequence of row numbers: {2,6,23}
  set(store,"append row",new_cust) -- add to bottom of list, invalid if view is sorted
  set(store,"prepend row",new_cust -- add to top of list, invalid if view is sorted
  set(store,"insert row",new_cust,pos#) -- add at pos, invalid if view is sorted
  set(store,"remove row",row#)
  set(store,"replace row",row#,new_cust) -- replace row or list of selected rows with new data

The above set() calls actually return the modified data, which you can ignore, or use to sync the modified data to disk, if you wish, whenever data is changed.

- Working with a single row/column
  set(store,"col data",row#,col#,data)
  get(store,"col data",row#,col#)
- Other functions
  set(store,"swap",row_a,row_b) -- trade places. row_a and row_b are integer row numbers
  set(store,"move before",row_a,row_b) -- only valid if view is unsorted
  set(store,"move after",row_a,row_b) -- only valid if view is unsorted
  NOTE: the above return the modified sequence, could be used to save updates to disk, etc.
- Information functions
  get(store,"n rows") -- returns current number of rows in model
  get(store,"n cols") -- returns number of columns in model
  get(store,"is sorted")
  get(store,"sort column id") -- 1..n
  get(store,"sort order") -- 0 = ascending, 1 = descending
  NOTE: sort order is only valid if "is sorted" = TRUE

* Eu sequences must be conformant - IOW, each column must contain one consistent type of data: if the first column contains strings, then all entries in column one must be strings, if a column contains numbers, for example, age or balance due, then all entries in that column should be numbers.

Each view column can have an optional sort column id - this tells the view column which column in the model (store) contains the value to use in the sort. In practical terms, this means that you could tell column #1 (names) that it should be sorted based on the value in #3 (amt_due). You may also want to make the TreeView's headers clickable, so the user can sort each column when desired. You can also, thru code, tell the model to sort itself by column and direction:

     set(store,"sort column id",3,GTK_SORT_ASCENDING)
            -- instead of an integer column #, you can use:
            -- GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID (-2) = no sorting
            -- sort directions are, as you would expect: GTK_SORT_ASCENDING | GTK_SORT_DESCENDING

You can also write your own custom sorting function, and tell the TreeView to use that function. See the GTK docs on GtkTreeSortable for a prototype (GtkTreeIterCompareFunc) you can use when writing your Euphoria function.

###

Retrieving data

To get the full data set back as a Euphoria sequence, just use:

        get(store,"data")

To get the selected data back from a ListView/TreeView, you must first obtain a GtkSelection object from the view. Normally, this can be done at the time the view is created:

        constant selection = get(tv,"selection") -- this tracks the changing selection(s)
        set(selection,"mode",GTK_SELECTION_SINGLE)

Then, you can get the data contained in the selected row if selection mode is GTK_SELECTION_SINGLE or GTK_SELECTION_BROWSE; or rows, if selection mode is GTK_SELECTION_MULTIPLE.

     object rowlist = get(selection,"selected rows") -- this returns a Eu sequence of one or more integers, e.g. {3} or {1,3,5,6}
     object data
            for i = 1 to length(rowlist) do
                data = get(store,"row data",rowlist[i]) -- use each row # to get the row's data
                -- here, you do something with that data...

Sometimes, it's convenient get the contents of a single cell in a row by calling:

       data = get(store,"col data",row,2) -- where 2 is the column#

The above instructions apply primarily to CellRendererText renderers. Lists using other types of CellRenderers require different methods to retrieve the contents/results of a selection. Please refer to the alphabetical guide, the GTK docs on CellRenderer properties, and the respective demos.

###

Floats

The default display format for floating point numbers (gFLT) used in ListViews will probably not be what you want - there will be too many digits following the decimal point. In order to fix this, you can connect your own cell data function formatting routine. See test66 for an example of how to do this.

As a convenience, another way to handle common values with 2 decimal points is to declare the column as gSTR, but to pass atoms or integers. No cell data formatting function is needed for this special case.