Painting with ApplescriptObjC

Hello,

I have another project, and I need the help of confirmed ASOC programmers to know if this project is possible with Cocoa’s methods.

I want to implement an editable map, given the following parameters:

The map view is a matrix of 64x64 cells of a given size (8x8 pixels).
Each cell retains one information : a type of terrain (the map is flat).
This information is set with a tool : you choose a “type of terrain” from a palette, and click on the cells (like a Photoshop’s pencil on a bitmap) to attribute the type of terrain to the cell.
Once finished with the editing, you click on “Save” to write the map to a file. There is no undoable action.

Full stop.

The first implementation of this project was made with a bitmap. The “cells” were points of this bitmap, so everything (click locations included) had to be multiplied by 8.

I was wondering how to get this done with Cocoa’s powerful interface and methods.

My first idea was to implement a monstrous set of 4096 tiny image wells arranged to form the matrix. :o

My second was to make a image background of a grid and calculate where the click occurred (that was the first implementation) but I didn’t know what to do next. :confused:

Then I went to the idea of a tableView, with cells in row and columns.

It should not be impossible to do this – with minimal memory consumption.

Or am I just being seriously wrong?

Regards,

How do you want it to look? Should it have a grid or something to delineate the different 8x8 pixel areas? And, do you want it to look different when you put something in the cells (different from an empty cell, and also different for each of the different terrains)?

Ric

Ric,

I’m testing the tableView version of the map (cells dimensions seemed to be in pixels but they are actually bigger ”this is not critical as they are just symbols. I just wanted the map to be big enough to be read, and small enough to not fill the screen).

I defined a gray grid, horizontal and vertical, because it certainly helps the user.

An undefined cell is white. It would certainly be better to set the cell color (if possible) instead of storing an image, or even an integer value, for memory saving.

As color represent a type of terrain, I can’t use the color picker but I’ll define a set of possible colors (blue=water, yellow=sand and so on).

What do you think?

Regards,

I think putting colors into the cells is a good idea – good visual feedback but not so busy as text or an image would be. I don’t know about using a table view though – I’ll have to think about that one.

One more question. Once the user “paints” the map, what will he do then? Do you need to get the terrain values back out from the cells if you click on them?

Ric

Yes I have to know each of the values of the map, that’s the problem. A color is maybe a good visual feedback, but it’s not easy to handle ” or maybe colors as properties? there shouldn’t be more than twelve colors (they could even be user-defined colors).

I won’t begin now because it’s late, but I’ll think about it.

Regards,

PS: Right before falling asleep, I discovered the NSMatrix class. Seems promising…

Well, I have to admit that reading the documentation gives me more questions than answers. What is exactly a NSMatrix class? It seems to be an abstract class use to coordinate object instances, the cells.

Is NSMatrix, for this group of cells, the same thing as a NSController for an array, or do I have to add a controller to the matrix?

We must provide a rect, along with a number of rows and columns : does it mean that NSMatrix will determinate the size of the cells by itself?

I decipher that it must be initialized by something like:

property grid : missing value -- any view in IB to give a frame rect.
property gMatrix : missing value -- our matrix.


set my gMatrix to current application’s NSMatrix’s alloc’s initWithFrame_mode_cellClass_numberOfRows_numberOfColumns_(grid’s |frame|(),1,1,8,8)

but of course. nothing happens except errors :confused:

2011-05-04 19:49:27.216 Map[12024:903] -[NSCFNumber allocWithZone:]: unrecognized selector sent to instance 0x20003bd80
2011-05-04 19:49:27.222 Map[12024:903] *** -[MapAppDelegate applicationWillFinishLaunching:]: -[NSCFNumber allocWithZone:]: unrecognized selector sent to instance 0x20003bd80 (error -10000)

what’s wrong here? I gave the method a legal NSRect, and two integers for the rows and the columns. Now, there are two parameters I don’t really understand:

aMode
The tracking mode for the matrix; this can be one of the modes described in NSMatrixMode. This is just a type:

NSRadioModeMatrix = 0,
NSHighlightModeMatrix = 1,
NSListModeMatrix = 2,
NSTrackModeMatrix = 3

I tried to put “NSHighlightModeMatrix”, then I tried 1 as parameter – error

classId
The class to use for any cells that the matrix creates and uses. This can be obtained by sending a class message to the desired subclass of NSCell.

classID of the matrix’s cells is another type:

NSNullCellType = 0,
NSTextCellType = 1,
NSImageCellType = 2

once again, I tried “NSTextCellType”, and then 1 as parameter – error.

I read somewhere that learning chinese language can take a life time. I hope it’s not the same with Cocoa, I have now less than half of my life.

Of course there IS a NSMatrix object in IB, but it’s a radio group – so it’s useless for my needs.

It’s much easier to do it in IB – you don’t have to use the radio group. Just drag out a text field or an image view or whatever you want to be in your matrix, size it how you like and go to Layout —> Embed Objects In —>Matrix. Then in the inspector just set the number of rows and columns. I think the problem is going to be the sizing. You can’t make a text field 8x8. You can with an image view, but it doesn’t seem to have a plain line border, and you would have to put an image, just a square of color into it since you can’t set its background color.

I have another way to do it that uses buttons that I define in code. Since you can only have a background color in a button if it has no border, I also create boxes to surround the button, so you can see it. I then have a series of regular buttons at the bottom of the window labeled, red ,blue, yellow, green, black and orange. These buttons are all connected to the pickColor method, and have their tags set to 0 though 5. The 4096 small buttons all call the same method, “click”, which just tells the button that called it to color in its background with the currently selected color. The window that contains all these buttons was sized to 880 by 870.

script ButtonsViewAppDelegate
	property parent : class "NSObject"
	property x : 30
	property y : 60
	property contentView : missing value --IBOutlet for the window's content view
	property colorList : missing value
	property theColor : missing value
	property c : class "NSColor"
	
	on applicationWillFinishLaunching_(aNotification)
		set colorList to current application's NSArray's arrayWithObjects_(c's redColor(), c's blueColor(), ¬
			c's yellowColor(), c's greenColor(), c's blackColor(), c's orangeColor(), missing value)
		repeat 64 times
			set x to x + 12
			repeat 64 times
				set y to y + 12
				set aButton to current application's NSButton's alloc()'s initWithFrame_({{x, y}, {11, 11}})
				set aBox to current application's NSBox's alloc()'s initWithFrame_({{x, y}, {12, 12}})
				aBox's setBoxType_(4)
				aButton's setBezelStyle_(current application's NSSmallSquareBezelStyle)
				aButton's setTitle_(missing value)
				aButton's setAction_("click:")
				aButton's setTarget_(me)
				aButton's setBordered_(0)
				contentView's addSubview_(aBox)
				contentView's addSubview_(aButton)
			end repeat
			set y to 60
		end repeat
	end applicationWillFinishLaunching_
	
	on pickColor_(sender)
		set theColor to colorList's objectAtIndex_(sender's |tag|())
	end pickColor_
	
	on click_(sender)
		sender's |cell|()'s setBackgroundColor_(theColor)
	end click_
	
end script

As you might expect, this is kind of slow with so many objects.

Ric

Ric,

That is close to my solution 1, the “monstrous set of 4096 tiny image wells” :wink:

I’m fighting with this new little toy. You are right, I cannot make a text field of this size, but when it’s embed in the matrix, it can be any size, even 8x8. And it draws very fast.

Now my problem is: how to select one of this cell? If I make it editable, I get a piece of an insertion point desperately blinking into the cell. I can even enter text! :slight_smile: If I make it selectable, I get nothing.

So what’s the role of my matrix? How to use its methods? They have so tempting names:

gMatrix’s sendAction_to_forAllCells_(“mapCellClicked”,me,1)
gMatrix’s setCellBackgroundColor_(aColor)
and so on. it would be sad not to play with them. But:

How do you get a NSTextCell to refuse editing and change its background color when you click on it? Couldn’t be better to get one notification of a single matrix instead of 4096 possible IBActions ?

If I was able to program in Objective-C, I would subclass NSCell into a MapCell object, but. :confused:

I don’t think these are the ones you should use. I thought I could use selectedRow and selectedColumn, but if you log these when you click on a cell, it’s always one behind, logging the one you clicked on before instead of the current one.

I did find a way that works quite well. The parent class of the script needs to be set to NSMatrix, and the class of the matrix in IB needs to be set to that of your script. I made the matrix with text fields as the cells, and they were set to be selectable but not editable. I also put in a single button connected to the “report” method to list the cells that had been clicked on and turned red.

script TextMatrixAppDelegate
	property parent : class "NSMatrix"
	property theMatrix : missing value --IBOutlet for the matrix
	
	on mouseDown_(theEvent)
		set thePoint to convertPoint_fromView_(theEvent's locationInWindow(), missing value)
		set {ans, aRow, aColumn} to getRow_column_forPoint_(reference, reference, thePoint)
		cellAtRow_column_(aRow, aColumn)'s setBackgroundColor_(current application's NSColor's redColor())
	end mouseDown_
	
	on report_(sender) --IBAction for a button
		repeat with aCell in theMatrix's |cells|()
			if aCell's backgroundColor() is current application's NSColor's redColor() then
				set {ans, redRow, redColumn} to theMatrix's getRow_column_ofCell_(reference, reference, aCell)
				log "There is a red cell at row:" & redRow & "  column:" & redColumn
			end if
		end repeat
	end report_
end script

Ric

What about this:

script MapAppDelegate
    property parent : class "NSObject"
    property gMatrix : missing value
    		
-- method to set the gToolColor here

    on cellMapClick_(sender) -- IBAction for gMatrix
        sender's selectedCell()'s setBackgroundColor_(gToolColor)
    end
end script

Cells are button cells. If I set the matrix’s background color to light gray and the undefined cells background (color = missing value) to white, and the cell spacing (h and v) to 1, I get what I wanted.

That’s a very good way to do it – I had tried putting buttons into the matrix, but I was having trouble with how slow things were in IB. In fact, I did something (I’m not sure what now) that caused IB to hang up and I had to do a force quit to get out of it. Anyway, I tried it again using your method, and now it seems to work fine, though the building the first time is still pretty slow.

Ric

Just for informational purposes, here is the way that you could create the same kind of matrix of buttons that you did in code instead of in IB:

script NullMatrixAppDelegate
	property parent : class "NSObject"
	property mat : missing value --the matrix
	property cView : missing value --IBOutlet for the window's content view
	
	on applicationWillFinishLaunching_(aNotification)
		set myCell to current application's NSButtonCell's alloc()'s init()
		myCell's setTitle_(missing value)
		myCell's setBordered_(0)
		myCell's setBackgroundColor_(current application's NSColor's whiteColor())
		myCell's setBezelStyle_(current application's NSSmallSquareBezelStyle)
		
		set mat to current application's NSMatrix's alloc()'s initWithFrame_mode_prototype_numberOfRows_numberOfColumns_({{30, 30}, {400, 400}}, 1, myCell, 64, 64)
		--mat's setAutosizesCells_(1) --Makes the cells fill the whole frame of the matrix set in the init method
		mat's setCellSize_({8, 8}) --Not needed if you use the method above
		mat's sizeToCells() --Sizes the Matrix to fit the cells.  The size of the matrix in the init method is ignored
		mat's setTarget_(me)
		mat's setAction_("click")
		mat's setBackgroundColor_(current application's NSColor's lightGrayColor())
		mat's setDrawsBackground_(1)
		cView's addSubview_(mat)
	end applicationWillFinishLaunching_
	
	on click()
                     mat's selectedCell()'s setBackgroundColor_(current application's NSColor's redColor())
	end click
	
end script

Ric

Ric,

Thank you very much for providing this hard-coded version – it would make the XIB file lighter and is maybe more easy to maintain. I haven’t tried it for now, and if you have: is there a notable speed difference between the two?

Regards,

I didn’t notice any speed change while running the program, but it was definitely faster to build and compile.

Ric