gnustep-dev
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Fwd: NSView patch


From: Fred Kiefer
Subject: Re: Fwd: NSView patch
Date: Mon, 02 Mar 2009 18:02:15 +0100
User-agent: Thunderbird 2.0.0.19 (X11/20081227)

Matt Rice wrote:
> On Mon, Feb 23, 2009 at 8:50 AM, Fred Kiefer <address@hidden> wrote:
>> Matt Rice wrote:
>>> On Mon, Feb 23, 2009 at 5:50 AM, Fred Kiefer <address@hidden> wrote:
>>>> The other problem that we sometimes mark subviews as still needing
>>>> display due to rounding errors should be addressed. Here it would be
>>>> helpful to get the detailed values of the involved rectangles. These
>>>> surely are in Matts gdb log, but as he is using a changed version of the
>>>> NSView code it is hard for me to tell, which is which. What would help
>>>> is to have the values of _visibleRect, _invalidRect and aRect at the
>>>> beginning of the method displayRectIgnoringOpacity:inContext:
>>> the superview at the beginning of the method.
>>>
>>> Breakpoint 2, -[NSView displayRectIgnoringOpacity:inContext:]
>>> (self=0x276ff90, _cmd=0xdc4e70, aRect={origin = {x = 0, y = 0}, size =
>>> {width = 476, height = 381}}, context=0x299b740) at NSView.m:2368
>>> 2368      BOOL flush = NO;
>>> $_invalidRect ={origin = {x = 0, y = 0}, size = {width = 1, height = 1}}
>>> $_visibleRect ={origin = {x = 0, y = 0}, size = {width = 476, height = 381}}
>>> $aRect ={origin = {x = 0, y = 0}, size = {width = 476, height = 381}}
>>>
>>> still in the super-view but right before we go to the subview...
>>>
>>> Breakpoint 3, -[NSView displayRectIgnoringOpacity:inContext:]
>>> (self=0x276ff90, _cmd=0xdc4e70, aRect={origin = {x = 0, y = 0}, size =
>>> {width = 476, height = 381}}, context=0x299b740) at NSView.m:2450
>>> 2450                  if (NSIsEmptyRect(isect) == NO)
>>> $_invalidRect ={origin = {x = 0, y = 0}, size = {width = 0, height = 0}}
>>> $_visibleRect ={origin = {x = 0, y = 0}, size = {width = 476, height = 381}}
>>> $aRect ={origin = {x = 0, y = 0}, size = {width = 476, height = 381}}
>>> $isect ={origin = {x = 48.7490425, y = 192.641785}, size = {width =
>>> 118, height = 54}}
>>> $subviewFrame ={origin = {x = 48.7490425, y = 192.641785}, size =
>>> {width = 118, height = 54}}
>>>
>>> right after we go
>>> isect = [subview convertRect: isect fromView: self];
>>>
>>> Breakpoint 4, -[NSView displayRectIgnoringOpacity:inContext:]
>>> (self=0x276ff90, _cmd=0xdc4e70, aRect={origin = {x = 0, y = 0}, size =
>>> {width = 476, height = 381}}, context=0x299b740) at NSView.m:2453
>>> 2453                      [subview displayRectIgnoringOpacity: isect
>>> inContext: context];
>>> $new_isect ={origin = {x = 0, y = 0}, size = {width = 117.999985, height = 
>>> 54}}
>>>
>>>
>>> now here in the view where the error occurs
>>>
>>> Breakpoint 2, -[NSView displayRectIgnoringOpacity:inContext:]
>>> (self=0x284f7e0, _cmd=0xdc4e70, aRect={origin = {x = 0, y = 0}, size =
>>> {width = 117.999985, height = 54}}, context=0x299b740) at
>>> NSView.m:2368
>>> 2368      BOOL flush = NO;
>>> $_invalidRect ={origin = {x = 0, y = 0}, size = {width = 118, height = 54}}
>>> $_visibleRect ={origin = {x = 0, y = 0}, size = {width = 118, height = 54}}
>>> $aRect ={origin = {x = 0, y = 0}, size = {width = 117.999985, height = 54}}
>>>
>>> Breakpoint 1, gsbp () at NSView.m:2362
>>>
>> Thank you for checking all these values. It is really just a small
>> rounding error in the conversion. What about cheating here? For this
>> special case the simplest solution would be to use the bounds rectangle
>> of the sub view as new isect, when the real isect before the conversion
>> is equal to the frame rect of the sub view.
>> I know this will not help us in all cases, but it would quite often save
>> us the computation of the conversion and in this specific case it is
>> also more correct.
>>
>> The code would look somewhat like this, plus plenty of comments
>> explaining why we do this:
>>
>>              isect = NSIntersectionRect(aRect, subviewFrame);
>>              if (NSIsEmptyRect(isect) == NO)
>>                {
>>                  if (NSEqualRects(isect, subviewFrame) == YES)
>>                    isect = [subview bounds];
>>                  else
>>                    isect = [subview convertRect: isect fromView: self];
>>                  [subview displayRectIgnoringOpacity: isect inContext:
>> context];
>>                }
>>
>> The main problem here is that somebody will have to convince me that
>> this is correct in all cases, even with scaling and rotation.
>>
> 
> I don't think that will work because I'm also seeing rounding errors on
> the isect = NSIntersectionRect(...) calls... though not in the stuff i
> sent earlier for some reason... not sure why...
> but that would also only work when displaying whole views,
> and i take it you don't like the idea of just using
> GSAlmostEqualRects() in NSView?
> 
> here's 2 test cases, bar.app is the minimal one and another one that
> allows you to drag the purple view around by clicking on it, and
> resize it by clicking the black square, rotate and scale and all that
> other stuff... (note that scaling doesn't seem to work until its been
> rotated)
> the one thing it doesn't do is allow you to set the origin at
> non-integer increments unless whatever input device your using gives
> non-integer coordinates (i don't appear to have one)
> if its not reproducable I could add something like a NSStepper to
> increment the deal by the value of the value of the top slider or
> something...
> 
> after adding some NSLog's to NSView....
> 
> 2009-02-24 21:34:36.263 bar[10292] subviewFrame
> displayRectIgnoringOpacity:inContext: <BarView: 0x24cbd70>
> 
> 146.749039 205.641785 118.000000 54.000000
> 
> 2009-02-24 21:34:36.263 bar[10292] aRect
> displayRectIgnoringOpacity:inContext: <BarView: 0x24cbd70>
> 
> 0.000000 0.000000 700.000000 500.000000
> 
> 2009-02-24 21:34:36.271 bar[10292] isect before conversion
> displayRectIgnoringOpacity:inContext: <BarView: 0x24cbd70>
> 
> 146.749039 205.641785 117.999985 54.000000
> 
> 
> 2009-02-24 21:34:36.271 bar[10292] isect after conversion
> displayRectIgnoringOpacity:inContext: <BarView: 0x24cbd70>
> 
> 0.000000 0.000000 117.999985 54.000000
> 
> now i could probably work around this by just flooring the points, but
> i am hesitant to work around things in gui for fear they will be
> forgotten,
> and go unnoticed give someone else the joy of rediscovering this..

Attached you find a version of your patch that mostly works on Apple.
What is missing is that displayRectIgnoringOpacity:inContext: gets not
called on redraw. Apple seems to use internal methods there instead.

You application showed a problem in GNUstep. The event loops in your
code did not work, this was caused by a difference in NSApplication
nextEventMatchingMask:...: between GNUstep and Cocoa. We take
distantFuture as the default for a missing limit, Apple uses
distantPast. I already corrected all places in GNUstep gui that call
this method to not rely on the default value. The next step will be to
correct the implementation in NSApplication. But first I wanted to give
the other GNUstepers some time to comment on that change.

On Cocoa the background view always is cleaned up, when the subview
rotates and the scaling has an immediate effect on the line width. (The
difference for the cairo backend I could explain in detail, I just don't
have a better solution for now)

I will play some more with that application.

Fred

#import <AppKit/AppKit.h>

@interface NSView(foo)
- (NSRect) _frameExtend;
@end

@implementation NSView(foo)
- (NSRect) _frameExtend
{
  return [self convertRect: [self bounds] toView: [self superview]];
}
@end



@interface FooView : NSView
{
  NSRect pickerRect;
  float resizeInc;
  NSTextField *tf1;
  NSTextField *tf2;
  NSTextField *tf3;
  NSTextField *tf4;
  NSView *watchView;
}
- (void) subviewFrameChanged:(NSView *)sender;
- (void) watchView:(NSView *)sender;
@end

@implementation FooView
- (float) resizeIncrement
{
  return resizeInc;
}
- (void) watchView:(NSView *)object
{
  [[NSNotificationCenter defaultCenter] addObserver:self 
selector:@selector(frameChanged:) name:NSViewFrameDidChangeNotification 
object:object];
  watchView = object;
  [self subviewFrameChanged:object];
}

- (void) frameChanged:(NSNotification *)notif
{
  [self subviewFrameChanged:[notif object]];
}

- (id) initWithFrame:(NSRect)fr
{
  NSSize tfSize = NSMakeSize(700, 20);
  NSPoint tfPt;

  tfPt.x = fr.origin.x + 2;
  tfPt.y = fr.origin.y + fr.size.height - tfSize.height - 2;


  self = [super initWithFrame:fr];
  tf1 = [[NSTextField alloc] initWithFrame:NSMakeRect(tfPt.x, tfPt.y, 
tfSize.width, tfSize.height)];
  tfPt.y -= tfSize.height + 2;
  tf2 = [[NSTextField alloc] initWithFrame:NSMakeRect(tfPt.x, tfPt.y, 
tfSize.width, tfSize.height)];
  tfPt.y -= tfSize.height + 2;
  tf3 = [[NSTextField alloc] initWithFrame:NSMakeRect(tfPt.x, tfPt.y, 
tfSize.width, tfSize.height)];
  tfPt.y -= tfSize.height + 2;
  tf4 = [[NSTextField alloc] initWithFrame:NSMakeRect(tfPt.x, tfPt.y, 
tfSize.width, tfSize.height)];

  [tf1 setEditable:NO];
  [tf2 setEditable:NO];
  [tf3 setEditable:NO];
  [tf4 setEditable:NO];

  resizeInc = 1.0f;

  [self addSubview:tf1];
  [self addSubview:tf2];
  [self addSubview:tf3];
  [self addSubview:tf4];
  return self;
}

NSString *GoodNSStringFromRect(NSRect r)
{
  return [NSString stringWithFormat:@"x=%f y=%f w=%f h=%f", r.origin.x, 
r.origin.y, r.size.width, r.size.height];
}


- (void) subviewFrameChanged:(NSView *)sender
{
        NSRect frameExtend = [sender _frameExtend];
        pickerRect.origin.x = frameExtend.origin.x + frameExtend.size.width + 2;
        pickerRect.origin.y = frameExtend.origin.y + frameExtend.size.height + 
2;
        pickerRect.size.width = pickerRect.size.height = 18;
        

        [tf1 setStringValue:GoodNSStringFromRect([watchView frame])];
        [tf2 setStringValue:GoodNSStringFromRect([watchView bounds])];
        [tf3 setStringValue:GoodNSStringFromRect([watchView _frameExtend])];

}

- (void) setResizeIncrement:(id)sender
{
  resizeInc = [sender floatValue];
  [tf4 setStringValue:[NSString stringWithFormat:@"%f", resizeInc]];
}

- (void) drawRect:(NSRect)r
{
  NSView *subview = watchView;
        NSRect frameExtend = [subview _frameExtend];
        [[NSColor whiteColor] set];
        NSRectFill([self bounds]);

  [[NSColor blackColor] set];
  NSRectFill(pickerRect);

        [[NSColor blackColor] set];
        NSFrameRect([subview frame]);
        [[NSColor redColor] set];
        NSFrameRect(frameExtend);
}

- (void) mouseDown:(NSEvent *)ev
{
  NSPoint origPt = [self convertPoint:[ev locationInWindow] fromView:nil];

  if (NSPointInRect(origPt, pickerRect))
  {
  while ((ev = [NSApp 
nextEventMatchingMask:NSLeftMouseDraggedMask|NSLeftMouseUpMask 
untilDate:[NSDate distantFuture] inMode:NSEventTrackingRunLoopMode 
dequeue:YES]))
    {
      if ([ev type] == NSLeftMouseUp)
        break;

      {
        NSView *view = watchView;
        NSPoint newPt = [self convertPoint:[ev locationInWindow] fromView:nil];
        NSSize frameSize = [view frame].size;
        
        frameSize.width -= (resizeInc * (origPt.x - newPt.x));
        frameSize.height -= (resizeInc * (origPt.y - newPt.y));
        origPt = newPt;

        [view setFrameSize:frameSize];
        [view setNeedsDisplay:YES];
        [self setNeedsDisplay:YES];
      }
    }
  }
}

@end

@interface BarView : NSView
{
  NSSize scaleSize;
  float rotation;
}
@end

@implementation BarView
- (id) initWithFrame:(NSRect)fr
{
  self = [super initWithFrame:fr];

  scaleSize.width = 1.0f;
  scaleSize.height = 1.0f;
  rotation = 0.0f;
  return self;
}

- (void) displayRectIgnoringOpacity:(NSRect)aRect
inContext:(id)context
{
  [super displayRectIgnoringOpacity:aRect inContext:context];
  NSLog(@"needs display after display %i", [self needsDisplay]);
}


- (void) setScaleHeight:(id)sender
{
  NSSize unit = {1.0, 1.0};
  NSSize newScale = scaleSize;
  [self scaleUnitSquareToSize:[self convertSize:unit fromView:nil]];

  newScale.height = [sender floatValue];
  [self scaleUnitSquareToSize:newScale]; 

  
  [self setNeedsDisplay:YES];
  scaleSize = newScale;
}

- (void) setScaleWidth:(id)sender
{
  NSSize unit = {1.0, 1.0};
  NSSize newScale = scaleSize;
  [self scaleUnitSquareToSize:[self convertSize:unit fromView:nil]];
  newScale.width = [sender floatValue];
  [self scaleUnitSquareToSize:newScale]; 
  [self setNeedsDisplay:YES];
  scaleSize = newScale;
}

- (void) setRotation:(id)sender
{
  [self setFrameRotation:[sender floatValue]];

  [self setNeedsDisplay:YES];
}

/*
 * this isn't strictly needed to reproduce the bug, it just
 * allows one to see that the bug appears and disappears depending on
 * the placement of the view
 */
 
- (void) mouseDown:(NSEvent *)ev
{
  FooView *superView = (FooView *)[self superview];
  NSPoint framePt = [superView convertPoint:[ev locationInWindow] fromView:nil];
  float offsetX = framePt.x - _frame.origin.x;
  float offsetY = framePt.y - _frame.origin.y;

  while ((ev = [NSApp 
nextEventMatchingMask:NSLeftMouseDraggedMask|NSLeftMouseUpMask 
untilDate:[NSDate distantFuture] inMode:NSEventTrackingRunLoopMode 
dequeue:YES]))
    {
      if ([ev type] == NSLeftMouseUp)
        break;


      {
         NSPoint newPt = [superView convertPoint:[ev locationInWindow] 
fromView:nil];

         newPt.x -= offsetX;
         newPt.y -= offsetY;

         if (newPt.x < 1.0) newPt.x = 1.0;
         if (newPt.y < 1.0) newPt.y = 1.0;

         [self setFrameOrigin:newPt];
         framePt = newPt;
         [self autoscroll:ev];

         [superView setNeedsDisplay:YES];
         [self setNeedsDisplay:YES];
      }
    }
}

- (void) drawRect:(NSRect)r
{
  NSRect rect = [self bounds];

  [[NSColor purpleColor] set];
  NSRectFill(r);

  [[NSColor yellowColor] set];
  [NSBezierPath strokeLineFromPoint: NSMakePoint(NSMinX(rect),NSMinY(rect)) 
toPoint: NSMakePoint(NSMaxX(rect),NSMaxY(rect))];

  [[NSColor greenColor] set];
  [NSBezierPath strokeLineFromPoint: NSMakePoint(NSMinX(rect),NSMaxY(rect)) 
toPoint: NSMakePoint(NSMaxX(rect),NSMinY(rect))];

  [[NSColor blueColor] set];
  [NSBezierPath strokeLineFromPoint: NSMakePoint(NSMidX(rect),NSMinY(rect)) 
toPoint: NSMakePoint(NSMidX(rect),NSMaxY(rect))];

  [[NSColor redColor] set];
  [NSBezierPath strokeLineFromPoint: NSMakePoint(NSMinX(rect),NSMidY(rect)) 
toPoint: NSMakePoint(NSMaxX(rect),NSMidY(rect))];
}
@end

@interface Foo : NSObject
{
  BarView *bv;
  FooView *fv;
}
@end

@implementation Foo
- (void) redraw:(id)sender
{
  [fv setNeedsDisplay:YES];
  [bv setNeedsDisplay:YES];
}


- (void) applicationDidFinishLaunching:(id)notif
{
  NSWindow *win = [[NSWindow alloc]
        initWithContentRect:NSMakeRect(0,0,700,500)
        styleMask:NSTitledWindowMask|NSClosableWindowMask|NSResizableWindowMask
        backing:NSBackingStoreBuffered
        defer:NO];
  NSSlider *slider = [[NSSlider alloc] initWithFrame:NSMakeRect(0,0, 700, 16)];
  NSSlider *slider2 = [[NSSlider alloc] initWithFrame:NSMakeRect(0,18, 700, 
16)];
  NSSlider *slider3 = [[NSSlider alloc] initWithFrame:NSMakeRect(0,36, 700, 
16)];
  NSSlider *slider4 = [[NSSlider alloc] initWithFrame:NSMakeRect(0,54, 700, 
16)];

  [slider setTitle:@"scale hidth"];
  [slider2 setTitle:@"scale height"];
  [slider3 setTitle:@"rotation"];
  [slider4 setTitle:@"resize increment"];

 
  fv = [[FooView alloc] initWithFrame:NSMakeRect(0,0,700,500)];
  bv = [[BarView alloc] initWithFrame:NSMakeRect(146.7490425, 205.641785, 118, 
54)];

  [slider setTarget:bv];
  [slider2 setTarget:bv];
  [slider3 setTarget:bv];
 
  [slider4 setTarget:fv];

  [slider setAction:@selector(setScaleWidth:)];
  [slider2 setAction:@selector(setScaleHeight:)];
  [slider3 setAction:@selector(setRotation:)];
  [slider4 setAction:@selector(setResizeIncrement:)];

  [slider setMinValue:0.1];
  [slider2 setMinValue:0.1];
  [slider3 setMinValue:0.0];
  [slider4 setMinValue:0.0];
  

  [slider setMaxValue:100.0];
  [slider2 setMaxValue:100.0];
  [slider3 setMaxValue:360.0];
  [slider4 setMaxValue:1.0];
  [slider setObjectValue:[NSNumber numberWithFloat:1.0f]];
  [slider2 setObjectValue:[NSNumber numberWithFloat:1.0f]];
  [slider3 setObjectValue:[NSNumber numberWithFloat:0.0f]];
  [slider4 setObjectValue:[NSNumber numberWithFloat:1.0f]];
 
  [bv setPostsFrameChangedNotifications:YES];
  [fv addSubview:bv]; 
  [[win contentView] addSubview:fv];
  [fv watchView:bv];

  [[win contentView] addSubview:slider];
  [[win contentView] addSubview:slider2];
  [[win contentView] addSubview:slider3];
  [[win contentView] addSubview:slider4];
  [win makeKeyAndOrderFront:self];                              
}

@end

int main()
{
  NSAutoreleasePool *pool = [NSAutoreleasePool new];
  Foo *foo = [Foo new];
  NSApplication *app = [NSApplication sharedApplication];
  NSMenu *menu = [NSMenu new];
  
  [menu addItemWithTitle:@"quit" action:@selector(terminate:) 
keyEquivalent:@"q"];
  [menu addItemWithTitle:@"redraw:" action:@selector(redraw:) 
keyEquivalent:@"r"];

  [app setMainMenu:menu];
  [app setDelegate:foo];
  [app run];

  [pool release];

  return 0;
}

reply via email to

[Prev in Thread] Current Thread [Next in Thread]