nishihatapalmer/TreeTable

How to use the classes in my own project

Closed this issue ยท 45 comments

carlca commented

I've used mvn clean install to build your project and to copy (hopefully!) the swing.treetable classes to the Maven repository (.m2) on my Mac. Am I right in thinking that I can refer to these classes now without having to add the files to my project?

carlca commented

This is all that has been produced in the Maven repository...

SCR-20231016-nquo

The one file in there just contains this...

SCR-20231016-nria

Needless to say, any attempt to reference the TreeTable stuff either in my pom.xml...

SCR-20231016-nsas

or my code...

SCR-20231016-ntnc

is meeting with failure!

You've got lots of info on your Github showing how to use the code, not so much on how to access it in the first place.

I suppose I could add your .java files to my project, but I was under the impression this was not the correct way of doing things...

Hi Carlca,

The version of TreeTable defined in its POM file is 1.0-SNAPSHOT. So your version field should say "1.0-SNAPSHOT", not "RELEASE".

Hope that helps. Note that I have not published TreeTable to maven central yet, so you would have to either include the TreeTable files with your code if you want other people to build it, or provide instructions to install it locally for them too.

I believe that TreeTable is pretty stable - but I never finished writing all the tests I wanted to and so never got to publish it - then I got distracted by other important work that came up. If you are intent on using it, then I will look at publishing a final version on maven central.

carlca commented

Hi! Thanks for the reply. I've gotten round this question by, as you suggest, including the TreeTable in my own project, but being careful not to amend it in any way. It's curious that you mention Maven Central, because I was able to publish a library package of my own to the local repository in .m2 by doing a mvn clean install. This didn't work with TreeTable, but I've got it building, so that's all right.

I tried correcting the entry in pom.xml to 1.0-SNAPSHOT but the .m2 repository remained as described in the images I posted earlier, but as I say, I'm happy to use the classes directly!

carlca commented

I've been following the instructions in your Github page and by looking at the demo project. I'm making progress, I think, but I'm still not getting the expansion and collapse of the nodes working. These images will illustrate the problem...
(I hope you don't mind me asking these questions, only, after trying several other "TreeTable" solutions, your's is the first one to look like it may be the one!)

Top level nodes showing...

SCR-20231016-scsi

After clicking on the audiothing node...

SCR-20231016-sdss

The correct number of nodes are being expended. Here is an excerpt of the underlying data...

audio - audio.vital.synth - Vital.clap
audiodamage - com.audiodamage.dubstation2 - Dubstation 2.clap
audiomodern - com.audiomodern.chordjam - Chordjam.clap
audiority - com.audiority.polaris - Audiority/Polaris.clap
audiority - com.audiority.spacestationum282 - Audiority/Space Station UM282.clap
audiothing - com.audiothing.blindfoldeq - BlindfoldEQ.clap
audiothing - com.audiothing.filterjam - Filterjam.clap
audiothing - com.audiothing.fogconvolver2 - FogConvolver2.clap
audiothing - com.audiothing.noises - Noises.clap
audiothing - com.audiothing.outerspace - OuterSpace.clap
audiothing - com.audiothing.spacestrip - SpaceStrip.clap
audiothing - com.audiothing.springs - Springs.clap
audiothing - com.audiothing.thingsbubbles - ThingsBubbles.clap
audiothing - com.audiothing.thingscrusher - ThingsCrusher.clap
audiothing - com.audiothing.thingsflipeq - ThingsFlipEQ.clap
audiothing - com.audiothing.thingsmotor - ThingsMotor.clap
audiothing - com.audiothing.thingstexture - ThingsTexture.clap
audiothing - com.audiothing.valves - Valves.clap
birdbird - com.birdbird.birdsampler - Rolling Sampler.clap
chowdsp - org.chowdsp.byod - BYOD.clap
chowdsp - org.chowdsp.chowtapemodel - CHOWTapeModel.clap
chowdsp - org.chowdsp.chowkick - ChowKick.clap
chowdsp - org.chowdsp.chowmultitool - ChowMultiTool.clap

Ignore the fact that columns 2 and 3 are not showing anything yet, I just haven't got round to that yet! But what should happen, obviously, is that 13 new lines should be added to the list and all of the entries (manufacturers in this case) should move down accordingly.

I based my code on the code in the demo project. This is my addExpandCollapseListener...

				treeModel.bindTable(pluginTable);
				// deal with collapse and expansion of nodes
				treeModel.addExpandCollapseListener(new TreeTableModel.ExpandCollapseListener() {
					@Override
					public boolean nodeExpanding(TreeNode node) {
						if (node.getChildCount() == 0) { // if a node is expanding but has no children, set it to allow no children.
							((DefaultMutableTreeNode) node).setAllowsChildren(false);
						}
						return true;
					}
					@Override
					public boolean nodeCollapsing(TreeNode node) {
						((DefaultMutableTreeNode) node).removeAllChildren();
						return true;
					}
				});

I notice that on the Github instruction you suggest yourMethodToAddChildrenToNode(node); but the Demo code didn't seem to have that but still works as expected, so I'm a bit confused by this. I've obviously missed out an important step!

Any pointers (not applicable in a Java context, I know!) you can give me would be really appreciated ๐Ÿ˜ƒ

carlca commented

My whole project is available to view at https://github.com/carlca/PluginManager

You wouldn't be able to run the project, unless by an amazing coincidence, you happen to have a bunch of VST, VST3 or CLAP audio plugins installed on your system, and that system happened to be macOS! The project only works on macOS, because audio plugins on the Mac are similar to apps in that the files are really folders in disguise, and this app relies on this to access the metadata!

If you do have both of these things, I will happily let you have a copy of the working app, once it is working, that is ๐Ÿ˜‰

Hi, I can't meet those strict technical criteria! But I do have a windows 10 machine with a load of VSTs, Cubase, Ableton and a few other things on it!

I will have a look at your project code when I get a moment, might not be until the weekend though.

carlca commented

No worries! I'll continue to try and suss it out until then. It's a good learning exercise in any case ๐Ÿ˜‰

Had a quick look at your code.

Looks like you build the whole tree before you instantiate the model. You probably don't need the expandcollapselistener, that's designed for dynamically changing the tree on an expand or collapse event.

carlca commented

The data is held in a:
List<Triplet<String, String, String>> plugins = new ArrayList<>();
Where each triplet is of the form Manufacturer, Ident and Plugin.
The data is essentially in this non-tree format...

audiority - com.audiority.polaris - Audiority/Polaris.clap
audiority - com.audiority.spacestationum282 - Audiority/Space Station UM282.clap
audiothing - com.audiothing.blindfoldeq - BlindfoldEQ.clap
audiothing - com.audiothing.filterjam - Filterjam.clap
audiothing - com.audiothing.fogconvolver2 - FogConvolver2.clap
audiothing - com.audiothing.noises - Noises.clap
audiothing - com.audiothing.outerspace - OuterSpace.clap
audiothing - com.audiothing.spacestrip - SpaceStrip.clap
audiothing - com.audiothing.springs - Springs.clap
audiothing - com.audiothing.thingsbubbles - ThingsBubbles.clap
audiothing - com.audiothing.thingscrusher - ThingsCrusher.clap
audiothing - com.audiothing.thingsflipeq - ThingsFlipEQ.clap
audiothing - com.audiothing.thingsmotor - ThingsMotor.clap
audiothing - com.audiothing.thingstexture - ThingsTexture.clap
audiothing - com.audiothing.valves - Valves.clap
birdbird - com.birdbird.birdsampler - Rolling Sampler.clap
chowdsp - org.chowdsp.byod - BYOD.clap
chowdsp - org.chowdsp.chowtapemodel - CHOWTapeModel.clap
chowdsp - org.chowdsp.chowkick - ChowKick.clap
chowdsp - org.chowdsp.chowmultitool - ChowMultiTool.clap

The app, as it stands, is creating just one node for the manufacturer audiothing but it is adding plugin nodes to the manufacturerNode...
manufacturerNode.add(pluginNode);

So, what you saw in the image above was just the collapsed table rows - one for each Manufacturer, but each manufacturer has one or more subnodes for the plugins. It's those pluginsthat I want to see when I extend a manufacturer node.

carlca commented

In your demo app, it starts off by displaying the equivalent of my app displaying the collapsed list of manufacturers. The difference is that your app behaves correctly when a node is expanded; the nodes below all move down to accommodate the newly revealed sub-nodes. That's all I want mine to do!!

carlca commented

Hi Matt! Could I ask you to take another quick look at my project.
https://github.com/carlca/PluginManager.git

I had to replace the existing repo with an entirely new one due to some git related nonsense. Suffice it to say, I will not be sending Linus Torvalds a Christmas card this year, and am about to investigate Mercurial as an alternative to Git ๐Ÿ˜ 

I've updated the project so that it displays both a JTree showing the data structure correctly, and a JTable using my attempts to use your classes. I've also added in a 4th option to the drop down called "DEMO" which should show the data without needing macOS plugins. It's an IntelliJ project so you should be able to build and run.

You'll find the JTree code at line 111 of PluginManager.java and the JTable code at line 118.

Both approaches are using the same data code from lines 97 to 109...

Something odd is certainly going on. Tree expansion is working, but for some reason the table is not getting the values to display from the treetablemodel. It never seems to call getColumnValue, which is what returns the actual values from the tree. This should be what the JTable calls from a TableModel to get what to display.

Expanding a node does expand the node, but again, the values are just the original list. I can see that the TreeTableModel is indeed building the correct nodes to display, but it isn't then calling getColumnValue to obtain the actual display data.

What is weird about this is that by telling a JTable that it has a TableModel, calling getColumnValue is just what a JTable does, nothing to do with my code. No idea what is going on right now - but some progress I guess. My original demo code works as before.

carlca commented

Thanks again for looking at this! I'm not going to do anything more on it tonight, as I'm watching the football! I'll take a fresh look at it tomorrow morning.

OK, I've found two problems in your code:

  1. You override getValueAt() in PluginManagerTableModel. Don't do this! Not only is it not necessary to override getValueAt, you are trying to take the value from the tree model in that method, not the tree table model!

  2. The user object associated with each DefaultMutableTreeNode is a String, not a Plugin object.

You need to create the user object of the DefaultMutableTreeNode as the Plugin object, not the string representation of it.

Otherwise, when the tree table model tries to get the user object and get the different column values, it only has the name of the object, not the object itself that it can get the properties of.

carlca commented

That's great! I'll check that out first thing tomorrow. Thanks, again. I really appreciate your help ๐Ÿ˜ƒ

carlca commented

Hi Matt! I think we're almost there. The nodes are expanding correctly and the sub-nodes are being displayed. The only problem is that the overall list is not growing to accommodate the extra inserted nodes.

Could you grab the repo again, run the app and select DEMO then update? Note the behaviour of the very last node vcvrack, in the JTable compared to the JTree. It's the same with any node - the total number of items is not growing, but it's best illustrated with the last item!

The other details such as displaying the appropriates text in the right columns, I can take care of.

carlca commented

Aha! I wonder if it's because in your MyObject class, you have the children defined as private final List<MyObject> children; whereas in mine the equivalent class is just defined as private final List<Object> children;. One way to find out ๐Ÿ˜‰

carlca commented

No, that wasn't it. Any ideas?

carlca commented

Could it be because in your demo app, within the MyObjectForm class, you are explicitly recursively loading random levels of child nodes, whereas in mine, the data is just a flat list of Plugin objects. I spotted this because on mine, the addClass method was marked as unused, whereas it is used in mine. I think that has to be the answer...

carlca commented

No that wasn't it either! The problem as that, for some reason, I was overriding the getRowCount which was fixing the number of rows on display. The clue was in the method, I guess ๐Ÿ˜‰

Thanks very much for your help, Matt! I should be able to tidy things up from here on in! It's been a useful learning exercise ๐Ÿ˜ƒ

That is probably not the problem. If the nodes are pre built, there's nothing to do. The demo is just illustrating that you can build nodes dynamically on expand if you want to.

I'll have a look when I get a mo.

carlca commented

You say that, but as far as I can tell the nodes are all behaving totally correctly now!

carlca commented

As you suggested, and which I had forgotten, the data is being assembled into a two level tree with this code...

	// Create the root node
	DefaultMutableTreeNode root = new DefaultMutableTreeNode("Plugins");
	// Iterate over the plugins and create child nodes for each manufacturer and plugin
	for (Triplet<String, String, String> plugin : scanner.getPlugins()) {
		String manufacturer = plugin.getValue0();
		String ident = plugin.getValue1();
		String pluginName = plugin.getValue2();
		// Find or create the manufacturer node
		DefaultMutableTreeNode manufacturerNode = findOrCreateManufacturerNode(root, manufacturer);
		// Create the plugin node
		DefaultMutableTreeNode pluginNode = new DefaultMutableTreeNode(pluginName);
		// Add the plugin node to the manufacturer node
		manufacturerNode.add(pluginNode);
	}

Haha, I just looked at it and it was working!

Glad it's all good for you now.

Also a useful learning exercise for me, you never know what people will find confusing when you write things up.

Although I just found a bug, when you try to expand a leaf node there is an exception. Not sure if that is your code or mine.

Probably should set allowschildren to false for leaf nodes though. Then there will be no expand handle anyway.

carlca commented

Yes, well spotted! That was the conclusion I came to mere nano-seconds ago. It can wait till tomorrow. It's to late to start messing around with it ๐Ÿ˜‰

It is a bug in TreeTable, in the TreeTableRowSorter. If the table has never been sorted, but the table tries to update a node, then we get an index out of range as there is no sorted indexes available. Needs to check if there is anything there before it tries to check if the update is inside it!

I will fix this and issue an update.

But still, if you don't want leaf nodes to have expand/collapse handles, set allowsChildren to false on the leaf DefaultMutableTreeNode and the problem should be avoided.

carlca commented

It's coming on nicely ๐Ÿ˜ƒ

Screenshot 2023-10-19 at 21 27 39

There's a couple of small points you could still help me with. I can't work out how to get the folder icons showing in the lower version. The icons are referenced in the code, and indeed, they show up in the JTree without any special catering for it.

Also, could you point me in the right direction for changing the keystrokes. I'd like to have Cursor Right to expand the node, and cursor left to jump to the parent node, or to collapse the parent if the cursor is there already. Essentially the same thing that the JTree does.

carlca commented

I sorted out the vanishing icons. It helps if you call setIcons() ๐Ÿ˜‰

carlca commented

I've almost sorted out the key strokes. I've got the expand and collapse working for a parent node. The one thing that is missing if for the tree to respond to a VK_LEFT on a leaf node (I remember the VK_ codes from my Delphi/Windows days!) by selecting the parent node of that node, but without collapsing anything at that point.

I've made a start by creating a JTable descendent which I intend to use in place of the JTable. The code is...

package com.carlca.pluginmanager;

import net.byteseek.swing.treetable.TreeTableModel;

import javax.swing.*;
import javax.swing.tree.TreeNode;
import java.awt.event.KeyEvent;
import net.byteseek.swing.treetable.TreeUtils;

public class PluginManagerJTable extends JTable {

  @Override
  protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) {
    if (ks.getKeyCode() == KeyEvent.VK_LEFT && !isEditing()) {
      if (getSelectedRow() >= 0 && getSelectedColumn() == 0) {
        TreeTableModel treeTableModel = (TreeTableModel) getModel();
        int selectedRow = getSelectedRow();
        TreeNode selectedNode = (TreeNode)treeTableModel.getValueAt(selectedRow, 0);
        if (selectedNode.isLeaf()) {
          // walk back through the nodes until a node is reached which is the parent of selectedNode
          // then select that node

          // something involving TreeUtils???

          // confirm that the key has been processed
          return true;
        }
      }
    }
    return super.processKeyBinding(ks, e, condition, pressed);
  }
}

If you look at the comments, you'll see what I want to do. Any ideas?

Why not just call Treenode.getParent()?

carlca commented

That's an excellent question ๐Ÿ˜‰ Now I've found the parent, how do I select it programatically?

I may consider adding support for the key behaviour you mention. Would be good to follow the principle of least surprise and behave like a Jtree by default.

Haven't worked with Jtree much before; do you know if the Jtree key behaviour is documented anywhere? Can't seem to find it in the oracle documentation.

carlca commented

If you check my repo now, you'll see that the key behaviour is exactly how I wanted it. I ended up having to create a JTable descendent which I used to replace the JTable in the app (IntelliJ is really good at letting you do this!).
https://github.com/carlca/PluginManager.git

Cool, I will have a look.

I am investigating replicating Jtree navigation key bindings in TreeTable directly, so you may not need this eventually.

Already fixed the exception bug ๐Ÿ˜

carlca commented

Good call about combining the Manufacturer and Plugin Columns. This is what it looks like now...

Screenshot 2023-10-22 at 23 23 53

I'm think of sacrificing the Ident column and using the space for user defined tags!