This page is no longer maintained — Please continue to the home page at www.scala-lang.org

scala.swing JTree wrapper design

5 replies
Ken Scambler
Joined: 2009-11-07,
User offline. Last seen 42 years 45 weeks ago.

Hi everyone,

I've been working on a prototype for a JTree wrapper for a couple of weeks,
and my ideas have solidified enough to run up the flagpole here.

USE CASES
JTrees are commonly used in several situations:
- A small, fixed tree as a navigation mechanism
- A graphical representation of some data structure in tree form.
- As above, but editable: nodes can be edited, added, removed, and moved.
(Also dragged and dropped)
- A representation of the filesystem, nested into directories
- A deeply nested, infinite, or cyclic graph structure that needs to be
constructed lazily. This is particularly arduous in Java, but is easily
supportable in Scala.

MODEL REPRESENTATION
In Scala Swing, the actual Swing XXXModel classes are hidden, and real
collections of user objects are used. For instance ListView[A] uses List[A]
as its representation, and Table exposes what looks like a 2D Seq.

While a tree has no natural Scala collection capable of displaying it, most
user's tree structures will emerge naturally from their data structures, or
with nested Products (ie case classes/tuples) or nested Seqs. The only
reasonable model representation, IMHO is to provide a root object, and a
function that finds child nodes given another node. This can easily allow
for all the use cases above; our class signature is therefore:

class Tree[A](root: A, createChildren: A => Seq[A])

// Usage examples:
// Display filesystem
new Tree[File](new File("."), f => if (f.isDirectory) f.listFiles else
Seq.empty)

// Recursively factorise all integers from 1 to 1000 (infinite depth):
new Tree[Int](1000, n => 1 to n filter (n % _ == 0)}

// Display some XML document
new Tree[Node](Foo, {_.child filterNot
(_.text.trim.isEmpty)})

TYPED OR UNTYPED?
It is debatable whether we should use a type parameter; many lists do not
care about the type of their contents, or use nested containers and
therefore have no common class between branch and leaf nodes. However, I
think there is enough chance that a user WILL care about the node type that
it is still useful. An example is displaying the filesystem; each branch and
leaf would be a directory or file respectively.

SELECTION REPRESENTATION
In Java Swing, a unit of selection in a JTree is a TreePath. TreePath is
basically a glorified List, and does not deserve to be propagated or wrapped
in Scala. scala.List[A] fits perfectly in this role. However, it is
probably appropriate to provide a type alias, such as:
1) object Tree {type Path[A] = List[A]}
2) class Tree[A]... {type Path = List[A]}

I'm not sure which one is really appropriate though, if any; it makes some
sense for the Path type to belong to the Tree instance. However, it's ugly;
someone used to dealing with "TreePath" now has to deal with "Tree[A]#Path".
Tree.Path[A] is slightly nicer. Alternatively, we could simply put type
TreePath = List[A] in the scala.swing package object.

NO BRAINERS
Some design patterns can be directly copied from Tree's cousins, ListView
and Table:
- object selection {object paths; object rows}
- class Tree.Renderer, Tree.AbstractRender, and Tree.GenericRenderer
- Events:
ComponentEvent

imaier
Joined: 2008-07-01,
User offline. Last seen 23 weeks 2 days ago.
Re: scala.swing JTree wrapper design

Hi Ken,

it's again a fair analysis of the subject, thanks. Here are my comments
(inlined).

On 2/28/10 6:46 AM, Ken Scambler wrote:
>
> Hi everyone,
>
> I've been working on a prototype for a JTree wrapper for a couple of weeks,
> and my ideas have solidified enough to run up the flagpole here.
>
> USE CASES
> JTrees are commonly used in several situations:
> - A small, fixed tree as a navigation mechanism
> - A graphical representation of some data structure in tree form.
> - As above, but editable: nodes can be edited, added, removed, and moved.
> (Also dragged and dropped)
> - A representation of the filesystem, nested into directories
> - A deeply nested, infinite, or cyclic graph structure that needs to be
> constructed lazily. This is particularly arduous in Java, but is easily
> supportable in Scala.

Did you have a look at how other framework do this? Collection controls
with lazily created items are sometimes called "virtual". I guess we can
do something similar with collection views (formerly known as
projections). The question is whether its actually feasible/practical.
Only an implementation can prove it :)

>
> MODEL REPRESENTATION
> In Scala Swing, the actual Swing XXXModel classes are hidden, and real
> collections of user objects are used. For instance ListView[A] uses List[A]
> as its representation, and Table exposes what looks like a 2D Seq.
>
> While a tree has no natural Scala collection capable of displaying it, most
> user's tree structures will emerge naturally from their data structures, or
> with nested Products (ie case classes/tuples) or nested Seqs. The only
> reasonable model representation, IMHO is to provide a root object, and a
> function that finds child nodes given another node. This can easily allow
> for all the use cases above; our class signature is therefore:
>
> class Tree[A](root: A, createChildren: A => Seq[A])

Minor nitpick: createChildren doesn't actually need to *create*
children. I would expect a static tree view just returns a Seq that's
already there somewhere, so simply "children" might a better name.

>
> // Usage examples:
> // Display filesystem
> new Tree[File](new File("."), f => if (f.isDirectory) f.listFiles else
> Seq.empty)
>
> // Recursively factorise all integers from 1 to 1000 (infinite depth):
> new Tree[Int](1000, n => 1 to n filter (n % _ == 0)}
>
> // Display some XML document
> new Tree[Node](Foo, {_.child filterNot
> (_.text.trim.isEmpty)})
>
> TYPED OR UNTYPED?
> It is debatable whether we should use a type parameter; many lists do not
> care about the type of their contents, or use nested containers and
> therefore have no common class between branch and leaf nodes. However, I
> think there is enough chance that a user WILL care about the node type that
> it is still useful. An example is displaying the filesystem; each branch and
> leaf would be a directory or file respectively.

I guess you answered that question in the examples above already. I
would definitely go for a generic TreeView. Adding a single (probably
invariant) type parameter doesn't cost a lot.

>
> SELECTION REPRESENTATION
> In Java Swing, a unit of selection in a JTree is a TreePath. TreePath is
> basically a glorified List, and does not deserve to be propagated or wrapped
> in Scala. scala.List[A] fits perfectly in this role. However, it is
> probably appropriate to provide a type alias, such as:
> 1) object Tree {type Path[A] = List[A]}
> 2) class Tree[A]... {type Path = List[A]}

The Path type probably does not belong to the instance (I assume you can
use a path of one tree view for another). I would put it into the
companion object instead of the package object, as this is more in line
with the existing scala.swing design.

>
> I'm not sure which one is really appropriate though, if any; it makes some
> sense for the Path type to belong to the Tree instance. However, it's ugly;
> someone used to dealing with "TreePath" now has to deal with "Tree[A]#Path".
> Tree.Path[A] is slightly nicer. Alternatively, we could simply put type
> TreePath = List[A] in the scala.swing package object.
>
> NO BRAINERS
> Some design patterns can be directly copied from Tree's cousins, ListView
> and Table:
> - object selection {object paths; object rows}
> - class Tree.Renderer, Tree.AbstractRender, and Tree.GenericRenderer
> - Events:
> ComponentEvent
> +-- TreeEvent[A]
> +-- TreePathSelected[A]
> +-- TreeExpansionEvent[A]
> | +-- TreeCollapsed[A]
> | +-- TreeExpanded[A]
> | +-- TreeWillCollapse[A]
> | +-- TreeWillExpand[A]
> +-- TreeModelEvent[A]
> +-- TreeNodesChanged[A]
> +-- TreeNodesInserted[A]
> +-- TreeNodesRemove[A]
> +-- TreeStructureChanged[A]
>
>
> DEFAULT CHILD METHODS
> To facilitate easy use, the Tree companion object could expose
> createChildren functions for common use cases. For instance:
>
> object Tree {
> def files(f: File)
> def filteredFiles(filter: File => Boolean)(f: File): Seq[File]
>
> def products(node: Any) = node match {
> case s: Seq[_] => s
> case o => Seq.empty
> }
>
> def seqs(node: Any) = node match {
> case s: Seq[_] => s
> case o => Seq.empty
> }
> }

I disagree about these. Why the above and why not other things such as
XML nodes, Document elements, Compiler tree nodes... They don't belong
into a companion object, which is essentially a closed world (not across
revisions but that's a different story :)). If we can come up with some
really time/keystroke saving, really commonly used utils we can put them
elsewhere.

>
> MATTERS REQUIRING FRAMEWORK-WIDE CONSIDERATION:
> - Drag And Drop, and Cell Editing are features which have Swing-wide
> implementation in Java, and need to be Scala-fied in a way that works for
> everything (especially Tree, Table and ListView), not just Tree. I have
> some ideas for these, but it belongs in another thread, and should be
> treated as separate tasks.
>

Uh oh, d'n'd! any suggestions or better yet implementations welcome!

> - There is a level of commonality between Tree, ListView, and Table which
> may need to be abstracted. For instance:
> - Drag and drop (as above)
> - Cell Editing (as above)
> - Selection by rows
> - Iteration through data model items
> - Cell Renderers (currently my Tree.Renderers are almost verbatim
> cut-and-pasted from Table and ListView, suggesting that there is commonality
> requiring a higher level of abstraction)

Agreed.

>
>
> I would be greatly interested on hearing Scala Swing users' opinions on
> these matters! As a long time Swing user (~10 years), I think Ingo's
> wrapper framework is absolutely fantastic, and it would be great to see it
> more feature-complete. Other Swing enthusiasts out there: lets get it
> moving.

Other people's opinions on the above matters are indeed welcome!

Thanks,
Ingo

>
> Cheers,
> Ken
>

Ken Scambler
Joined: 2009-11-07,
User offline. Last seen 42 years 45 weeks ago.
Re: scala.swing JTree wrapper design

Hi Ingo,

Thanks for responding so quickly! My comments inlined again:

>> - A deeply nested, infinite, or cyclic graph structure that needs to be
>> constructed lazily. This is particularly arduous in Java, but is easily
>> supportable in Scala.

> Did you have a look at how other framework do this? Collection controls
> with lazily created items are sometimes called "virtual". I guess we can
> do something similar with collection views (formerly known as
> projections). The question is whether its actually feasible/practical.
> Only an implementation can prove it :)

I've only looked at Swing: the official way is to use
TreeWillExpandListeners, and mutate the TreeModel on click. Now that I've
googled for it, .NET does much the same thing. Scala can and should have a
more natural usage, IMHO. Collection views were my first thought too, but
surprisingly, my implementation works without either technique: (although I
can't prove that Swing won't cause it to infinite loop if the tree doesn't
terminate...)

// In object Tree
protected class LazyNode[A](val parent: Option[LazyNode[A]], val
userObject: A, children: A => Seq[A]) extends TreeNode {
lazy val childNodes: Seq[LazyNode[A]] = children(userObject) map {n =>
new LazyNode(Some(this), n, children)}

def getChildAt(childIndex: Int): TreeNode = childNodes(childIndex)
def getChildCount() = childNodes.size
def getParent(): TreeNode = parent.orNull
def getIndex(node: TreeNode) = childNodes indexOf node
def getAllowsChildren() = true
def isLeaf() = childNodes.isEmpty
def children(): java.util.Enumeration[A] = new java.util.Enumeration[A]
{
val iterator = childNodes.iterator
def hasMoreElements() = iterator.hasNext
def nextElement = iterator.next.userObject
}
override def equals(o: Any) = o match {
case r: AnyRef => r eq this
case _ => false
}
override def toString() = userObject.toString()
}

>> Minor nitpick: createChildren doesn't actually need to *create*
>> children. I would expect a static tree view just returns a Seq that's
>> already there somewhere, so simply "children" might a better name.
Agreed.

> I guess you answered that question in the examples above already. I
> would definitely go for a generic TreeView. Adding a single (probably
> invariant) type parameter doesn't cost a lot.
All good.

> The Path type probably does not belong to the instance (I assume you can
> use a path of one tree view for another). I would put it into the
> companion object instead of the package object, as this is more in line
> with the existing scala.swing design.
No worries.

>> DEFAULT CHILD METHODS
>I disagree about these. Why the above and why not other things such as
>XML nodes, Document elements, Compiler tree nodes... They don't belong
>into a companion object, which is essentially a closed world (not across
>revisions but that's a different story :)). If we can come up with some
>really time/keystroke saving, really commonly used utils we can put them
>elsewhere.

Yeah fair point, that sort of thing probably belongs elsewhere. However, I
think at the least there needs to be some convenient default usage to handle
simple Tree literals (say for a fixed navigation menu), something like:

new Tree("Hobbies" -> ("Surfing", "Lawn bowls", "Extreme Sports" ->
("Spelunking", "Speed dating"))

Haven't worked out exactly the best way to do this yet though. Pleasingly
though, just about anything I can think of can be treated as a special case
of the root node/child function representation.

> Uh oh, d'n'd! any suggestions or better yet implementations welcome!
Can do. I'll deal with it as a separate thing for the moment though.

>> - There is a level of commonality between Tree, ListView, and Table which
>> may need to be abstracted. For instance:
>> - Drag and drop (as above)
>> - Cell Editing (as above)
>> - Selection by rows
>> - Iteration through data model items
>> - Cell Renderers (currently my Tree.Renderers are almost verbatim
>> cut-and-pasted from Table and ListView, suggesting that there is
>> commonality
>> requiring a higher level of abstraction)

>Agreed.
I'll do some more thinking about this, and come up with some sort of
proposal for some sort of master trait for the three widgets.

By the way, how do I go about contributing code? I have, or will soon, a
rough first cut of a prototype, but I'm not sure where to go from here.

Cheers,
Ken

imaier
Joined: 2008-07-01,
User offline. Last seen 23 weeks 2 days ago.
Re: scala.swing JTree wrapper design

On 2/28/10 2:45 PM, Ken Scambler wrote:

> By the way, how do I go about contributing code? I have, or will soon, a
> rough first cut of a prototype, but I'm not sure where to go from here.

Sorry, I missed that part when I read your mail the first time. The most
convenient thing would be if you can put up a public git repository for
now. By the time we integrate your code, we need a signed contributors
agreement from you:

http://www.scala-lang.org/sites/default/files/contributor_agreement.pdf

Thanks!
Ingo

Ken Scambler
Joined: 2009-11-07,
User offline. Last seen 42 years 45 weeks ago.
Re: scala.swing JTree wrapper design
Hi Ingo and others,

I really have to apologise for getting sidetracked, it's been some months now.  In the end I decided it was better to just publish it first and fix it later.  I've added the code to
 
http://github.com/kenbot/ScalaSwingTreeWrapper

It's not complete, and at the moment, the Editor functionality doesn't work.  I've put some effort towards trying to abstract some common functionality out between Lists, Trees, and hypothetical TreeTables and ListTables that have Renderable and Editable cells.

The common trait is called "Cells", and I've tried to put common Renderer and Editor functionality in a "CellsCompanion" trait.

I'm not yet convinced that the common base traits pull their weight, but we'll see.

(sorry if this has appeared twice -- I had mistakenly posted this to Nabble rather than the mailing list proper.)
Cheers,
Ken

On 6 March 2010 19:11, Ingo Maier <ingo [dot] maier [at] epfl [dot] ch> wrote:
On 2/28/10 2:45 PM, Ken Scambler wrote:

By the way, how do I go about contributing code? I have, or will soon, a
rough first cut of a prototype, but I'm not sure where to go from here.

Sorry, I missed that part when I read your mail the first time. The most convenient thing would be if you can put up a public git repository for now. By the time we integrate your code, we need a signed contributors agreement from you:

http://www.scala-lang.org/sites/default/files/contributor_agreement.pdf

Thanks!
Ingo


imaier
Joined: 2008-07-01,
User offline. Last seen 23 weeks 2 days ago.
Re: scala.swing JTree wrapper design

Oh, I totally missed this. That's awesome!

Keep up the good work!
Ingo

On 7/9/10 4:36 AM, Ken Scambler wrote:
> Hi Ingo and others,
>
> I really have to apologise for getting sidetracked, it's been some
> months now. In the end I decided it was better to just publish it first
> and fix it later. I've added the code to
>
> http://github.com/kenbot/ScalaSwingTreeWrapper
>
> It's not complete, and at the moment, the Editor functionality doesn't
> work. I've put some effort towards trying to abstract some common
> functionality out between Lists, Trees, and hypothetical TreeTables and
> ListTables that have Renderable and Editable cells.
>
> The common trait is called "Cells", and I've tried to put common
> Renderer and Editor functionality in a "CellsCompanion" trait.
>
> I'm not yet convinced that the common base traits pull their weight, but
> we'll see.
>
> (sorry if this has appeared twice -- I had mistakenly posted this to
> Nabble rather than the mailing list proper.)
> Cheers,
> Ken
>
> On 6 March 2010 19:11, Ingo Maier > wrote:
>
> On 2/28/10 2:45 PM, Ken Scambler wrote:
>
> By the way, how do I go about contributing code? I have, or will
> soon, a
> rough first cut of a prototype, but I'm not sure where to go
> from here.
>
>
> Sorry, I missed that part when I read your mail the first time. The
> most convenient thing would be if you can put up a public git
> repository for now. By the time we integrate your code, we need a
> signed contributors agreement from you:
>
> http://www.scala-lang.org/sites/default/files/contributor_agreement.pdf
>
> Thanks!
> Ingo
>
>

Copyright © 2012 École Polytechnique Fédérale de Lausanne (EPFL), Lausanne, Switzerland