Drawing in ASOC

Hi,

My aim: Drawing in a window a bar chart.

My problem: How to make drawings in a window’s custom view (or on a free place in the window itself)

a) in ASOC
b) in OC but with sending values from applescript to OC-script.

My attempts:

To a)

on drawRect_(sender)
        set theRect to NSBezierPath's bezierPathWithRect_({{x:30,y:30},{height:50,|width|:50}})
        --log theRect -- it logs
        set thePath to NSBezierPath's bezierPath()
        log thePath -- it logs
        thePath's appendBezierPath_(theRect)
        current application's class "NSColor"'s blueColor's |set|()
        
        thePath's fill() -- no rect !!
        --tell current application to beep -- it beeps
        -- displayRect_(theRect)
    end drawrect_

The rectangle was not drawing by a button-click or a handler-call, only by putting it into the ‘applicationWillFinishLaunching’-handler (this makes no sense for me).

To b)
I am not familiar in OC-coding and I tinkered it with helps in some documents:
I made a new class and set the custom view to GraphikView.

#import <Cocoa/Cocoa.h>

@interface GraphikView : NSView


@end
@implementation GraphikView

NSPoint startPoint, endPoint;

int sidestep = 50;
int heightStep = 5;

//just for testing; these values have to be send from AppDelegate.applescript:
int anz6 = 0;
int anz5 = 4;
int anz4 = 6;
int anz3 = 10;
int anz2 = 5;
int anz1 = 3;

- (id)initWithFrame:(NSRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code here.
    }
    
    return self;
}

- (void)drawRect:(NSRect)dirtyRect
{
    // draw coordinates
    startPoint = NSMakePoint(0.0, 10.0);
    endPoint = NSMakePoint(300.0, 10.0);
    [NSBezierPath strokeLineFromPoint: startPoint toPoint: endPoint];
    startPoint = NSMakePoint(4.0, 6.0);
    endPoint = NSMakePoint(4.0, 150.0);
    [NSBezierPath strokeLineFromPoint: startPoint toPoint: endPoint];
       
    // draw columns
     NSBezierPath* thePath = [NSBezierPath bezierPath];
    [[NSColor blueColor] set];
    [thePath appendBezierPathWithRect:NSMakeRect(10.0+0*sidestep, 10.0, 10.0,1.0+anz1*heightStep)];
    [thePath fill];
    [thePath appendBezierPathWithRect:NSMakeRect(10.0+1*sidestep, 10.0, 10.0,1.0+anz2*heightStep)];
    [thePath fill];
    [thePath appendBezierPathWithRect:NSMakeRect(10.0+2*sidestep, 10.0, 10.0,1.0+anz3*heightStep)];
    [thePath fill];
    [thePath appendBezierPathWithRect:NSMakeRect(10.0+3*sidestep, 10.0, 10.0,1.0+anz4*heightStep)];
    [thePath fill];
    [thePath appendBezierPathWithRect:NSMakeRect(10.0+4*sidestep, 10.0, 10.0,1.0+anz5*heightStep)];
    [thePath fill];
    [thePath appendBezierPathWithRect:NSMakeRect(10.0+5*sidestep, 10.0, 10.0,1.0+anz6*heightStep)];
    [thePath fill];

};

@end

This works, but how do I get the values anz1, anz2, … now from the mainscript (they are calculated there) to OC-script?
The values will be updated every 2 seconds by timerDidFire_(theTimer).

Greetings

Heiner

28.7.2012 : Only correction of the orthography

I’m not sure why the ASObjC code isn’t working, but if you’re doing much drawing Objective-C is preferable simply because it can get called so often.

You can pass your values by making them properties, in either object really. For example, if you had a property in the app delegate script:

property anzValues: {1, 2, 3, 4, 5, 6}

You could use:

It would probably be more efficient to get the whole list at once, but you should be able to see the idea.

The other option is to have a property in the Objective-C object, and set that from your script.

Hi Shane!

Problems!

In the last part of the line [[[NSApp delegate] anzValues] objectAtIndex:0]*hightStep)] I get error messages:

  • Instace method ‘-anzValues’ not found (return type defaults to ‘id’
  • invalid operands to binary expression (‘id’ and ‘int’) . (I think the last message is a result of the first one)

What have I done before:

  • I added a new custom view,
  • I build a new file: cocoa OC-class : class-name: GraphikView and subclass of: NSView (with the view selected),
  • I get the .h and .m . files,
  • In Ident.-Insp. I set class to GraphikView too.

The script do you know.

In addition now:

  • in appDel-script set the new property anzValues: {anz1, anz2, … }
  • in applicationWillFinishLaunching I set anz1 to 1, anz2 to 2, etc.

That’s not enough I think. There will be some bindings too ?

Heiner

No, you don’t need any bindings.

I’m not sure why the property is not working. Delete it and add a handler:

on anzValues()
return {1, 2, 3, 4, 5, 6}
end anzValues

Please have a look at .h file. Has to be there a transfer line for anzValues?

No, because the handler is in the AS class.

Sorry!

Maybe I have created a wrong file (class). The grafic - which worked in #1 - is shown immediately after launching, without any triggering in AS-script. So I think it can’t get any influence by AS code. (?)

Sorry, I’m afraid I’m not following…

In the catacombs of my computer I found a AS-partialsolution (don’t know from where):

  1. Add a new AS-file (class); named it to ‘DrawingASClass’
  2. Add in IB a custom view; in IB’s Inspector pane set the class to ‘DrawingASClass’
  3. Add the following code to ‘DrawingASClass’-script:
script DrawingASClass
	property parent : class "NSView"
    
	property NSBezierPath : class "NSBezierPath"
	property NSGraphicsContext : class "NSGraphicsContext"
    
		
	on drawRect_(dirtyRect)
		--if NSGraphicsContext's currentContextDrawingToScreen then -- not necessarily ?!
			--NSGraphicsContext's saveGraphicsState()
			set theRect to {{x:10, y:10}, {height:100, |width|:100}}
			set theNSBezierPath to NSBezierPath's bezierPath
			theNSBezierPath's appendBezierPathWithRect_(theRect)
            current application's class "NSColor"'s blueColor's |set|() 
			--theNSBezierPath's fill()
            theNSBezierPath's stroke()
			--NSGraphicsContext's restoreGraphicsState()
		--end if
	end drawRect_

end script

The rect is drawn to the custom view imediatly after launching (because the property class “NSView” is set to parent?).

As far as well. But it is static - and that’s my problem all the time, I don’t know how to change the rect values from the main script to make the heights of the rects dynamically. All my attempts (including Shan’s recommendations) leds with me to nothing. I found no access to the new class, it seems to be closed. The same thing with ObjC-project.

Are you calling setNeedsDisplay: after changing the values?

To Shane:
Yes, I made some tries (see the following script below)

Now, with a little help, I can send values from one script to another.

But that leads to a strange problem:
The rect is not drawn by setting in the script DrawMyRect the property theHeight as missing value, but only (in my script!) by a fixed value.

script AppDelegate
	--property parent : class "DrawMyRect"
    property parent : class "NSObject"
    property drawingRect: missing value -- bind in IB to drawing area "DrawMyRect" (custom view)
    
    property theHeight: 50 -- just for testing
    
    on sendValue_(sender)
        set rectFrame to drawingRect's frame
        --log rectFrame -- OK
        -- changing the rect's height:
        current application's DrawMyRect's changeTheValue_(theHeight)
        -- draw with new height again:
        current application's DrawMyRect's drawRect_(rectFrame)
    end sendValue__
    
	
	on applicationWillFinishLaunching_(aNotification)
		-- Insert code here to initialize your application before any files are opened 
	end applicationWillFinishLaunching_
	
	on applicationShouldTerminate_(sender)
		-- Insert code here to do any housekeeping before your application quits 
		return current application's NSTerminateNow
	end applicationShouldTerminate_
	
end script
script DrawMyRect
	property parent : class "NSView"
	property NSBezierPath : class "NSBezierPath"
	property NSGraphicsContext : class "NSGraphicsContext"

   property theHeight: missing value
   -- property theHeight: 50 -- works but without changing
    
    on changeTheValue_(theValue)
        --log theValue -- OK
        set theHeight to theValue
    end changeTheValue
    
    
	on drawRect_(dirtyRect)
       -- tell current application to beep
        --log theHeight -- OK
        
        if NSGraphicsContext's currentContextDrawingToScreen then
			NSGraphicsContext's saveGraphicsState()
            
            set theNSBezierPath to NSBezierPath's bezierPath
            -- Coordinates: 
            NSBezierPath's strokeLineFromPoint_toPoint_({10,10},{400,10})
            NSBezierPath's strokeLineFromPoint_toPoint_({15,5},{15,150})
            -- OK they are drawn
            -- Rect:
		set theRect to {{x:40, y:10}, {height: theHeight, |width|:10}} -- theHeight should to be set
		log theRect -- OK rect is changed!
            
            current application's class "NSColor"'s blueColor's |set|() 
            
            theNSBezierPath's appendBezierPathWithRect_(theRect)
			theNSBezierPath's fill()
            NSGraphicsContext's restoreGraphicsState()
            
            -- setNeedsDisplay_(true) -- Error:
            -- +[DrawMyRect drawRect:]: unrecognized function setNeedsDisplay_. (error -10000)
            
            --current application's class "NSView"'s setNeedsDisplay_(true) -- Error:
            -- [DrawMyRect drawRect:]: +[NSView setNeedsDisplay:]: unrecognized selector sent to class 0x7fff7398b410 (error -10000)
		end if
	end drawRect_
end script

Has anyone an idea how to solve the problem?

Heiner

You’re calling those methods there on the class DrawMyRect – they are instance methods, so you need to call them on the instance of your class (drawingRect?).

Well, I changed the scripts as follows and with a little naming corrections for a better understanding.

script AppDelegate
   --property parent : class "DrawMyRect"
    property parent : class "NSObject"
    property theHeight: 50
    
    on sendValue_(sender)
        -- changing the barRect's height:
        current application's DrawMyRect's changeTheValue_(theHeight)
    end sendValue__

script DrawMyRect
	property parent : class "NSView"
	property NSBezierPath : class "NSBezierPath"
	property NSGraphicsContext : class "NSGraphicsContext"
    
    property theHeight: 10
    property frameRect: missing value -- equal to dirtyRect
    
    (* I think it's not necessary
    on initWithFrame_(frame)
		continue initWithFrame_(frame)
		-- Initialization code here.
		return me
	end initWithFrame_
     *)
    
    on changeTheValue_(theValue)
        --log theValue -- OK
        set theHeight to theValue
        -- draw with new height again:
        my drawRect_(frameRect)
    end changeTheValue
    
       
   on drawRect_(dirtyRect)
        -- for reading out the first time and than set above for calling back:
        set frameRect to dirtyRect
        
        -- log theHeight -- OK
        if NSGraphicsContext's currentContextDrawingToScreen then
	   NSGraphicsContext's saveGraphicsState()
           set theNSBezierPath to NSBezierPath's bezierPath
           
            current application's class "NSColor"'s blueColor's |set|()
            -- Coordinates:
            NSBezierPath's strokeLineFromPoint_toPoint_({10,10},{400,10})
            NSBezierPath's strokeLineFromPoint_toPoint_({15,5},{15,150})
            
            -- barRect:
	    set barRect to {{x:40, y:10}, {height: theHeight, |width|:10}} -- theHeight should to be set
	    -- log barRect -- OK, barRect is changed by button call!
           
            set theNewNSBezierPath to NSBezierPath's bezierPathWithRect_(barRect)
            theNewNSBezierPath's appendBezierPathWithRect_(barRect)
	    theNewNSBezierPath's fill()
            
            NSGraphicsContext's restoreGraphicsState()

            --current application's setNeedsDisplay_(true) -- Error:
            -- +[DrawMyRect drawRect:]: unrecognized function setNeedsDisplay_. (error -10000)
            
            --current application's class "NSView"'s setNeedsDisplay_(true) -- Error:
            -- [DrawMyRect drawRect:]: +[NSView setNeedsDisplay:]: unrecognized selector sent to class 0x7fff7398b410 (error -10000)
	end if
   end drawRect_
end script

What happens:

  1. The barRect is drawn after launching with the initial height of 10.
  2. After button click the height is changed to 50 in barRect.
  3. The new barRect is not drawn.

I think the drawing area must be set to redraw ability. And that is - as Shane said before and the documentations -with setNeedsDisplay_(true). But what is to set to this and how? Some attempts leads always to errors.

A view doesn’t redraw itself automatically after changing a value.
You have to call setNeedsDisplay:

So do I think too.

But what is to set to this and how?

Some attempts leads always to errors.

try this


on changeTheValue_(theValue)
--log theValue -- OK
set theHeight to theValue
-- draw with new height again:
my setNeedsDisplay_(true)
end changeTheValue

Note that drawRect: should not be called directly

But how can I change the bar rect’s height from the main script?

Please check this.

¢ Set the class of the custom view to DrawMyRect (in Interface Builder)
¢ In the main script define a property myView with value missing value. Connect the property to the view in Interface Builder
¢ Call

myView's changeTheValue_(theValue)

The first 2 points I did it before.

Then as you said:

script AppDelegate
–property parent : class “DrawMyRect”
property parent : class “NSObject”
property theCustomView: missing value – bind in IB to custom view class “DrawMyRect”
property theHeight: 50

on sendValue_(sender)
    theCustomView's changeTheValue_(theHeight)
end sendValue__

end script

on changeTheValue_(theValue)
set theHeight to theValue
my setNeedsDisplay_(true)
end changeTheValue

The result:

  1. barRect is drawn, height: 10
  2. After button click: barRect disappeared, coordinates are drawn, but the barRect’s height is set to 50

Strange!

It’s not strange, it’s normal.

Coerce the passed Cocoa NSNumber object to integer


set theHeight to (theValue as integer)