Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NSBrowser bindings changes and general binding improvements #284

Open
wants to merge 29 commits into
base: master
Choose a base branch
from

Conversation

gcasa
Copy link
Member

@gcasa gcasa commented Aug 23, 2024

This branch is to implement bindings in NSBrowser to NSTreeController and to fix and improve our use of bindings.

@gcasa gcasa requested a review from fredkiefer as a code owner August 23, 2024 07:30
@gcasa gcasa marked this pull request as draft August 23, 2024 07:31
@gcasa
Copy link
Member Author

gcasa commented Aug 23, 2024

@fredkiefer Converted to draft. Sorry. :)

@gcasa gcasa force-pushed the NSBrowser_bindings_branch branch from 83958c1 to 35fb894 Compare September 9, 2024 13:23
@gcasa
Copy link
Member Author

gcasa commented Oct 11, 2024

It's now working, I am reviewing how it's working because I had to do something similar to what I did in NSOutlineView, which I am not sure of.

@gcasa gcasa marked this pull request as ready for review October 11, 2024 20:18
// but if one isn't it uses the description... per documentation.
if (keyPath != nil)
{
val = [child valueForKeyPath: keyPath];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fredkiefer Per our discussion yesterday I am going to try a few things to get the value directly with bindings. It seems to me that arrangedObjects.value would work if the data structure was a simple array, but given that it is a set of tree nodes with embedded children it doesn't seem quite as straightforward. It is still, perhaps, that I am not seeing something that is obvious.

Copy link
Member Author

@gcasa gcasa Oct 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fredkiefer Honestly, I have been thinking about this a lot. I don't know if there is a way to simplify it. With the path that is given arrangedObjects.value, this syntax is okay for arrays and simple data structures, but I am not sure it's going to work as we are using NSTreeNode or similar data structures. I agree that bindings should make things easier but I am not sure how to make it easier for US. sigh. As implemented this works as expected, but it uses the same "trick" to get the value as NSOutlineView. Alternatively, I would need to concatenate the value keyPath with arrangedObjects to get the above or, alternatively, get if it's a leaf or other attributes by doing something similar (manipulating the keyPath).

So it seems no matter what I do the solution is slightly messy. I apologize, a lot of this is me ranting after sitting here cogitating this for hours on end.

@gcasa
Copy link
Member Author

gcasa commented Nov 9, 2024

@fredkiefer These changes currently function as intended. I am still looking for a way to simplify them to your satisfaction. I am unconvinced, however, that, given the complexity of the data structure, that KVB can do it as simply as we would like. Ultimately, I'm wondering if it even matters if it is simple on our side as long as it works and is simple for the developer/user.

Copy link
Member

@fredkiefer fredkiefer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I might be wrong with some of the comments, it is just I don't understand the intention behind all this.

Source/NSBrowser.m Outdated Show resolved Hide resolved
Source/NSBrowser.m Show resolved Hide resolved
@@ -3319,7 +3344,20 @@ - (id) _itemForColumn: (NSInteger)column
{
id cell = [selectedCells objectAtIndex: 0];

item = [cell objectValue];
if (theBinding != nil)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The binding is used only as an indicator. That way we could as well set the objectValue of each cell instead of storing the values in the _columnDictionary. This looks like we added complexity without gaining anything.
I would expect that if we want to treat bindings differently, we would try to get the values only when needed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am working on this issue.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the better solution here might be to use the column matrix, but I am not certain. This would eliminate the need for the columnDictionary (possibly) the issue is that the binding based code generally needs the objects to be "nodes" which contain references to children, etc... the objectValue is used as what is displayed, so setting that the node class wouldn't work right. I am working through this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tried the above. It seems as though the objectValue needs to be the string or object actually being displayed, not the node.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what you tried and why that failed. Those this mean that in the binding case it is not possible to set the value of the cells, while it works in the other cases? That really sounds weird. I really don't want to block you with this but the current code in this PR seems to have a lot of unnecessary complexity. If that gets into the codebase it is really hard to remove it again. I wouldn't be willing to pay that price but if you want to go ahead with it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cell of the NSBrowser uses the objectValue as what is displayed in the browser. What I had tried was to set the objectValue to the actual node. This results in getting the columns correctly, but the data doesn't display properly. What I will do is try this another way to see if I can factor out the need for the columnDictionary.

Source/NSBrowser.m Show resolved Hide resolved
@gcasa
Copy link
Member Author

gcasa commented Nov 11, 2024

Sorry, I might be wrong with some of the comments, it is just I don't understand the intention behind all this.

No worries. I'll do my best to explain the intentions behind each change. There were honestly some things in the macOS implementation that I didn't understand myself.

@gcasa
Copy link
Member Author

gcasa commented Dec 15, 2024

With your permission, I will go ahead and merge this unless you have any overriding concerns. @fredkiefer

@gcasa
Copy link
Member Author

gcasa commented Jan 19, 2025

@fredkiefer I have made further changes to the tests that indicate that the objectValue is set to the string of the node on Cocoa. I feel as though I have explored every possibility. The only solution is to maintain some parallel data structure. If I could store it in the cell itself, that would be feasible.

@fredkiefer
Copy link
Member

Let's go back a step and look at all this from the outside. An NSBrowser has different ways to get its values for display. This could be via different types of delegates, that come either as item based delegate, passive delegate or a delegate that creates the rows for each column itself.
What we want to achieve here is to extend this mechanism to bindings. In my understanding having a binding completely replaces the delegate behaviour. This seems not to be the case for your code as it will be mostly be active when an item based delegate is present. Otherwise there would be no need to change the _itemForColumn: method.
If this is correct, I would suggest that you have a separate branch inside all methods where bindings come into play. That would make the code easier to read and understand.
Your code also seems to mix the concepts of NSContentBinding and NSContentValueBinding. I would expect that this only works correctly when both basically reference the same objects, with that later just adding one more key to the path. In most cases this will hopefully be the case, but does the implementation really have to rely on that? Maybe we could ignore NSContentValueBinding for the start and only implement the description behaviour?

Could you please point me to the test code that you are using for this feature? Maybe I will be able to better understand what you are trying to handle with your changes.

@gcasa
Copy link
Member Author

gcasa commented Jan 20, 2025

@fredkiefer its here:

https://github.com/gcasa/NSBrowser_binding_test

I though I had linked this before. My convention is to name the test similar to the branch name so I can keep it straight. My apologies.

@gcasa
Copy link
Member Author

gcasa commented Jan 20, 2025

I will make further comments in the morning. I am not feeling well at the moment. I will explain why it contains and uses NSContentBinding and NSValueBinding the way it does.

@gcasa
Copy link
Member Author

gcasa commented Jan 20, 2025

Let's go back a step and look at all this from the outside. An NSBrowser has different ways to get its values for display. This could be via different types of delegates, that come either as item based delegate, passive delegate or a delegate that creates the rows for each column itself. What we want to achieve here is to extend this mechanism to bindings.

The code as I have implemented it does this. It gets the relevant data from the bindings rather than from the delegate.

In my understanding having a binding completely replaces the delegate behaviour. This seems not to be the case for your code as it will be mostly be active when an item based delegate is present.

The bindings behave very much like the item based delegate in how the data is pulled from each node.

Otherwise there would be no need to change the _itemForColumn: method. If this is correct, I would suggest that you have a separate branch inside all methods where bindings come into play.

Is that not what is being done? Every method that pulls the data has different conditionals for when a delegate is used and when a binding is used.

That would make the code easier to read and understand. Your code also seems to mix the concepts of NSContentBinding and NSContentValueBinding.

This difference is called for in the documentation...
https://developer.apple.com/library/archive/documentation/Cocoa/Reference/CocoaBindingsRef/BindingsText/NSBrowser.html

See under content... it says that "Unless contentValues is also bound, the titles of the items in the NSBrowser are derived by invoking invoking the descriptionmethod for each of the content objects."

I would expect that this only works correctly when both basically reference the same objects, with that later just adding one more key to the path. In most cases this will hopefully be the case, but does the implementation really have to rely on that? Maybe we could ignore NSContentValueBinding for the start and only implement the description behaviour?

See above.

Could you please point me to the test code that you are using for this feature? Maybe I will be able to better understand what you are trying to handle with your changes.

Previous comment does this.

https://github.com/gcasa/NSBrowser_binding_test

Per the test, the behavior as I have implemented it is consistent with the documentation and tests on macOS Ventura. Please let me know if you can think of a cleaner way to implement this as I am at a loss.

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

Successfully merging this pull request may close these issues.

2 participants