Issues with NSOutlineView and ImageAndTextCell - objective-c

I'm having an NSOutlineView with items (and children in it).
Here's the cell modification code :
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item {
if ([item isKindOfClass:[JQPage class]])
{
[cell setImage:[NSImage imageNamed:#"doc_empty_icon&16"] size:16.0];
}
else if ([item isKindOfClass:[JQElement class]])
{
[cell setImage:[NSImage imageNamed:#"brackets_icon&16"] size:16.0];
}
}
And here's a visual example of what I need :
Any ideas?

Im not sure about pushing the children to the right. You might have to overwrite som e class function.
You can swap the image to a white version when the item is selected. I don't know if NSOutlineView support Automatic Black/white swap, but if it does you have to set the background style with something like
-(void)setBackgroundStyle:(NSBackgroundStyle)backgroundStyle {
[super setBackgroundStyle:NSBackgroundStyleLight];
}

What you're looking for regarding the image is NSImage setTemplate. From the docs,
Cocoa cells take advantage of the nature of template images—that is, their simplified color scheme and use of transparency—to improve the appearance of the corresponding control in each of its supported states.
In this case the image will be swapped with its negative on selection.

Related

NSOutlineView setNeedsDisplayInRect calls fail

I have an NSOutlineView that uses a custom NSCell subclass to draw an NSProgressIndicator. Each NSCell has a refreshing property that is set by the NSOutlineView delegate willDisplayCell:forItem: method, like so:
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
cell.refreshing = item.refreshing;
}
Each item instance contains an NSProgressIndicator that is started and stopped dependent upon whether that particular item is refreshing.
- (NSProgressIndicator *)startProgressIndicator
{
if(!self.progressIndicator)
{
self.progressIndicator = [[[NSProgressIndicator alloc] initWithFrame:NSMakeRect(0, 0, 16.0f, 16.0f)] autorelease];
[self.progressIndicator setControlSize:NSSmallControlSize];
[self.progressIndicator setStyle:NSProgressIndicatorSpinningStyle];
[self.progressIndicator setDisplayedWhenStopped:YES];
[self.progressIndicator setUsesThreadedAnimation:YES];
[self.progressIndicator startAnimation:self];
}
return self.progressIndicator;
}
- (void)stopProgressIndicator
{
if(self.progressIndicator != nil)
{
NSInteger row = [sourceList rowForItem:self];
[self.progressIndicator setDisplayedWhenStopped:NO];
[self.progressIndicator stopAnimation:self];
[[self.progressIndicator superview] setNeedsDisplayInRect:[sourceList rectOfRow:row]];
[self.progressIndicator removeFromSuperviewWithoutNeedingDisplay];
self.progressIndicator = nil;
}
for(ProjectListItem *node in self.children)
{
[node stopProgressIndicator];
}
}
The items NSProgressIndicator instances are stopped and started within my NSCell's drawInteriorWithFrame:inView: class, like so:
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
if(self.refreshing)
{
NSProgressIndicator *progressIndicator = [item progressIndicator];
if (!progressIndicator)
{
progressIndicator = [item startProgressIndicator];
}
// Set the progress indicators frame here ...
if ([progressIndicator superview] != controlView)
[controlView addSubview:progressIndicator];
if (!NSEqualRects([progressIndicator frame], progressIndicatorFrame)) {
[progressIndicator setFrame:progressIndicatorFrame];
}
}
else
{
[item stopProgressIndicator];
}
[super drawInteriorWithFrame:cellFrame inView:controlView];
}
The issue I'm having is that although the NSProgressIndicators are correctly told to stop, the calls to stopProgressIndicator have no effect. Here's the code that fails to trigger the refresh of the NSOutlineView row in question. I've manually checked the NSRect returned by my call to rectOfRow and can confirm that the value is correct.
[self.progressIndicator setDisplayedWhenStopped:NO];
[self.progressIndicator stopAnimation:self];
[[self.progressIndicator superview] setNeedsDisplayInRect:[sourceList rectOfRow:row]];
[self.progressIndicator removeFromSuperviewWithoutNeedingDisplay];
self.progressIndicator = nil;
When the NSOutlineView finishes refreshing all of its items it triggers a reloadData: call. This is the only thing that seems to reliably update all of the cells in question, finally removing the NSProgressIndicators.
What am I doing wrong here?
It's bad practice to mess with view hierarchies while things are getting drawn. The best thing to do would be to use an NSCell version of NSProgressIndicator, but AppKit doesn't have such a thing. The solution described in Daniel Jalkut's answer to a similar question is probably the fastest path to what you want. Here's the basic idea:
Rather than use the refreshing variable to switch paths during drawing, observe your items' refreshing state. When it becomes true, add an NSProgressIndicator as a subview of your NSOutlineView at the item's position given frameOfCellAtColumn:row: (it can be convenient to set up a column for the progress indicators on the far right). When it becomes false, remove the corresponding progress indicator. You will also want to be sure to set the autosizing flags appropriately on these progress indicators so they move around as the outline view gets resized.

NSOutlineView Changing disclosure Image

I my outline view, i am adding Custom cell, To drawing custom cell, i am referring example code , present in the Cocoa documentation
http://www.martinkahr.com/2007/05/04/nscell-image-and-text-sample/
I want to change the disclosure image of the cell with my custom image, i have tried following things
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
if([item isKindOfClass:[NSValue class]])
{
MyData *pDt = (MyData *)[item pointerValue];
if(pDt->isGroupElement())
{
[cell setImage:pGroupImage];
}
}
}
but that too not working, Is there any other way to change the disclosure image,
also how can i find out in willDisplayCell whether Item is expand or collapse, so i can set the image accordingly,
Is this only the place to change the disclosure image ?
After researching this issue myself, and trying some of the answers here, I have found that the other approaches mentioned will work, but will require that you perform a lot more manual intervention in order to avoid screen artifacts and other strange behavior.
The simplest solution I found is the following, which should work in most cases.
This solution has the added benefit of the system automatically handling a great many other cases, such as column movement, etc, without your involvement.
- (void)outlineView:(NSOutlineView *)outlineView willDisplayOutlineCell:(id)cell
forTableColumn:(NSTableColumn *)tableColumn
item:(id)item
{
[cell setImage:[NSImage imageNamed: #"Navigation right 16x16 vWhite_tx"]];
[cell setAlternateImage:[NSImage imageNamed: #"Navigation down 16x16 vWhite_tx"]];
}
In your case, you would wrap this up with your class detection logic, and set the cell images appropriately for your cases.
A nice way to change the disclosure image is to use a view based outline view:
In your ViewController with NSOutlineViewDelegate:
- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
CustomNSTableCellView *cell = [outlineView makeViewWithIdentifier:tableColumn.identifier owner:self];
cell.item = item;
return cell;
}
You have to subclass your NSOutlineView and overide the method:
- (id)makeViewWithIdentifier:(NSString *)identifier owner:(id)owner
{
id view = [super makeViewWithIdentifier:identifier owner:owner];
if ([identifier isEqualToString:NSOutlineViewDisclosureButtonKey])
{
// Do your customization
// return disclosure button view
[view setImage:[NSImage imageNamed:#"Disclosure_Categories_Plus"]];
[view setAlternateImage:[NSImage imageNamed:#"Disclosure_Categories_Minus"]];
[view setBordered:NO];
[view setTitle:#""];
return view;
}
return view;
}
//Frame of the disclosure view
- (NSRect)frameOfOutlineCellAtRow:(NSInteger)row
{
NSRect frame = NSMakeRect(4, (row * 22), 19, 19);
return frame;
}
For who looks for Swift2 Solution. Subclass NSRow of your outlineview and override didAddSubview method as below.
override func didAddSubview(subview: NSView) {
super.didAddSubview(subview)
if let sv = subview as? NSButton {
sv.image = NSImage(named:"IconNameForCollapsedState")
sv.alternateImage = NSImage(named:"IconNameForExpandedState")
}
}
You've got the basic idea but what you will need to do is draw the image yourself. Here's the code I use:
- (void)outlineView:(NSOutlineView *)outlineView willDisplayOutlineCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item {
NSString *theImageName;
NSInteger theCellValue = [cell integerValue];
if (theCellValue==1) {
theImageName = #"PMOutlineCellOn";
} else if (theCellValue==0) {
theImageName = #"PMOutlineCellOff";
} else {
theImageName = #"PMOutlineCellMixed";
}
NSImage *theImage = [NSImage imageNamed: theImageName];
NSRect theFrame = [outlineView frameOfOutlineCellAtRow:[outlineView rowForItem: item]];
theFrame.origin.y = theFrame.origin.y +17;
// adjust theFrame here to position your image
[theImage compositeToPoint: theFrame.origin operation:NSCompositeSourceOver];
[cell setImagePosition: NSNoImage];
}
You will need 3 different images as you can see, one for the ON state, one for the OFF state and also one for the MIXED state which should be halfway between the two. The mixed state makes sure you still get the opening and closing animation.
This is what i have tried and working so far,
/* because we are showing our own disclose and expand button */
- (NSRect)frameOfOutlineCellAtRow:(NSInteger)row {
return NSZeroRect;
}
- (NSRect)frameOfCellAtColumn:(NSInteger)column row:(NSInteger)row {
NSRect superFrame = [super frameOfCellAtColumn:column row:row];
if ((column == 0) && ([self isGroupItem:[self itemAtRow:row]])) {
return NSMakeRect(0, superFrame.origin.y, [self bounds].size.width, superFrame.size.height);
}
return superFrame;
}
I have subclassed NSOutlineView class and override these methods,
[self isGroupItem] is to check whether its group or not.
but got one problem, now looks like mousehandling i need to do :( , on double clicking group row is not toggling

Adding a row with transparent background

I have an NSTableView, with an "add" button below it. When I click on the button, a new row gets added to the table and is ready for user input.
The row appears in a white color. Can I set the color of the row to a transparent color? Is this possible? I cannot figure out how to do this.
My code for setting my table to be transparent:
[myTable setBackgroundColor:[NSColor clearColor]];
[[myTable enclosingScrollView] setDrawsBackground: NO];
Code for adding a row:
[myTableArray addObject:#""];
[myTable reloadData];
[myTable editColumn:0 row:[myTableArray count]-1 withEvent:nil select:YES];
try setting the cell's background color transparent
[cell setBackgroundColor:[UIColor clearColor]];
it works for me
I think you may have to do some subclassing to accomplish what you're trying to do.
By subclassing your NSTableView you can override the preparedCellAtColumn:row: method like so:
- (NSCell*) preparedCellAtColumn:(NSInteger)column row:(NSInteger)row {
NSTextFieldCell *edit_field;
edit_field = (NSTextFieldCell*) [super preparedCellAtColumn:column row:row];
if ( [self editedRow] == row && [self editedColumn] == column ) {
[edit_field setBackgroundColor:[NSColor clearColor]];
[edit_field setDrawsBackground:NO];
}
return edit_field;
}
However, the NSTableView documentation indicates that your cell has another method called, which seems to reset the color. (editWithFrame:inView:editor:delegate:event:) Creating a subclass of NSTextViewCell that overrides this method may do what you're looking for.
EDIT
Searching through the documentation I found this:
If the receiver isn’t a text-type NSCell object, no editing is performed. Otherwise, the field editor (textObj) is sized to aRect and its superview is set to controlView, so it exactly covers the receiver.
So what you need to customize in this case is the field editor, which is covering up any display changes you're performing on the NSTableView or the cell.
The field editor is returned by the window delegate's method windowWillReturnFieldEditor:toObject:
This should let you set the properties of the edited cell before returning it to the NSTableView
EDIT
Tried this to no avail but might help out:
-(id) windowWillReturnFieldEditor:(NSWindow *)sender toObject:(id)client{
NSText *editor = [window fieldEditor:YES forObject:client];
[editor setBackgroundColor:[NSColor clearColor]];
[editor setDrawsBackground:NO];
return [editor autorelease];
}

NSOutlineView Drag-n-Drop

In my application, NSOutlineView used as below,
1 -- Using CustomOutlineView because i want to control NSOutlineView Background,
2 -- Using CustomeCell, becuase i need to have customize Cell
Header File : MyListView.h
/*
MyUICustomView is the Subclass from NSView and this is i need to have
for some other my application purpose
*/
#interface MyListView : MyUICustomView<NSOutlineViewDataSource>
{
// MyCustomOutlineview because, i need to override DrawRect Method to have
// customized background
MyCustomOutlineView *pMyOutlineView;
}
#property(nonatomic,retain)MyCustomOutlineView *pMyOutlineView;
and also i should be able to drag-n-drop within Outline view,
for having drag-n-drop i have done following,
-(void)InitOutlineView{
// Creating outline view
NSRect scrollFrame = [self bounds];
NSScrollView* scrollView = [[[NSScrollView alloc] initWithFrame:scrollFrame] autorelease];
[scrollView setBorderType:NSNoBorder];
[scrollView setHasVerticalScroller:YES];
[scrollView setHasHorizontalScroller:NO];
[scrollView setAutohidesScrollers:YES];
[scrollView setDrawsBackground: NO];
NSRect clipViewBounds = [[scrollView contentView] bounds];
pMyOutlineView = [[[MyCustomOutlineView alloc] initWithFrame:clipViewBounds] autorelease];
NSTableColumn* firstColumn = [[[NSTableColumn alloc] initWithIdentifier:#"firstColumn"] autorelease];
#ifdef ENABLE_CUSTOM_CELL
// Becuase cell should have Image, Header Info and brief detail in small font,
// so i need to have custom cell
ImageTextCell *pCell = [[ImageTextCell alloc]init];
[firstColumn setDataCell:pCell];
// SO i can fill the data
[pCell setDataDelegate:self];
# endif
[pMyOutlineView setDataSource:self];
/* This is to tell MyCustomOutlineView to handle the context menu */
[pMyOutlineView setDataDelegate:self];
[scrollView setDocumentView:pCTOutlineView];
[pMyOutlineView addTableColumn:firstColumn];
[pMyOutlineView registerForDraggedTypes:
[NSArray arrayWithObjects:OutlinePrivateTableViewDataType,nil]];
[pMyOutlineView setDraggingSourceOperationMask:NSDragOperationEvery forLocal:YES];**
}
and to Support drag-n-drop Implemented following method
- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard{
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:items];
[pboard declareTypes:[NSArray arrayWithObject:OutlinePrivateTableViewDataType] owner:self];
[pboard setData:data forType:OutlinePrivateTableViewDataType];
return YES;
}
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index{
// Add code here to validate the drop
NSLog(#"validate Drop");
return NSDragOperationEvery;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index{
NSLog(#"validate Drop");
}
but still when i try to drag the row of NSOutlineView nothing is happening, even i tried to debug via NSLog but i couldn't see any log from above function,
Am i missing any important method ?
to support Drag-n-drop
Based on your code, it looks like you're not returning anything for the ...writeItems... method. This method is supposed to return a BOOL (whether the items were successfully written or you're denying the drag). Returning nothing should be giving you a compiler warning ...
HI,
FInally got able to rid of this,
The problem is
[firstColumn setWidth:25];
So drag was working only upto 25 pixel form the left, and i commented this, then its working time, but strange to see, my outline view has only one column , for drawing, its not checking the restriction but for drag-n-drop its checking,
Thanks Joshua

NSSplitView splitter pane change notification

Hello I need to implement four views splitters like in Maya, 3ds max, Blender or other similar modeling tools. I use NSSplitView on Mac side of my editor and I need to know when user drags one pane to sync another pane.
Is there any way to get new size from one NSSplitView and sync another view to it? I have working code for Windows Forms in C# version of this editor, but I can't figure out how to do it on a Mac. Full source code is at http://github.com/filipkunc/opengl-editor-cocoa.
Thanks a lot.
I fixed it with this code:
- (void)splitViewDidResizeSubviews:(NSNotification *)notification
{
NSSplitView *splitView = (NSSplitView *)[notification object];
NSView *topSubview0 = (NSView *)[[topSplit subviews] objectAtIndex:0];
NSView *topSubview1 = (NSView *)[[topSplit subviews] objectAtIndex:1];
NSView *bottomSubview0 = (NSView *)[[bottomSplit subviews] objectAtIndex:0];
NSView *bottomSubview1 = (NSView *)[[bottomSplit subviews] objectAtIndex:1];
if (fabsf([bottomSubview0 frame].size.width - [topSubview0 frame].size.width) >= 1.0f)
{
if (splitView == topSplit)
{
NSLog(#"topSplit");
[bottomSubview0 setFrame:[topSubview0 frame]];
[bottomSubview1 setFrame:[topSubview1 frame]];
}
else
{
NSLog(#"bottomSplit");
[topSubview0 setFrame:[bottomSubview0 frame]];
[topSubview1 setFrame:[bottomSubview1 frame]];
}
}
}