librelist archives

« back to archive

TreeView Component

TreeView Component

From:
Nicholas White
Date:
2011-07-17 @ 10:09
Hi,

I'm emailing about the feature request I raised[1] about adding a
data-grid-viewer-like widget to the shoes API (presumably using the
GTK binding to a TreeView). I'd like to help (I've never used GTK
directly before, but I've been reading throught this[2]), so I've
sketched out the API for discussion. I'll use this table[3] as an
example to illustrate usage.


1) Model-View separation
GTK's TreeView has a clear difference between the data in the tree and
the view of that tree - and this should be preserved in the Shoes API.
To make the widget simple to use there should only be one type:
>> a = Gridview.new
This will contain the model and a default view; if you want to create
new views onto the same data you could do:
>> a_view = a.new_view
Any DML calls - see (2) - will affect the shared model (i.e. a *and*
a_view), but any style calls - (5) below - will only affect the
instance that it was called on (i.e. a *or* a_view).

2) DML
DML (I stole the term from SQL - basically insertion & deletion into
the model) should be simple. In GTK's TreeView, rows can are indexed
in a tree (a 2D grid is a tree with only a single level of row index)
using (I think) a series of colon-separated numbers. In the
example[3], the third row from the top has index 0:1 (base-zero
indices). I think the shoes API should have the same hierarchy, but
use symbols to make up the index (something like symlinks to rows;
GTK's TreeRowReferences) as working with absolute indices (that change
as the rows above it are delete or new ones are inserted) has caused
me a lot of trouble in the past (although getting the absolute index
of a row as a list of integers and vice-versa should be possible).
Each row should be a Hash, and the column names should be symbols
(that don't necessarily correspond to the heading name in the UI). to
create the example:

>> a.upsert :1, :11, id => 11, name => "Billy Bob Junior"
>> a.upsert :2, id => 2, name => "Joey Jojo"
>> a.index_of :1, :11  # not relly a good example as the symbols are numbers...
[0,0]
>> a.indexed_by 0, 0
[:1,:11]
>> a.delete :1, :11 # deletes the first row we inserted
>> a.clear # empties the table (like the GTK method)

I think upsert (insert or update) is better than separate operations
as it reduces the "surface area" of the API, and means you don't have
to write boilerplate that checks for an existing row before updating.
You can always use the returned object for this. Columns should be
determined dynamically (in the Shoes API; you can't do this with the
underlying GTK TreeView), and (5) specifies default values to use for
columns if an insertion doesn't provide all of them (just like passing
a block to a new Hash is used for default values).

3) Access
The [] operator should give you row access (i.e. return a Hash), and
any mutation methods (e.g. update, delete_if, store, &c.) on the
returned Hash should result in upsert calls on the TreeView object to
keep the table in sync (deleting from the Hash should replace the
value in the table with the relevant column's default value.

>> a[:1, :11][name] = "Billy Bob Senior"

Iteration is fairly important, and you should be able to iterate over
rows and columns. When iterating over rows, you should be able to
specify part of a rowkey to iterate over sub-branches of the tree:

>> a.each_row{|*rowkey,row| ... }
>> a.each_row[:3]{|*rowkey,row| ... } # only iterate over rows including 
and below the :3 branch; [:3] and [:3,:31] in the example
>> a.each_col{|col,rows| ... } # here rows is a Hash of rowkeys (e.g. 
[:3,:31]) to values (where the value is for the relevant column for that 
row

4) Callbacks
Callbacks (GTK's Signals) are one of the most important features of
the table (imo). They should in general behave like the change()
callbacks in the rest of the Shoes API (and so should always be given
a reference to the top-level TreeView). Unlike SQL triggers, they
can't modify the change that's happening (i.e. change the values being
inserted or updated), and so the Hashes in the examples below should
be immutable (frozen?).

>> a.on_upsert {|self,*rowkey,changes,previous| ... }

Here /previous/ is nil for an insert and an immutable Hash of the
previous rows' values for an update. The /changes/ Hash can be empty,
and contains only the values that have changed (i.e. it's not a
snapshot of the row in its new state). Similarly, there should be a
callback for deletions:

>> a.on_delete{|self,*rowkey| ... }

5) Styles
This is somewhat more difficult, as there are some attributes at a
cell level (e.g. background colours in alternate rows) and others at a
column level (e.g. the function to sort the rows by). There are also
some callbacks that should be per-view, not per model - see (1) - like
what has been selected:

>> a.on_select{|self,*rowkey,col| ... }

Maybe the column-level attributes should be set on the TreeView object:

>> a.column_is :name, :editable => true, :hidden => false, :default => 
{|*rowkey| "" }, :sortable => true

And there should be a way of setting column-level attributes for all
columns (to prevent iteration or boilerplate - and because column
names can be added dynamically):

>> a.all_columns_are :width => :autofit # can GTK automatically size 
columns to fit the largest element?

The :sortable attribute should sort by the default order (i.e. <=> in
ruby) if a boolean is given, or by the given block to allow custom
sort orders. Note that sorting is per-view, not per-model. The
:editable attribute has to be set at the column level as (afaik) GTK
doesn't let you set per-cell editability. This is unfortunate, as it
would be nice to let a cell contain one of Shoes' EditLine elements to
indicate a cell was editable. As for per-cell attributes, maybe it
should be possible to model each cell as a Shoes slot, so you can add
any of the existing Shoes elements to it:

>> a.column_is :name, :cell => {|self,*rowkey| background black }

There are examples of GTK TreeViews with checkboxes in cells[4], so
the below should be possible:

>> a.column_is :has_name, :cell => { check }


What do you think? Altough I'm not able to write the implementation, I
could write unit tests if that would help. I think the
data-grid-viewer is second most important widget after the button (at
least in the applications I usually write :), so I hope this
eventually ends up in Green Shoes.

Thanks,

Nick

[1] https://github.com/ashbb/green_shoes/issues/33
[2] http://scentric.net/tutorial/treeview-tutorial.html
[3] http://developer.gnome.org/gtkmm-tutorial/2.22/figures/treeview_tree.png.en
[4] http://mail.gnome.org/archives/gtk-list/2003-September/msg00202.html

Re: [shoes] TreeView Component

From:
ashbb
Date:
2011-07-20 @ 11:45
Hi Nicholas,

Wow, awesome! Thank you for the great suggestion.
But sorry, I have no time this week to read in detail. :(
So, please give me more time. I'll reply next week.

Thanks,
ashbb

Re: [shoes] TreeView Component

From:
ashbb
Date:
2011-07-24 @ 02:06
Hi Nicholas,

> I've sketched out the API for discussion.
Great!
I didn't know GTK's TreeView well. So, the API you sketched and the
references you found were very helpful for me to study. Thanks!

You've sketched many ideas, e.g. "a series of colon-separated numbers",
"using [] operator to access", etc. They are really nice. But looks a little
bit too close to GTK APIs, IMHO.

Shoes has a method list_box():
http://ashbb.github.com/green_shoes/ListBox.html

So, how about following the list_box style?

For example:

require 'green_shoes'

Row = Struct.new :id, :name
tables = [[[1, 'Billy Bob'], [11, 'Billy Bob Junior'], [12, 'Sue Bob']],
          [[2, 'Joey Jojo']],
          [[3, 'Rob McRoberts'], [31, 'Xavier McRoberts']]]

tree = tables.map do |table|
  table.map do |id, name|
    Row.new id, name
  end
end

Shoes.app treeview: true do
  tree_view tree do |tv|
    para tv.id, tv.name # tv is a selected row
  end
end

I'm not sure it's possible to implement, though. :-P

ashbb

Re: [shoes] TreeView Component

From:
Nicholas White
Date:
2011-07-30 @ 22:03
I like the list_box API - it's nice and simple. Is the "item" list
mutable (i.e. if you do it.items << "s" will it add "s" to the list of
options? I'm on a mac at the minute so can't really test it. I can
definitely see a TreeView as a generalisation of a (mutable) list_box
- but with the "items" as an array of Hashes (column names to values).
The many-views-of-one-model (para (1) of my email) maps fairly well -
if you have two list_boxes that share the same items array, then they
(should?) share the same model:

>> a = list_box items => [ "a", "b", "c" ]
>> b = list_box items => a.items
>> a.items << "d"
>> puts b.items
[ "a", "b", "c", "d" ]

Again, I don't know if green_shoes' list_box actually does this, but
model-view separation is a fairly powerful tool.

As for square-bracket indexing, you can reference list_box elements
with absolute indices (e.g. a.items[2]) so that would work for free
with a TreeView list box. I agree, it's harder to see how
multi-dimensional row indices (and indeed named rows all together) fit
into this - I need to think a bit more about that.

As for callbacks, the list_box's "change" one is a callback on the
view - so I think the "upsert" callback is still needed for hooking
events into changes of the model. To illustrate - the list_box is
missing a callback that gets invoked when someone calls the "items="
method with a new array. You're right that my solution is
over-engineered; if the upsert callback got given two Hashes (the new
row and the old row) we wouldn't need a separate "delete" callback:

>> v = tree_view upsert => {|new,old| puts new.inspect, " ", old.inspect}
>> v.items << :id => 7, :name => "Me"
[name=Me,id=7] nil
>> v.items[0][:name] = "You"
[name=You,id=7] [name=Me,id=7]
>> v.items.clear
nil [name=You,id=7]

I see the "items" property of the list_box / tree_view as a way of
accessing the model, and all the other methods on the list_box
(including the ones common to all green_shoes components[1]) as ways
of accessing the view. We'd get a lot of customisation for free (i.e.
no new methods added to the API), but would still need new methods for
sorting. This may be useful for existing list_boxes though; currently
if you want to add a new element you'd have to sort it each time,
which is verbose. Compare:

>> y = list_box :items => [ "c" ]
>> y.items = (y.items << "b").sort
>> y.items = (y.items << "a").sort

With:

>> y = list_box :items => [ "c" ], :sorted
>> y.items = "b"
>> y.items = "a"

You're more familiar with gtk bindings than I am, so I hope your last
line is not true. Version 1.0 could just map to the list_box API (but
with callbacks on the model too :) - everything else is just a
nice-to-have.

Thanks,

Nick

[1] http://ashbb.github.com/green_shoes/Common.html

On 24 July 2011 03:06, ashbb <ashbbb@gmail.com> wrote:
> Hi Nicholas,
>
>> I've sketched out the API for discussion.
> Great!
> I didn't know GTK's TreeView well. So, the API you sketched and the
> references you found were very helpful for me to study. Thanks!
>
> You've sketched many ideas, e.g. "a series of colon-separated numbers",
> "using [] operator to access", etc. They are really nice. But looks a little
> bit too close to GTK APIs, IMHO.
>
> Shoes has a method list_box():
> http://ashbb.github.com/green_shoes/ListBox.html
>
> So, how about following the list_box style?
>
> For example:
>
> require 'green_shoes'
>
> Row = Struct.new :id, :name
> tables = [[[1, 'Billy Bob'], [11, 'Billy Bob Junior'], [12, 'Sue Bob']],
>           [[2, 'Joey Jojo']],
>           [[3, 'Rob McRoberts'], [31, 'Xavier McRoberts']]]
>
> tree = tables.map do |table|
>   table.map do |id, name|
>     Row.new id, name
>   end
> end
>
> Shoes.app treeview: true do
>   tree_view tree do |tv|
>     para tv.id, tv.name # tv is a selected row
>   end
> end
>
> I'm not sure it's possible to implement, though. :-P
>
> ashbb
>
>

Re: [shoes] TreeView Component

From:
ashbb
Date:
2011-07-31 @ 01:09
Hi Nicholas,

> Is the "item" list mutable?
Yes.
Try out the following. But you have to use the latest Green Shoes.
Because I've fixed a bug you found. ;-)
# Thanks to you, I noticed this bug.


https://github.com/ashbb/green_shoes/commit/b979fb41610faef2f0f3c8f67766004fce1f3df0

require 'green_shoes'
Shoes.app do
  lb = list_box items: [1,2,3,4,5]
  list_box items: lb.items
  button('add 6'){lb.items += [6]}
end

> As for callbacks, the list_box's "change" one is a callback on
> the view - so I think the "upsert" callback is still needed for
> hooking events into changes of the model.
Umm,...
What is "change"?
In the case of EditBox and EditLine, the "change" means the time
user edits the text. But in ListBox, it means "select", not change
the items.
Try out the following.

Shoes.app do
  lb = list_box items: [1,2,3,4,5]
  lb.change{para lb.text}
end

I think your upsert and sort functions are useful.
But they are the functions that manipulate items.
And the items is an Array object, not ListBox object.
So, it's better not to add your upsert method into ListBox class
itself, but to add Array class.
IMHO, though. :)

> I hope your last line is not true.
hahaha. Yeah, I hope, too.
Let's continue to discuss about a simple TreeView API and try to
implement. ;-)

ashbb

Re: [shoes] TreeView Component

From:
ashbb
Date:
2011-08-02 @ 12:55
Hi Nicholas and folks,

> try to implement. ;-)
Done!


https://github.com/ashbb/green_shoes/commit/8878a7cfc9bc696c3c5c8c5413f3134c69ede458

Try out the latest Green Shoes. :-D

http://rubygems.org/gems/green_shoes

ashbb

Re: [shoes] TreeView Component

From:
Nicholas White
Date:
2011-08-03 @ 19:48
Excellent work! I've been playing about in a different direction -
possibly related to the other thread on observers. I keep going on
about the importance of separating the model and the view; consider
this example -

 require 'green_shoes'
 Shoes.app do
   para "Choose a number:"
   @num = 1
   @box = list_box items: [@num.to_s]
   button "Add Next" do @box.items << (@num += 1).to_s end
   button "Remove Odd" do @box.items.select!{|num|num.to_i%2==0} end
   button "Sysout Items" do puts @box.items.inspect end
 end

Here the model is an array of strings, initially ["1"]. I've added
some buttons that play about with the model (i.e. add and remove
elements from it, and to print the contents of the model out to the
console. If you try this app you'll see that the model as dumped to
the console is updated correctly, but the view (i.e. the list_box) is
never updated (it only has the single choice "1"). I've had to do
quite a bit of hackery to get this to work as expected (and had to use
the "diff" gem as I don't have time to re-invent the wheel :)...

require 'observer'
require 'diff'

class Shoes::ListBox < Shoes::Native
  def initialize args
    super args
    @observer = lambda{|diff|
      offset = 0
      diff.diffs.map{|x|x[0]}.each{|sign,index,value|
        if sign == '+'
          real.insert_text index + offset, value; offset += 1
        else
          real.remove_text index + offset; offset -= 1
        end
      }
    } # fired when the model changes
    @remove_observer = lambda{} # to unwire the current model from the view
    self.items = @items || [] # wire up the initial model
  end

  def items= items
    # current implementation
    @items.length.times{real.remove_text 0}
    @items = items
    items.each{|item| real.append_text item.to_s}

    # wire view to model (note we do all this mangling of the
    # items object in case anyone has a reference to it.
    actual_array = items.dup
    (items.methods -  Object.class.instance_methods +
[:to_s,:inspect]).each{|m|items.instance_eval "undef :#{m}"}
    items.extend Observable
    items.instance_variable_set :@actual_array, actual_array
    def items.method_missing m, *args, &blk
      copy = @actual_array.dup
      ret = @actual_array.send m, *args, &blk
      puts @actual_array.inspect
      if copy != @actual_array; changed; notify_observers
copy.diff(@actual_array) end
      ret
    end

    # good old observer pattern
    @remove_observer.call # remove old model
    @remove_observer = lambda{ # remove this model (for next time)
      items.delete_observer @observer
    }
    items.add_observer @observer, :call
  end
end

If you paste this between the require 'green_shoes' and the app then
it works as expected (i.e. the view - list_box - correctly shows the
model after the buttons are pressed). The code's a bit unreadable, but
the general idea is to hook all (interesting) method calls to the
model and fire an observer if the model has changed. It gives the
observer a list of diffs that have been applied to the model, and the
observer makes calls on the GTK widget binding to update it to match
the model. What are your thoughts on this?

Also, I've noticed you duplicate the data in the model - there's one
copy in the GTK widget and another copy in the "items" instance
variable of the list_box. This is not a big issue in this example, but
may cause memory-related problems in a treeview with loads of data. I
haven't finished looking through your treeview commit, so I don't know
if your implementation suffers from this, though.

Thanks -

Nick

On 2 August 2011 13:55, ashbb <ashbbb@gmail.com> wrote:
> Hi Nicholas and folks,
>
>> try to implement. ;-)
> Done!
>
> 
https://github.com/ashbb/green_shoes/commit/8878a7cfc9bc696c3c5c8c5413f3134c69ede458
>
> Try out the latest Green Shoes. :-D
>
> http://rubygems.org/gems/green_shoes
>
> ashbb
>

Re: [shoes] TreeView Component

From:
ashbb
Date:
2011-08-04 @ 12:26
Hi Nick,

> I've had to do quite a bit of hackery to get this to work as expected
Wow, awesome code using observer and diff. I've not used the gem. :-P

But wait a moment. We have the `ListBox#items=` method.
So, let me revise your first code a little bit. ;-)

require 'green_shoes'
Shoes.app do
  para "Choose a number:"
  @num = 1
  @box = list_box items: [@num]
  button "Add Next" do
    @box.items += [@num += 1]
  end
  button "Remove Odd" do
    @box.items = @box.items.select{|num|num%2==0}
  end
  button "Sysout Items" do puts @box.items.inspect end
end

It seems to work as expected. ;-)

> Also, I've noticed you duplicate the data in the model - there's
> one copy in the GTK widget and another copy in the "items"
> instance variable of the list_box.
Yes, you are right.
Hence, you can write without `to_s` like the above code. ;-)

Cheers,
ashbb

Re: [shoes] TreeView Component

From:
Nicholas White
Date:
2011-08-05 @ 22:32
I take your point that it's a little complicated :) However, while
using item= fixes the app I wrote it means you have to abide by
non-obvious rules or the view will get out of sync with the model.
Also, this doesn't address the fact that the are two copies of the
mode (one in the Ruby code & one in the GTK widget) - and using item=
means you have to clear and repopulate the entire GTK model every time
you want to do an update, no matter how trivial. This is not an issue
with the list_box as there's so few Strings in the model that the
performance & memory overhead is minimal, but in a TreeView with many
columns and tens of rows this would be noticeable. As an aside, the
GTK ComboBox behind the list_box stores its model in a TreeModel - the
same object behind the GTK TreeView[1]. This is why I think paying the
price of a complicated list_box implementation is necessary - the code
will generalise to an efficient TreeView implementation.

I've simplified my solution - what we really want for our model is
something that responds_to? exactly like an Array, but in fact is a
thing layer over (i.e. delegates the actual storage to) the GTK model.
This is a fairly useful idea - I've spent a while googling for
something like it previously when I was looking at object-relational
mapping (think Hiberate in Java) - I wanted something that looked like
an Array, but every time you accessed an element it would read it from
a database, and every time you set an element in the array it would
insert or update a row in the database. I've generalised this idea to
the below (implementation is incomplete - bear with me...):

# so you basically need at(i), set(i,val), size & delete_at(i)
# in your actual class to be an array
module ArrayLike
	include Enumerable
	
	def clear
		size.times{|i|delete_at 0}; self
	end
	
	# returns copy, so ok to be inefficient
	[:+, :-, :&, :*, :|].each{|m| instance_eval "def #{m} other;
to_ary.send :#{m}, other end"}
	
	def to_ary; ret = []; each{|x|ret<<x}; ret end
	
	def concat other; other.each{|x|push x}; self end
	
	def empty?; size == 0 end
	
	def push *args; args.each{|arg|set size, arg} end
	
	def join sep=$,
		each_with_object(""){|i,s|s.empty? ? (s<<i) : (s<<sep<<i) }
	end	
	
	def inspect; "[#{join(", ")}]" end
	
	def select! &blk
		off=0
		size.times{|i|
			if not blk.call at(i+off); delete_at (i+off); off-=1 end
		}
		self
	end
	
	def each; size.times{|i|yield at(i)} end
	
	def slice! *args; ret=self[*args]; self[*args]=nil; ret end
	
	def values_at *args
	  args.map{|a|a.class.included_modules.include?(Enumerable) ? a.to_a
: [a]}.flatten.map{|i|at(i)}
	end
	
	alias_method :<<, :push
	alias_method :index, :find_index
end

# todo
# [ "<=>", "assoc",  "choice", "collect!", "combination", "compact",
# "compact!", "delete",  "delete_if", "each_index",  "fetch", "fill",
# "flatten", "flatten!", "indexes", "indices", "insert",  "last", "length"
# "map!", "nitems", "pack", "permutation", "pop", "product",  "rassoc",
# "reject!", "replace", "reverse", "reverse!", "rindex", "shift",
# "shuffle", "shuffle!", "transpose", "slice",  "sort!", "uniq", "uniq!",
# "unshift" ]

I've left off a quite a few methods, but you get the idea; you
implement set, at, delete_at & size methods in your class that
delegate to your backing store, include this module and you act like
an array. All the implementations of the array methods are done so
that they only references these four delegating methods. You don't
even have to write unit tests for this - you can use the ones in
Rubyspec[2] to ensure this model is exactly interchangable with a
native Ruby Array. If you do implement the rest of this you should
consider releasing it as a separate gem - I unfortunately don't have
the time or I'd do this myself (above code BSD-licensed if you want it
:).

To use this in my example shoes app you still need some (although
less) monkey-patching:

class Shoes::ListBox < Shoes::Native
  def initialize args
    super args
    @remove_observer = lambda{} # to unwire the current model from the view
    self.items = @items || [] # wire up the initial model
  end

  def items= items
    # current implementation
    @items.length.times{real.remove_text 0}
    @items = items
    items.each{|item| real.append_text item.to_s}

    # wire view to model (note we do all this mangling of the
    # items object in case anyone has a reference to it).
    items.extend ArrayLike
    items.clear # might as well save some memory
    items.instance_variable_set :@real, real

    # and now the implementation of the ArrayLike interface
    def items.size; i=0; @real.model.each{i+=1}; i end
    def items.at i
      count=0
      @real.model.each{|model, path, iter|
        return model.get_value iter, 0 if i == count;
        count+=1
      }
    end
    def items.delete_at i; @real.remove_text (i<0 ? (i+size) : i) end
    def items.set i, val
      i+=size if i<0
      if i == size
        @real.append_text val.to_s
      else
        @real.remove_text i; real.insert_text i, val
      end
    end

    @remove_observer.call # remove old model
    @remove_observer = lambda{ # remove this model (for next time)
      new_items = items.to_a

      # re-implement the ArrayLike interface, pointing to the new_items Array
      def items.size; new_items.size end
      def items.at i; new_items.at(i) end
      def items.delete_at i; new_items.delete_at(i) end
      def set i, val; new_items[i] = val end
    }
  end
end

 Shoes.app do
   para "Choose a number:"
   @num = 1
   @box = list_box items: [@num.to_s]
   button "Add Next" do @box.items << (@num += 1).to_s end
   button "Remove Odd" do @box.items.select!{|num|num.to_i%2==0} end
   button "Sysout Items" do puts @box.items.inspect end
 end

The code that interacts with the GTK ComboBox is grossly inefficient
(lots of looping) but I couldn't find a "size" method in the API. I'm
also not sure that this would work as-is with multiple list_boxes (or
tree_views) sharing the same model - you'd have to have another layer
between the items Array (now ArrayLike) and the ComboBoxes / TreeViews
that implemented the Observer pattern (and so all the views could be
observers of a single model).

What do you think?

Nick

[1] http://ruby-gnome2.sourceforge.jp/hiki.cgi?Gtk%3A%3AComboBox#model
[2] https://github.com/rubyspec/rubyspec/tree/master/core/array

On 4 August 2011 13:26, ashbb <ashbbb@gmail.com> wrote:
> Hi Nick,
>
>> I've had to do quite a bit of hackery to get this to work as expected
> Wow, awesome code using observer and diff. I've not used the gem. :-P
>
> But wait a moment. We have the `ListBox#items=` method.
> So, let me revise your first code a little bit. ;-)
>
> require 'green_shoes'
> Shoes.app do
>   para "Choose a number:"
>   @num = 1
>   @box = list_box items: [@num]
>   button "Add Next" do
>     @box.items += [@num += 1]
>   end
>   button "Remove Odd" do
>     @box.items = @box.items.select{|num|num%2==0}
>   end
>   button "Sysout Items" do puts @box.items.inspect end
> end
>
> It seems to work as expected. ;-)
>
>> Also, I've noticed you duplicate the data in the model - there's
>> one copy in the GTK widget and another copy in the "items"
>> instance variable of the list_box.
> Yes, you are right.
> Hence, you can write without `to_s` like the above code. ;-)
>
> Cheers,
> ashbb
>
>

Re: [shoes] TreeView Component

From:
ashbb
Date:
2011-08-07 @ 07:54
Hi Nicholas,

> using item= means you have to clear and repopulate the entire GTK model
> every time you want to do an update
Yes, you are right.

> but in a TreeView with many columns and tens of rows this would be
> noticeable.
In general, that's true.
But there are very few Shoes apps which need so much high performance, IMHO.
:-P

As you know, the `:item` attribute is just an array.
So, if you accept using `item=` method when you update each time,
you can use whole methods of Array.

ashbb

Re: [shoes] TreeView Component

From:
Nicholas White
Date:
2011-08-08 @ 05:20
> But there are very few Shoes apps which need so much high performance, IMHO.

I do! But seriously, that's why I started this thread - I'm writing
GUIs that display and aggregate large amounts of data to make it
"useful" (think H2's database HTML viewer[1] or excel's pivot
tables[2] that let you drill into aggregate rows). No ruby GUI
toolkits really support this yet (I could use the GTK2 bindings
directly, but I want concise GUI code, and WxRuby's wxGrid only
supports 2D data) - so I thought I'd help shoes out as it's by far the
easiest toolkit to work with. I'm not trying to be difficult by
thinking of theoretical edge cases - the functionality I've been
sketching out is pretty much a requirement for my use case.
However, I appreciate that this isn't shoes' usual use-case, and so
will understand if you don't want to over-complicate the shoes
implementation by supporting it...

[1] http://img.skitch.com/20101127-fbacj94arqxfbkqnph7g733r5u.png
[2] http://www.mssqltips.com/tipimages2/1716_pivot_table.jpg

On 7 August 2011 08:54, ashbb <ashbbb@gmail.com> wrote:
> Hi Nicholas,
>
>> using item= means you have to clear and repopulate the entire GTK model
>> every time you want to do an update
> Yes, you are right.
>
>> but in a TreeView with many columns and tens of rows this would be
>> noticeable.
> In general, that's true.
> But there are very few Shoes apps which need so much high performance, IMHO.
> :-P
>
> As you know, the `:item` attribute is just an array.
> So, if you accept using `item=` method when you update each time,
> you can use whole methods of Array.
>
> ashbb
>

Re: [shoes] TreeView Component

From:
ashbb
Date:
2011-08-08 @ 14:38
Hi Nicholas,

>> But there are very few Shoes apps which need so much high performance,
IMHO.
> I do!

Oh, yeah. :)
Okay, let me try to check treeview performance with Green Shoes.

Look at this: https://gist.github.com/1131773

- Line 23-33: Set up a table with about 10000 rows.
- Line 36-45: Open TreeView Window button launches a treeview window.
- Line 47-52: Modify Data button replaces two values to 9999
  and executes `tree_view_text=` method.
- Line 5-18: The tree_view_text= method does just clear all treeview rows
  and append all new rows again.

Look at this flush video:
http://www.rin-shun.com/shoes/green_shoes_treeview_performance_check.swf.html

The performance (response) is not so quick, but not so bad, IMHO.
Can't you bear it?

ashbb