Log in

No account? Create an account
entries friends calendar About MEEE! Previous Previous
Schadenfreude is my life
...because Misanthropy is FUN
Arachnivistic Velocity

The speed at which an arachnophobe retreats from a spider. Can be expressed as:


Where Av is the speed of the Arachnophobe in Km/sec and Da is the distance of the Arachnophobe from the spider in question. Note that for extremely small values of Da, Av approaches ∞
3 comments or Leave a comment
He was a pussy...i give you true seaborne terror....



1 comment or Leave a comment
I thought those darned illegals were taking away jobs that "Good Americans need and want", that they were denying us paying jobs in hard times.

It would seem, like much of the anti-immigration handwaving, that this line of crap is just that.

Come on you screaming mimi unemployed tea-party fucks! There's a set of bootstraps, go yank your fucking self up by them! The fact they're in a field, paying minimum wage, with no workman's comp shouldn't matter, IT'S A FUCKING JOB, RIGHT?

Once again, the far-right bullshit withers and dies under inconvenient facts.
Leave a comment
When last we left our intrepid hero, he had gotten his application, sans plotting, into a basically functional state. Then he went to Shane Stanley's awesome AppleScriptObjC class, and learned about bindings.

Things got interesting.

So, leaving the hyperbole behind, Bindings are a different way to well, bind UI controls to the code in your application. They can make your life much easier, in that you can get the same task done with less work and, quite often, less code. But, they do require you to think about things differently. So, we'll cover bindings in multiple passes, with this article talking about text fields. Text fields are a good start because the WiFi Analyzer has a fair amount of them, and they're pretty simple to deal with, once you know a couple things.

So, as you recall, the non-bindings way we use text fields is pretty simple. We set up the field name in the application as a property with a value of "missing value":

property currentSSID : missing value

Then in Interface Builder, we create the text field, and do the ctrl-drag thing to connect the text field to the property. We can then use it in the application like so:

currentSSID's setStringValue_(theCurrentInterface's ssid())

This sets value of the text field's string value to the value of theCurrentInterFace's SSID. To get the contents of that string value:

currentSSID as text

Simple enough. So how do we do this with bindings? Well, the difference in the setup is minor. First, you set your property up to be an empty string, not missing value:

property currentSSID : ""

Then, in Interface Builder, you select the text field, and open up the Inspector. In that, you enable bindings, and pick your application delegate, (in this case, Wi Fi Analyzer App Delegate), and in the Model Key Path, you enter the property you're binding the control to. I highly recommend pasting this in, it avoids really annoying problems:

bindings panel

Save this change, and sweet, we're done, right? No dragging, no other changes!

Welllllll...no. That's what I initially thought, and nothing seemed to work. The text field wasn't set, writing the value failed all over the place, it was a mess. The reason why, once some folks on the core AppleScript team explained it is simple. When you use the non-bindings method, then you're working with a pointer to the data. So, things like setStringValue_() work and you have to specify "as text" when you're using it. With bindings, you're working with the actual data itself, not a pointer to the data, so you have to use more 'traditional' AppleScript methods:

set my currentSSID to theCurrentInterface's ssid()



Once you realize this it's pretty simple. Also, I realize that there's not a huge difference in code or convenience between bindings and non-bindings for text fields, at least not in the way we're using them. However, as we'll see in the next iteration of this, when we move to other controls, bindings can simplify our lives rather a lot.
Leave a comment
When we left off last time, we had finished up with creating a brand new file, grabbing data for it, formatting the data into a line of tab-delimited text, and then writing that to the file.

Now, we're going to append data to an existing file, but first, I made some changes to the creating a new file code. Since this is tab-delimited text, it's reasonable to assume that it's going to get used in a spreadsheet application of some kind. Well, you'd be assuming, not me, since that's why I wrote it that way. However, I realized that header rows don't suck, so I created one of those in the new file, so that when you open it in something like Numbers or Excel, you get a nice header row that explains what each row does. I'm not going to put up the entire function again, just the new lines:

     set theTempString to "Time" & " " & "SSID" & " " & "Authmode" & " " & "Channel" &

" " & "Data Rate (Mbps)" & " " & "Signal Strength (dbm)" & " " & "Signal Noise (dbm)" & " " & "MAC of WAP" & "

     --createt the header line for the file

     set theFileString to my NSString's stringWithString_(theTempString)

     --AppleScript string to NSString

     set theFileData to theFileString's dataUsingEncoding_(NSUTF8StringEncoding of current application)

     --NSString to NSData

     theFileHandle's writeData_(theFileData)

     --Write the header to the new file

     set theFileLengthPointer to theFileHandle's seekToEndOfFile()

     --move the file pointer to the end of this string

     --since we KNOW what created this line, we don't have to be paranoid, like we are

     --with appending data

The first few lines are nothing major. We create a string with the column headers ended by a return, and shove that into an NSString. (We could save a line of code and just put the raw string in the stringWithString_() method, but this way is neater to read.

The new stuff is the set theFileLengthPointer to theFileHandle's seekToEndOfFile() line. What this does is move the "start writing data here" pointer to the end of the file, so that the next time we write data to that file, it's going to start in the right place. We use this in the appending function too:

on appendToExistingFile_(sender)
     set theOpenPanel to my NSOpenPanel's openPanel()

     --create an open panel

     theOpenPanel's setCanChooseFiles_(1)

     --the user can choose files

     theOpenPanel's setResolvesAliases_(1)

     --it will resolve aliases to their original file

     theOpenPanel's setCanChooseDirectories_(0)

     --the user cannot chose a directory, it makes no sense in this application's context

     theOpenPanel's setAllowsMultipleSelection_(0)

     --the user cannot choose multiple files, again, not appropriate for the application's context

     set theOpenPanelResult to theOpenPanel's runModal()

     --run the Open panel

     if theOpenPanelResult is 1 then --if the user clicked okay

          my appendToExistingDataButton's setEnabled_(false)

          --disable the button to append to an existing data file

          --we only work on one file at a time.

          my createNewDataButton's setEnabled_(false)

          --disable the button to create a new data file

          --we're appending to a file, letting the user create a new one

          --while this is going on would be dumb

          set theDataFilePath to first item of theOpenPanel's filenames()

          --get the file paths and names, in case we need them

          --this returns an array with a single item, so we use that

          set theDataFileURL to the first item of theOpenPanel's URLs()

          --get the URL to the file, as that's what we should be using

          --since this returns an array/list, we have to get the first item of that list

          set theFileHandle to my NSFileHandle's fileHandleForWritingToURL_error_(theDataFileURL, missing value)

          --create the file handle

          set theFileLengthPointer to theFileHandle's seekToEndOfFile()

          --set the file handle pointer to the current end of the file

          set theFileString to my NSString's stringWithString_("

          --we're going to write a single blank line just to be sure we aren't jamming on the end of the data.

          --We don't know for sure what created this original file, so better an extra blank line than mangled data.

          set theFileData to theFileString's dataUsingEncoding_(NSUTF8StringEncoding of current application)

          theFileHandle's writeData_(theFileData)

          set theFileLengthPointer to theFileHandle's seekToEndOfFile()

          --seek to new end of file

          set createDataFile to false

          --make sure this flag is false

          set appendToExisting to true

          --we need this to be true

          end if

end appendToExistingFile_

This code really works like the new file code with a few exceptions. Obviously we're not using the save panel, since the file already exists, so for this, we use NSOpenPanel instead. We set a few options, like allowing the user to choose files, resolving aliases if one is chosen, and disabling multiple files, and choosing directories, as neither of those make sense in this case. Once that's done, we use the open panel's runModal() to display the panel. If the user picks a file and hits okay, then we disable append and create new file buttons in the App UI, since we don't want another file being used while we're writing to this one.

Like we did with the save panel, we grab both the path and the URL, although unlike the new file code, we don't actually need the path. Also, unlike the save panel, NSOpenPanel returns us an array, or AppleScript list, not a text string. Since we need the text string, we set the file path and url variables to the first item of the array (Since we only allow the user to pick a single item, this assumption is fairly safe.):

set theDataFilePath to first item of theOpenPanel's filenames()
set theDataFileURL to the first item of theOpenPanel's URLs()

Next, we get the file handle for the file at the end of the URL via fileHandleForWritingToURL(), which gives us a file handle for the file we're going to be appending data to. Since we aren't dealing with error handling, we just pass nil or missing value to the method for the error parameter.

Now, we're appending data to the end of an existing file, so we want to make sure we're starting at the end of the file. Like we did in the new code for the new file code, we use seekToEndOfFile() to make sure we're starting at the end of the file. (Yes, as someone used to being far more explicit with such things in AppleScript, the 'trust me, the pointer's in the right place' thing going on here was a little disconcerting, but it obviously works, so...) Next, just because I want to be REALLY SURE that we can clearly tell where the data we're appending starts, (I'm paranoid, sue me), we write a single return to the file, then call seekToEndOfFile() again. So now we're at the end of a file, and we should have a blank line between the new and the old data.

We also make sure the createDataFile flag is false, and the appendToExisting flag is true. Once that's taken care of, we're done with this function, and we move on to the last of the new code for appending files in the loadData() handler.

This code is, as you can see, pretty much like the new file code:

if (appendToExisting is true) and (theSaveFileFlag is true) then
--both have to be true
     set theTempString to (theTime's stringValue() as text) & " " & (currentSSID's stringValue() as text) &

     " " & (authMode's stringValue() as text) & " " & (currentChannel's stringValue() as text) &

     " " & (currentDataRate's stringValue() as text) & " " & (signalStrength's stringValue() as text) &

     " " & (signalNoise's stringValue() as text) & " " & (currentWAPMAC's stringValue() as text) & "

     set theFileString to my NSString's stringWithString_(theTempString)

     --convert the AppleScript string to NSString

     set theFileData to theFileString's dataUsingEncoding_(NSUTF8StringEncoding of current application)

     --NSString to NSData

     theFileHandle's writeData_(theFileData)

     --write us some data

end if

The cleanup code when you're done writing is the same code as the new file code, so we don't have to go over it again.

The next update may be a while, I'm trying to dig up a way to do the signal vs. noise charts that will display directly in the application.
Leave a comment
What AppleScript Programmers have been waiting for, even if they didn't know it: AppleScriptObjC

As some of you might know, AppleScript Studio, (Studio for short), never impressed me. To be blunt, I thought that it was a nice try that quickly showed itself to have gone down a bad path. The problem was that to do anything but a really narrow range of tasks (for Cocoa anyway), you had to use the "call method" command with a gob of 'real' Cocoa code, and if you're going to do that, why bother with Studio? The debugger was essentially unusable, and much of the time, just rebuilding a project with no code changes whatsoever would fail.

I have a real limit as to how much work I'll do to make up for a bad tool, and that's what Studio was...a bad tool with good intentions. Especially because I'd been spoiled by really good AppleScript tools like Script Debugger. Compared to Script Debugger, Studio lived down to its unfortunate acronym.

However, while no one was looking, Apple was listening. With Mac OS X 10.6, they released AppleScriptObjC, which is finally the product that Studio never could be: A real, first-class way to use AppleScript to create Cocoa applications. No more half-baked implementations or "oh, you need call method to do THAT" nonsense. Access to all the frameworks now, and in the future, the same way that Cocoa developers using Objective-C, Ruby, or Python get them...for free.

I'm not going to do a tutorial on AppleScriptObjC, because I'm still wrapping my head around it. If you want a tutorial, there's a good one at MacScripter. What I am going to do is do a light comparison between an AppleScriptObjC application, and the Objective-C version, then one between an AppleScriptObjC application and the Studio version. As you'll see, AppleScriptObjC is a win.

One of the things that AppleScriptObjC does is use AppleScript in a more "Cocoa-y" fashion. That is, Apple made the decision that for AppleScriptObjC, the way you use AppleScript should match the way you'd use other languages. There are obviously going to be significant differences between Objective-C, Ruby, Python, and AppleScript, but in general, Apple tried to make using AppleScriptObjC 'feel' as close to using any other language as possible. (There are more details on this in the AppleScriptObjC release notes I linked to.)


DotView is a simple application that draws a solid circle, or dot, in a window. You can move the dot around with the mouse, there's a slider to change the dot size, and a color control to change the dot's color. It's a simple application, and so is a good way to show the similarities between Objective-C in Cocoa and AppleScript in AppleScriptObjC.

DotView window


Looking at the Objective-C version, there's not a lot of code. There's two .m files, DotView.m and main.m, and two header files, DotView.h and Preview.h. We'll focus on just the DotView.* files here, starting with the Objective-C version of DotView.h

<#import Cocoa/Cocoa.h>

@interface DotView : NSView {
     NSPoint center;
     NSColor *color;
     CGFloat radius;

// Standard view create/free methods
- (id)initWithFrame:(NSRect)frame;
- (void)dealloc;

// Drawing
- (void)drawRect:(NSRect)rect;
- (BOOL)isOpaque;

// Event handling
- (void)mouseUp:(NSEvent *)event;

// Custom methods for actions this view implements
- (IBAction)setRadius:(id)sender;
- (IBAction)setColor:(id)sender;


Not much really, about 14 lines of code that does the setup for things like setting the center, color, and radius of the dot, the functions for drawing the dot, and the functions for resizing and changing the color of the dot.

As far as the AppleScriptObjC version goes, there isn't one. AppleScript is a higher level language, and doesn't use header files. The AppleScriptObjC version still has to deal with the items that are in the Objective-C header file, but it will do so differently. (note that in this usage, "higher" is not a sign of superiority or inferiority. Instead, it denotes how "far from the hardware" a language lives. So AppleScript is a higher level language than Objective-C which is a higher level language than Assembly.)

What about the 'real' code? Well, here's the Objective-C DotView.m contents:

#import <Cocoa/Cocoa.h>
#import "DotView.h"

@implementation DotView

- (id)initWithFrame:(NSRect)frame {
     [super initWithFrame:frame];
     center.x = 50.0;
     center.y = 50.0;
     radius = 10.0;
     color = [[NSColor redColor] retain];
     return self;

- (void)dealloc {
     [color release];
     [super dealloc];
// drawRect: should be overridden in subclassers of NSView to do necessary
// drawing in order to recreate the the look of the view. It will be called
// to draw the whole view or parts of it (pay attention the rect argument);
// it will also be called during printing if your app is set up to print.
// In DotView we first clear the view to white, then draw the dot at its
// current location and size.

- (void)drawRect:(NSRect)rect {
     NSRect dotRect;

     [[NSColor whiteColor] set];
     NSRectFill([self bounds]);
// Equiv to [[NSBezierPath bezierPathWithRect:[self bounds]] fill]

     dotRect.origin.x = center.x - radius;
     dotRect.origin.y = center.y - radius;
     dotRect.size.width = 2 * radius;
     dotRect.size.height = 2 * radius;

     [color set];
     [[NSBezierPath bezierPathWithOvalInRect:dotRect] fill];

- (BOOL)isOpaque {
     return YES;

// Recommended way to handle events is to override NSResponder (superclass
// of NSView) methods in the NSView subclass. One such method is mouseUp:.
// These methods get the event as the argument. The event has the mouse
// location in window coordinates; use convertPoint:fromView: (with "nil"
// as the view argument) to convert this point to local view coordinates.
// Note that once we get the new center, we call setNeedsDisplay:YES to
// mark that the view needs to be redisplayed (which is done automatically
// by the AppKit).

- (void)mouseUp:(NSEvent *)event {
     NSPoint eventLocation = [event locationInWindow];
     center = [self convertPoint:eventLocation fromView:nil];
     [self setNeedsDisplay:YES];

// setRadius: is an action method which lets you change the radius of the dot.
// We assume the sender is a control capable of returning a floating point
// number; so we ask for it's value, and mark the view as needing to be
// redisplayed. A possible optimization is to check to see if the old and
// new value is the same, and not do anything if so.

- (void)setRadius:(id)sender {
     radius = [sender doubleValue];
     [self setNeedsDisplay:YES];

// setColor: is an action method which lets you change the color of the dot.
// We assume the sender is a control capable of returning a color (NSColorWell
// can do this). We get the value, release the previous color, and mark the
// view as needing to be redisplayed. A possible optimization is to check to
// see if the old and new value is the same, and not do anything if so.

- (void)setColor:(id)sender {
     [color autorelease];
     color = [[sender color] retain];
     [self setNeedsDisplay:YES];


I left some of the functional comments in, so it's easier to see what the different parts of DotView.m are doing, but if you remove the comments and blank lines, the entire 'main' part of the application is only 47 lines of code. Admittedly, it doesn't do a lot, but still, that's not a lot. What about the AppleScriptObjC version? Here:

property NSColor : class "NSColor"
property NSBezierPath : class "NSBezierPath"

script DotView
     property parent : class "NSView"

     property |center| : {x:0.0, y:0.0}
     property radius : 0.0
     property |color| : missing value

     on initWithFrame_(frame)
          continue initWithFrame_(frame)
          set my |center|'s x to 50.0
          set my |center|'s y to 50.0
          set my radius to 10.0
          set my |color| to NSColor's redColor()
          return me
     end initWithFrame_

     on drawRect_(rect)
          NSColor's whiteColor()'s |set|()
          tell NSBezierPath's bezierPathWithRect_(my |bounds|()) to fill()

          set origin to {(my |center|'s x) - (my radius), (my |center|'s y) - (my radius)}
          set |size| to {2 * (my radius), 2 * (my radius)}

          my |color|'s |set|()
          tell NSBezierPath's bezierPathWithOvalInRect_({origin, |size|}) to fill()
     end drawRect_

     on isOpaque()
          return true
     end isOpaque

     on mouseUp_(|event|)
          set eventLocation to |event|'s locationInWindow()
          set my |center| to my convertPoint_fromView_(eventLocation, missing value)
          tell me to setNeedsDisplay_(true)
     end mouseUp_

     on setRadius_(sender)
          set radius to sender's doubleValue()
          tell me to setNeedsDisplay_(true)
     end setRadius_

     on setColor_(sender)
          set my |color| to sender's |color|()
          tell me to setNeedsDisplay_(true)
     end setColor_

end script

This does the same thing as the Objective-C version, but in about 40 lines of code. If that doesn't make any sense, this is where AppleScript being a higher level language has its advantages. Because the AppleScript runtime, (the component that executes all AppleScript code), handles things like memory management for you, you don't have to write code to deal with de-allocating memory you allocated earlier. However, there's a price to be paid for this convenience, and that's usually in size, (all things being equal, an application written in an interpreted language like AppleScript tends to be larger, and need more memory than an application written in a compiled language like Objective-C), and speed, (interpreted languages tend to be slower than compiled ones). But, for code that really doesn't care about either, the difference is a wash, and is up to programmer preferences/job requirements.

The thing I wanted you to initially see, is that unlike Studio, which as we'll see, has a syntax implementation that "verbose" doesn't even begin to cover, AppleScriptObjC lets you get work done without having to bang out gobs of code for even simple things. The next thing we want to look at is the way that AppleScriptObjC and Objective-C have similar structure, even though the languages themselves are quite different. For example, let's look at setting up the variables for drawing the dot. To draw a solid colored circle, you need three basic bits of information:
  • You need the center of the circle, so you know where to start

  • You need the radius of the circle, so you know how big the circle will be

  • You need the color of the circle, so you know what color to use to draw and fill in the circle.

In the Objective-C version, this is done in the header, (.h) file with this code:

@interface DotView : NSView {
     NSPoint center;
     NSColor *color;
     CGFloat radius;

The code is using features of the NSView class to create three things:
  • An NSPoint variable, center

  • An NS color variable, *color

  • A CGFloat variable, radius

Those will be used in the DotView main code to tell the application what the center, color, and radius of the circle should be so it can be drawn correctly. Now, the AppleScriptObjC version:

property parent : class "NSView"

property |center| : {x:0.0, y:0.0}
property radius : 0.0
property |color| : missing value

The syntax is different, but still similar. We tell the script to use the features of NSView to create the same three variables: center, radius, and color. With AppleScriptObjC, we also have to set the initial values for the variables, whereas we didn't in Objective-C, but that's a syntax difference. The reason that AppleScriptObjC's center and radius variable names have vertical bars around them is because those particular words are normally reserved by AppleScript. The vertical bars tell the AppleScript runtime "Hey, for this application, use these variables the way I'm defining them here, not the way you normally would use them." In talking with some of the AppleScript team at Apple, they told me that the bars themselves are harmless. If you accidentally put them in where they aren't needed, no harm no foul, it shouldn't affect anything adversely. The reason that color is defined to be a missing variable is because its value will be set by a UI control, and so this is how AppleScriptObjC lets you reserve variables that you're going to "hook up" to the UI in your application.

The important point is, if you were to have never coded in anything but Objective-C in your life, and suddenly had to look at the AppleScriptObjC version of some Objective-C code, you'd have a far easier time of correctly interpreting what the code was doing than you'd ever have with Studio. Here, let's look at one more example. This time, we'll look at the code that creates the initial dot on application launch. First, the Objective-C code:

- (id)initWithFrame:(NSRect)frame {
     [super initWithFrame:frame];
     center.x = 50.0;
     center.y = 50.0;
     radius = 10.0;
     color = [[NSColor redColor] retain];
     return self;

Now the AppleScriptObjC code:

on initWithFrame_(frame)
     continue initWithFrame_(frame)
     set my |center|'s x to 50.0
     set my |center|'s y to 50.0
     set my radius to 10.0
     set my |color| to NSColor's redColor()
     return me
end initWithFrame_

The AppleScriptObjC code's a bit more verbose, but still, the similarities are undeniable. Thanks to the work the AppleScript team did to make AppleScript syntax work the way 'normal' Cocoa application syntax works, it is much, much easier to move between Objective-C and AppleScriptObjC's 'flavor' of AppleScript than it ever was to move between Objective-C and Studio's 'flavor' of AppleScript.

The advantages to this aren't just in more efficient code, or fewer lines. One big advantage to this similarity is that an AppleScriptObjC programmer can read the 'normal' Cocoa documentation far easier than a Studio programmer can, because the way you use the language now follows the 'correct' Cocoa methods more closely. So rather than having to recreate the entire Cocoa documentation set for AppleScriptObjC, the AppleScript team can create a smaller set of core AppleScriptObjC documentation to help you get started, and then you can use the normal Cocoa docs for everything else. That's a huge advantage.

But what about AppleScriptObjC and Studio? How does AppleScriptObjC compare to Studio? Quite favorably as it turns out. By 'quite favorably' I mean "leaves Studio in the dust, choking and wondering why those durn kids knocked its walker over". First, with Studio, you only had access to the parts of Cocoa that Studio explicitly knew about. If Apple introduced a new framework, you couldn't just use that in Studio, you had to wait for the Studio team to implement it. With AppleScriptObjC, there's none of that. AppleScriptObjC is able to use new frameworks and features as soon as they show up in the OS. Now, you could work around that limitation in Studio via the infamous "call method" which let you shell out to 'real' Cocoa code in the Studio application. It turns out, you did that a lot in a Studio application, to where a lot of people just gave in and learned Objective-C.

Task List

So from a feature standpoint, it's not even close. AppleScriptObjC wins easily over Studio. What about a code comparison? Unfortunately, I couldn't find a Studio version of DotView, but I did find another simple application that has both Studio and AppleScriptObjC versions: Task List. Task List is just what it sounds like: a simple application that lets you create and manage a list of tasks/to-dos

Task list
Task List

A nice simple application, so the comparison, as with DotView, will be simple. So, first, the AppleScriptObjC version:

script Task_ListAppDelegate
     property tableData : {}
     property removedTableData : {}
     property tableDataController : missing value
     property CalCalendarStore : class "CalCalendarStore"
     property NSMutableArray : class "NSMutableArray"
     property CalTaskClass : class "CalTask"

     on awakeFromNib()
          set removedTableData to NSMutableArray's array()
          set tableData to NSMutableArray's array()
     end awakeFromNib

     on applicationWillTerminate_(application)
     end applicationWillTerminate_

     on applicationWillResignActive_(application)
     end applicationWillResignActive_

     on applicationWillBecomeActive_(application)
     end applicationWillBecomeActive_

     on addTask_(sender)
          tableDataController's addObject_({priority:"3", task:"", status:"Not Started", calTask:missing value})
     end addTask_

     on removeTask_(sender)
          set deleted_objects to tableDataController's selectedObjects
          set deleted_object to item 1 of deleted_objects
          removedTableData's addObject_(deleted_object)
          tableDataController's remove_(missing value)
     end removeTask_

     on syncTaskList()
          set calendarStore to CalCalendarStore's defaultCalendarStore
          set theCalendars to calendarStore's calendars
          set todoPredicate to CalCalendarStore's taskPredicateWithCalendars_(theCalendars)
          set tasksInCalStore to CalCalendarStore's defaultCalendarStore's tasksWithPredicate_(todoPredicate)
          set tasksInTable to tableData's valueForKey_("calTask")
          set tasksToDelete to removedTableData's valueForKey_("calTask")

          set tasksAdded to NSMutableArray's array()

          -- Get tasks from iCal that aren't in the table, and delete tasks from iCal that we've been asked to kill
          repeat with aTask in tasksInCalStore
          if not (tasksInTable's containsObject_(aTask) as boolean) and not (tasksToDelete's containsObject_(aTask) as boolean) then
               -- Add a new task if it's not in the list of showing or deleted taks
               set priority to aTask's priority as string
               set |title| to aTask's |title| as string
               tableDataController's addObject_({priority:priority, task:|title|, |status|:"Not started", calTask:aTask})
               tell tasksAdded to addObject_(aTask)
          else if tasksToDelete's containsObject_(aTask) as boolean then
               -- Delete task we were asked to kill
               set returnValue to calendarStore's removeTask_error_(aTask, reference)
               if not item 1 of returnValue then
                    set err to item 2 of returnValue
                    error (err's localizedDescription())
               end if
          end if
     end repeat

     -- Update and create new calTasks based on table data
     repeat with tableDataItem in tableData
          set calTable to tableDataItem as record
               set aCalTask to calTable's calTask
          on error
               set aCalTask to missing value
          end try

          if aCalTask is not equal to missing value and not (tasksInCalStore's containsObject_(aCalTask) as boolean) and not tasksAdded's containsObject_(aCalTask) as boolean then
               -- Delete this task, which was deleted in iCal
               tell tableDataController to removeObject_(tableDataItem)
               exit repeat
          else if aCalTask is equal to missing value then
               -- Create new CalTask for a newly created table row
               set aCalTask to CalTaskClass's task()
               set aCalTask's calendar to (get first item of calendarStore's calendars)
               set the calTask of tableDataItem to aCalTask
          end if

          -- Save out both the existing tasks and freshly created tasks
          if aCalTask is not equal to missing value then
               set the |title| of aCalTask to tableDataItem's task
               set the priority of aCalTask to (tableDataItem's priority's integerValue)
               set returnValue to calendarStore's saveTask_error_(aCalTask, missing value)
               end if
          end repeat
     end syncTaskList
end script

That's not bad, about 73 lines of code, if you remove non-code lines. What about the Studio version? I'm not pasting it in here, because it's about 185 lines of code or so to do the same thing. Well, less, as we'll see in a bit. So let's look at some common code here, the "awake from nib" function, which is analogous to the application launch. The AppleScriptObjC version:

on awakeFromNib()
     set removedTableData to NSMutableArray's array()
     set tableData to NSMutableArray's array()
end awakeFromNib

That's pretty simple. Set a couple variables to arrays, and run something called syncTaskList. The Studio version:

on awake from nib theObject
     if name of theObject is "tasks" then
          -- Create the data source for our "tasks" table view
          set theDataSource to make new data source at end of data sources with properties {name:"tasks"}

          -- Create the data columns, "priority", "task" and "status". We also set the sort properties of each of the data columns, including the sort order, the type of data in each column and what type of sensitivity to use.
          make new data column at end of data columns of theDataSource with properties {name:"priority", sort order:ascending, sort type:numerical, sort case sensitivity:case sensitive}
          make new data column at end of data columns of theDataSource with properties {name:"task", sort order:ascending, sort type:alphabetical, sort case sensitivity:case sensitive}
          make new data column at end of data columns of theDataSource with properties {name:"status", sort order:ascending, sort type:alphabetical, sort case sensitivity:case sensitive}

          -- Set the data source as sorted
          set sorted of theDataSource to true

          -- Set the "priority" data column as the sort column
          set sort column of theDataSource to data column "priority" of theDataSource

          -- Finally, assign the data source of the table view to our data source
          set data source of theObject to theDataSource
     end if
end awake from nib

Oof. Even if I were to take the comments out, we can still see that the Studio version is a lot bigger, and seems to require you to do a lot more work for the initial application setup. That's because it does require more work for initial application setup. Even though you use Interface Builder to build the UI elements for both AppleScriptObjC and Studio, AppleScriptObjC is able to use things the way a 'real' Cocoa application does. So unlike Studio which makes you create manually create the AppleScript implementation of the data columns that will be used by the UI, and set all the properties of those data columns, AppleScriptObjC is able to use the information that you already put into Interface Builder, and that Interface Builder provides for free. The idea behind this is trick called "bindings". I'm not going to even try to explain bindings here, but they're what allow AppleScriptObjC to not have to need all the code that Studio needs. It's not that the UI in Studio doesn't have all the bindings info available to it, it's that bindings are invisible to Studio, and so with Studio, you're stuck using the older Data Source API. So, in a sense, even though you've built the UI in Interface Builder, you have to do a lot of redundant work in Studio to manually define what the UI elements in Task List are doing, work you don't have to do in AppleScriptObjC.

That's kind of the idea with Interface Builder, by the way. You create the UI elements, set them up, and then the code is able to just use them.

While Studio's use of AppleScript is more 'traditional', it's also far kludgier. This is repeated over and over throughout the code. Stuff that AppleScriptObjC can do in a few lines, Studio takes a book. Stuff that AppleScriptObjC doesn't even need code for, Studio needs lines and lines and lines.

Oh, and the syntax is so far removed from 'normal' Cocoa syntax, even allowing for specific language differences that there's almost no way to apply standard Cocoa documentation and sample code to Studio. The gulf between them is just too wide.

However, I do feel that I should come clean about something. When I said that AppleScriptObjC took about 73 lines of code to do the same thing that Studio needs almost 200 lines of code to do, I was lying. Blatantly lying. It really only needed 24 lines of code to do what Studio did. The other 49 or so lines of code let you integrate Task List into the iCal store, so you can see your iCal tasks in Task List, and have the tasks you add to Task List show up in iCal. I don't even want to think about what it would take to do that with Studio, assuming you even could without having to use "call method" and shell out to 'real' Cocoa code. However, AppleScriptObjC does lose to Studio in one important way: AppleScriptObjC and AppleScriptObjC applications will only run on Mac OS X 10.6 or later. If you want to create applications using AppleScript for Mac OS X 10.5 or earlier, you cannot use AppleScriptObjC.

AppleScriptObjC is a huge change, and it does bring with it a lot of new things you'll need to learn, and unlearn, especially if you are using Studio. But, it is also a huge leap forward in capability and features for AppleScript. It also shows, better than any amount of platitudes could, that AppleScript is not going away anytime soon. I just cannot see Apple doing this much work, and creating such a monstrously huge improvement to AppleScript just to knife it.
Leave a comment
(Just for those who care: no, i'm not an experienced Cocoa or ObjC programmer. It should have been obvious if you knew enough to ask, but just to clear it up.)

This post, we're going to take the initial save file setup we had from last week, and expand that to actually writing data to a new file. Doing this is going to require splitting up the overall writing code across three handlers in my application. In this case, we have made some minor changes to the code that executes after the user clicks "Okay" in the save panel dialog:

if theSavePanelResult is 1 then
     --if they clicked the 'save' button, then we want to get the path and set some flags
     set theDataFileURL to theSavePanel's |URL|()
     --get the encoded URL, which is not the file path, but we'll need it
     set theDataFilePath to theSavePanel's filename()
     --get the posix path to the file
     set createDataFile to true
     --we're creating a new data file, so this has to be true
     set appendToExisting to false
     --we are not appending data, so set to false
     set theFileManager to my NSFileManager's defaultManager()
     --create a file manager object so that we can create a blank file
     set theCreateFileResults to theFileManager's createFileAtPath_contents_attributes_(theDataFilePath, missing value, missing value)
     --creates a blank file at the path specified. Do NOT use "my theFileManager's..." because errors will happen
     set theFileHandle to my NSFileHandle's fileHandleForWritingToURL_error_(theDataFileURL, missing value)
     --use this to write to the file URL

end if

So as you look at it, you can see that all we did was make the file handle setup the last line. Note that I've set up theFileHandle as a property, because it's the lazy way to avoid scope issues. So we clicked our button to create a new file, we gave it a name, and a location, and we have a file path to get data into it. How do we build the data? Well, first, we have to define what that data is. In this application's case, it's series of lines of tab-delimited text. Each item is a bit of info about the current WiFi network, along with a timestamp for when the data was read. This can be entered as part of the track functionality, writing one line of data per second, or when you hit the refresh button, to write the data manually. The file handle is closed when you either turn off the track function, or deselect the save to file checkbox in the application UI. So let's look at building the data string.

Since I have a nice handler to grab the wifi data and populate the UI with the manual refresh or with the track button, we'll add some code to that. Here's the entire loadData handler:

on loadData(theCurrentInterface)
     currentSSID's setStringValue_(theCurrentInterface's ssid())
     --set the contents of SSID field to the current SSID
     set theCurrentAuthMode to (theCurrentInterface's securityMode() as text)
     --it's an NSNumber, will deal with it later. Forcing to text works for now
     if theCurrentAuthMode is "0" then
     --mode number to mode name

          authMode's setStringValue_("Open")

     else if theCurrentAuthMode is "1" then

          authMode's setStringValue_("WEP")

     else if theCurrentAuthMode is "2" then

          authMode's setStringValue_("WPA1 Personal")

     else if theCurrentAuthMode is "3" then

          authMode's setStringValue_("WPA2 Personal")

     else if theCurrentAuthMode is "4" then

          authMode's setStringValue_("WPA1 Enterprise")

     else if theCurrentAuthMode is "5" then

          authMode's setStringValue_("WPA2 Enterprise")

     else if theCurrentAuthMode is "6" then

          authMode's setStringValue_("WiFi Protected Setup")

     else if theCurrentAuthMode is "7" then

          authMode's setStringValue_("Dynamic Wep 802.1X")


          authMode's setStringValue_("Unknown/Invalid")

     end if

     currentChannel's setStringValue_(theCurrentInterface's channel())
     --set the channel
     currentDataRate's setStringValue_(theCurrentInterface's txRate())
     --set the data rate in Mbps
     signalStrength's setStringValue_(theCurrentInterface's rssi())
     --set the signal strength in dbm
     signalNoise's setStringValue_(theCurrentInterface's noise())
     --set the signal noise in dbm
     currentWAPMAC's setStringValue_(theCurrentInterface's bssid())
     --set the MAC of the base station
     theTime's setStringValue_((time string of (current date)))
     --let's add the code to save to a new file
     if (createDataFile is true) and (theSaveFileFlag is true) then

          set theTempString to (theTime's stringValue() as text) & " "
          & (currentSSID's stringValue() as text) & " "
          & (authMode's stringValue() as text) & " "
          & (currentChannel's stringValue() as text) & " "
          & (currentDataRate's stringValue() as text) & " "
          & (signalStrength's stringValue() as text) & " "
          & (signalNoise's stringValue() as text) & " "
          & (currentWAPMAC's stringValue() as text) & "
          set theFileString to my NSString's stringWithString_(theTempString)
          set theFileData to theFileString's dataUsingEncoding_(NSUTF8StringEncoding of current application)
          theFileHandle's writeData_(theFileData)
          --you could probably also use fileHandleForWritingToPath here, but since URLs are the way of the future
          --we should use them where we can
          --we stash the close function where it's going to be actually used

     end if

end loadData

There's really not much going on here. This handler, (and you can tell it's a 'normal' AppleScript handler because it doesn't have an underscore in the name), is passed a WiFi interface object, theCurrentInterface. It then pulls data from that to set various text fields in the Application UI. For example, we grab the SSID of the current WiFi network via theCurrentInterface's ssid(), and use that to set the contents of that text field in the UI via currentSSID's setStringValue_(theCurrentInterface's ssid())

Since the enumeration for the authentication mode can be one of 9 values, including "other", we have a series of if then else statements to handle that. To get the time string we use for theTime, instead of using the Cocoa technique, and NSDate/NSDateComponents, we use a more traditional AppleScript way: time String of (current date). It works just as well as any other method for our needs, and is simpler to code. adding that to the UI is then a single line: theTime's setStringValue_((time string of (current date)))

So now, we have all the information we need to build our string that we want to write to file. To do that, we again, combine Cocoa and traditional AppleScript to build a tab-delimited line with a trailing return:

set theTempString to (theTime's stringValue() as text) & " " & (currentSSID's stringValue() as text) & " " & (authMode's stringValue() as text) & " " & (currentChannel's stringValue() as text) & " " & (currentDataRate's stringValue() as text) & " " & (signalStrength's stringValue() as text) & " " & (signalNoise's stringValue() as text) & " " & (currentWAPMAC's stringValue() as text) & "

The blank spaces in between the quotes are the presentation of the tab formatter, \t. The odd quotation mark by itself is the presentation of the return formatter, \r.

So now we spend three statements on converting theTempString to an NSData object and writing that to a file. First, we create the NSString object: set theFileString to my NSString's stringWithString_(theTempString). Pretty clear, we use the stringWithString function, passing it the temp string. Next, we encode theFileString and convert it to an NSData Object:

set theFileData to theFileString's dataUsingEncoding_(NSUTF8StringEncoding of current application)

We're using UTF8 encoding, because that's the better way to do things, but there are a variety of encodings available, including MacRoman, etc. One thing to watch here is that you have to set the encoding as "<encoding method> of current application", or it fails miserably. Finally, we write that NSData object to the file we created via the file handler:

theFileHandle's writeData_(theFileData)

Every time this handler is called, as long as createFileData and theSaveFileFlag are both true, we get a line of data written.

So what happens when we're done writing? Well, it wouldn't make a lot of sense to put that code here, so we put it in the functions that we use when we're done tracking data, or when we deselect the save file checkbox. First, when we stop tracking:

on timerFired_(thetimer) --this handler runs the actual code for the timer
     if trackButtonSTate is 1 then --'on'

          loadData(theCurrentInterface) of me --grab stats once per second

     else if trackButtonSTate is 0 then --"off"

          thetimer's invalidate() --kill the timer
          theFileHandle's closeFile() --close the file handle we've been writing to
          set theSaveFileFlag to false --kill the save file flag
          set createDataFile to false --kill the new file flag
          set appendToExisting to false --kill the append file flag
          saveToFileCheckBox's setIntValue_(0) --disable the checkbox
          my createNewDataButton's setEnabled_(false) --disable the button to create a new data file
          my appendToExistingDataButton's setEnabled_(false) --disable the button to append to an existing data file

     end if

end timerFired_

We put the file handle cleanup code in the same block as the timer cleanup code. When you disable tracking, it's going to run the code to kill the timer that's controlling how fast the tracking is happening anyway, so it makes sense to put it here. It's only one line to shut down the file handle:

theFileHandle's closeFile()

That's it, the file handle is closed, file writing is done. The rest is just cleaning up properties and resetting controls to give a better indication to the user that they're no longer recording data to a file.

The same basic thing happens if they disable the save to file checkbox:

on saveToFile_(sender)
--when you click the "Save to file" checkbox this ONLY controls the button states, not what the buttons do
     if sender's intValue() is 1 then --if you're checking the checkbox

          set theSaveFileFlag to true
          my createNewDataButton's setEnabled_(true) --enable the button to create a new data file
          --the "my" is critical to having the now enabled button be able to send events
          --without "my", the button knows it's enabled, nothing else does
          my appendToExistingDataButton's setEnabled_(true) --enable the button to append to an existing data file

     else if sender's intValue() is 0 then --if you're de-checking the checkbox

          theFileHandle's closeFile() --close the file handle we've been writing to
          set theSaveFileFlag to false --kill this file flag
          set createDataFile to false --kill the new file flag
          set appendToExisting to false --kill the append file flag
          my createNewDataButton's setEnabled_(false) --disable the button to create a new data file
          my appendToExistingDataButton's setEnabled_(false) --disable the button to append to an existing data file

     end if

end saveToFile_

Nothing new here, close the file handle, reset things, and bob's your uncle.

Once you wrap your head around things, especially the encoding method thing, writing data to a file is pretty easy. Really, that was the biggest frustration for me, because even reading the Cocoa docs on this, there was nothing to really indicate that had to happen. Again, many thanks to Shane, Craig, and everyone else on the AppleScriptObjC list for all their help.
Leave a comment
Before we get into today's bit:
  1. File URLs are not File Paths

  2. Just because NSFileHandle can deal with URLs, that doesn't mean NSFileManager does. So you can use either a path or a URL to create a handle to an existing file, but to create the file itself, you have to use the path

  3. Documentation that interchanges terms like "path" and "url" makes me want to go all stabbity-stabbity

  4. Knowing when NOT to use "my" is important too

Now, on to today's bit.

I've been spending most of my time of late dealing with the "Cocoa" way to save and open files. Now, there's no functional reason to do this, AppleScript has had some solid ways to do this for some time now in Standard Additions. So, if you want to create a new file, open a write handle to it, write some data to it, then close the file handle, you have:

set theFile to choose file name with prompt "enter a new file name to be created, or choose a new file that will be obliterated" default name "newfile.txt"
set theFileHandle to open for access theFile write permission true
write aBigBunchOfData to theFileHandle
close access theFileHandle

It's pretty straightforward. Choose File name lets you 'choose' a file that may not exist yet. We then use Open For Access with write permission to get a handle to the file that we can use for writing data. Write then shoves data into the file handle, and close access closes off the file handle. If you pick an existing file, any data in that file is obliterated. (We'll deal with appending data later.)

Doing this in AppleScriptObjC, at least the "Cocoa" way is a bit more involved, but worth digging into, as a teaching exercise alone.

So, here's the code block I have for creating a new file. Note that I'm not yet actually writing data into it, just getting everything ready to do so:

on createNewFile_(sender) --create a new file
     set theSavePanel to my NSSavePanel's savePanel() --create the save panel object
     theSavePanel's setMessage_("Create New Data File")
     theSavePanel's setAllowedFileTypes_(theFileTypeArray)
     --we want to be specific here, and only allow certain types, in our case, text
     theSavePanel's setExtensionHidden_(0)
     --in the AppleScriptObjC is not objectiveC file: for bools, use 1 or 0 not YES or NO
     --if you don't, you'll get fussed at and you'll never know why
     theSavePanel's setAllowsOtherFileTypes_(1)
     --yes we only want text, but we don't want to be dicks about it.
     --if someone really wants something else, sure.
     --if someone really wants to use .dat or whatever, fine, they can
     theSavePanel's |setNameFieldStringValue_|("WiFi Analyzer Data.txt")
     --i named this as STringValue once and AppleScript being, well
     --AppleScript, it will hang on to that forever! so the pipes around it help me deal with that
     set theSavePanelResult to theSavePanel's runModal()
     --display the panel and get the binary result
     if theSavePanelResult is 1 then
     --if they clicked the 'save' button, then we want to get the path and set some flags
          set theDataFileURL to theSavePanel's |URL|()
     --get the encoded URL, which is not the file path, but we'll need it
          set theDataFilePath to theSavePanel's filename()
          set createDataFile to true
     --we're creating a new data file, so this has to be true
          set appendToExisting to false
     --we are not appending data, so set to false
          set theFileManager to my NSFileManager's defaultManager()
     --create a file manager object so that we can create a blank file
          set theCreateFileResults to theFileManager's createFileAtPath_contents_attributes_(theDataFilePath, missing value, missing value)
          --creates a blank file at the path specified.
          --Do NOT use "my theFileManager's..." because errors will happen
          set theFileHandle to my NSFileHandle's fileHandleForWritingToURL_error_(theDataFileURL, missing value)
          --use this to write to the file URL
          --you could probably also use fileHandleForWritingToPath here,
          --but since URLs are the way of the future
          --we should use them where we can
          log theFileHandle
          theFileHandle's closeFile() --we'll use this later
     end if
end createNewFile_

I left the comments in, as they do a decent job of explaining each statement. One thing I learned is that Cocoa is big on "create the object, THEN do stuff to it" whereas traditional AppleScript lets you avoid the create the object. For example, you don't have to create a file manager object, so that you can use that to create a new blank file, which then lets you get the file handle to it so that you can write data to it. With AppleScript, you just get the file path for the new file, get the handle, write, and go.

This also follows with the save panel. You create the save panel, configure the save panel, then actually run the save panel so it displays. (Also, if anyone has some sample ASOC code for beginSheetModalForWindow:completionHandler: or
beginWithCompletionHandler: and wanted to post it in the comments or link to where it is, I'd not cry.)

Next in line...actually writing data, and then appending data to the end of an existing file. Woohoo!
Leave a comment
Okay, so i've been adventuring with AppleScriptObjC, because a) FaceSpan 5 is in limbo, and will be for some time, (insert wailing noises here) and b) AppleScriptObjC is what i've been raging at Apple to give me for some time now, and it's not 100%, but it's pretty close. Any posts i put up for AppleScriptObjC will have "AppleScriptObjC" as the category, so you can find them easier.

My first application is really a port of a FS 5 application I wrote that's a WiFi signal analyzer. eventually, it will show you stats, let you automatically track those stats over time, refreshing once per second, save that data to a new file, or append to an existing file, and show you a live graph of signal vs. noise in the app window. I had all this working in FS 5, so i aim to get it all working in ASAppleScriptObjC.

First, a huge, huge, huge thank you to both Shane Stanley and Craig Williams. The both of them have been a huge help to me in this, and the community is far better for having them in it.

Second, if you haven't yet gone, run to MacScripter's AppleScriptObjC forums, it's a hell of a resource, and Craig has a great set of tutorials that were, and are a monstrous help to me.

In one sense, my refusal to deal with the unending limitations of ASS have been a help, as I don't have any bad habits to break from that direction. Since I don't know Objective C, I don't have to deal with those differences either. However, the lack of ObjC knowledge is a bit of a pain in the keister when reading Apple developer docs, although not as much as I thought it might be, thanks to Craig's tutorials.

So, some quick shots that I learned:
  • Don't rely on Xcode or anything else to tell you when you're missing a framework. You can reference CoreWLAN all day long, and you won't know you forgot to add the framework until you try to use some of the methods or properties. boo.yah

  • The Xcode debugger still doesn't work for beans with AppleScript, and even what little it worked for ASS, was too complicated to set up. There's two issues here. First, the way I want it to work is I set a breakpoint, and when it hits that breakpoint, it stops and i can step from there. I don't know why this doesn't work this way, don't care, because while i can dig up the email that tells me how to do it, seriously, this shouldn't be that hard. Enable breakpoint, stop on breakpoint.

    The other issue of course is that Xcode's debugger is GDB, and it's simply not designed to work, nor shall it ever work well with a higher-level language with dynamic syntax like AppleScript. I really wish Apple would throw a gob of money at Mark Aldritt so that he could write the AppleScriptObjC debugger implementation for Apple. He's the only one to ever really get it right.

  • Join the AppleScriptObjC list. Less noise than the ASS list, so it's quite useful if you don't really care about ASS, which I don't.

  • Before I use a class, I make sure to create the property for it, so lots of property NSTimer : class "NSTimer" kinds of things. It's pretty handy as a habit.

  • Setting up and using stuff in Interface Builder really is as easy as the tutorials make it seem.

  • Learning how to translate ":" to "_" is a pain in the butt, but important. For example, in Objective C, you have:

    (void)setNameFieldStringValue:(NSString *)value

    The AppleScriptObjC version is:

    theSavePanel's setNameFieldStringValue_("File.txt")

    That's pretty easy. Where it gets tricky is when you have stuff like this example from NSTimer:

    (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats

    The AppleScriptObjC version:

    NSTimer's scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(1, me, "timerFired:", "", true)

    Took me a few readthroughs to figure out that with AppleScriptObjC, that first parameter was a bit odd, and that i should see it as being, (assuming the method existed with only an interval parameter):

    NSTimer's scheduledTimerWithTimeInterval_(1)

    Once I grokked that there's always an underscore for every colon, even if it's just a trailing colon, things got a lot easier.

  • Another thing about NSTimer that I probably would have never realized on my own, is that to use NSTimer in this way, that selector bit should be read as "create another handler for theTimer that has the same name as the selector, and that this is where all the code for the timer will run:

    on timerFired_(thetimer)

    It's also a little odd, to me at least, to have the code to kill the timer in the same handler as the timer's functional code, but okay, sure. (there's still a bit of "take it on faith" with regard to NSTimer for me at this stage.)

  • "my" is very important. Say for example, you have a button (or two) that are disabled by default, and you want them to become enabled when another checkbox is clicked. Well, you set them as disabled in Interface Builder, so your initial state is set. Then you want them to be enabled when that checkbox is enabled. You might want to do this:

    on saveToFile_(sender)
         if sender's intValue() is 1 then
              createNewDataButton's setEnabled_(true)
              appendToExistingDataButton's setEnabled_(true)
         else if sender's intValue() is 0 then --if you're de-checking the checkbox
              createNewDataButton's setEnabled_(false)
              appendToExistingDataButton's setEnabled_(false)
         end if
    end saveToFile_

    and that will work, sort of. The buttons will enable and disable, but even after you wire them up to code, they won't DO anything when you click them. They know they're enabled, the application doesn't. For that bit to work, you need "my":

    on saveToFile_(sender)
         if sender's intValue() is 1 then
              my createNewDataButton's setEnabled_(true)
              my appendToExistingDataButton's setEnabled_(true)
         else if sender's intValue() is 0 then --if you're de-checking the checkbox
              my createNewDataButton's setEnabled_(false)
              my appendToExistingDataButton's setEnabled_(false)
         end if
    end saveToFile_

    Now, when you click the newly enabled buttons, stuff will happen. Stuff happening is good.

  • Objective C may tell you to use YES and NO for BOOLs. AppleScriptObjC wants 1 and 0, and don't you forget it.

  • Just because AppleScript is case-insensitive, Objective C is not, and will mess with your head until you realize that.

  • I really wish Apple's IT documentation was as good as its developer docs.

That's enough for now, I'll start going through some of the actual code stuff I'm working on later.
Leave a comment
So me and a couple of friends, (Peter Cohen of Macworld and Darby Lines, aka The Angry Drunk) are doing a podcast.

If you like angry, overly profane ranting about the unfathomable size of stupid on the Internet, then you can either go to the site and subscribe to the podcast, or if you are into teh iTunes, then you can subscribe via iTunes too. (Yes, you can actually search the iTunes Music Store for "Angry Mac Bastards", and we show up. hee.)

Note: all three of us are either currently or ex-IT, and Peter has to actually go into the Macworld.com and play in the forums as part of his job....

Angry sarcasm and profanity, we has it.
1 comment or Leave a comment
New puppy, she's six weeks old.

Ladies, gentlemen, I give you...

2 comments or Leave a comment
Tree vs. Roof!

Fun times. Everyone's okay, but oy vey!
1 comment or Leave a comment
Every once in a while, I think to myself, "Self, you should rejoin metaquotes, it's not a bad community after all"

Then I have some reason to read the comments, and I remember why I left, and holy fuckoly, am I glad I did. Like the recent dramaaaaady, caused by benkenobigal metaquoting a really awful joke I made.

Yes, I know. It's tacky. It's kinda icky. Yes, I'm well aware the woman had a C-Section, and therefore her yoni, her womanly well, her source of female power, the only good force in the universe is unharmed.




What I love is that the same people who are calling me sexist, et al for saying, and I quote myself:
I bet that if she spreads her legs too quickly, she sucks her underwear halfway up to her ovaries.

Feel free to titter away at jokes about the Duggar family's humpteen kids and various "Holy shit, it's a vagina not a clown car". In the spirit of the same kind of picayune shit the metaquotes dillholes are employing, I'll point out that Mrs. Duggar had those kids one at a time, not in groups of eight, and therefore, the clown car metaphor DOESN'T EVEN FUCKING APPLY TO HER, YOU FUCKING DINGALINGS.

It actually applies more, in spite of the c-section, to Ms. Suleman, who had eight kids at once, and therefore had a LOT OF PEOPLE in a VERY SMALL SPACE...just like a fucking clown car.

If you're going to rag on MY metaphor, don't misapply one yourself.

Oh, and for the "some things aren't funny crowd?"

<carlin>Fuck you</carlin>

It's all funny. Rape, incest, you name it, there's something funny about it. 9/11? That's funny too, bitches! I'd say they all have sore pussies, but I don't think they've had another human near that area without money being exchanged in decades.

O noes, I made a sloppy pussy joke. Shit, that's nothing. You want bad sexist jokes?

What do you tell a woman with two black eyes? Nothing, you already done told her twice.
What's the difference between a dog and a woman? The dog learns if you beat it hard enough.
Why is sex with a sheep better than sex with a woman? At least the sheep is quiet after you eat it.
Why is a woman's pussy and asshole so close together? So after the roofies kick in, you can carry her home like a six-pack.
Why are there necrophiliacs? They finally got the bitch to shut up, why get rid of her now?
What do you call that thing you wipe your dick off on after you get done jacking off? "Honey".
What's the correct way to ask your woman for a blowjob? Ask?

And that's just two minutes worth. They can get a lot worse.

Now, while people are having their little, "SEE! HE'S REALLY A MYSOGONIST", those of you who are sane will realize that civilization didn't collapse, nor are men roaming the streets dragging women by the hair.

Did your house burn down?
Did you lose the right to vote?
Did you have to quit your job so you can make babies?
Were you gang-raped by circus midgets wearing Man Coulter masks?


Of COURSE it was tacky and icky, I knew that when I posted it. What I love are the "more feminist than thou" crowd acting like that joke, or (most likely), the ones I just posted, are somehow great assaults on women, or an attitude that needs correcting. Get over yourselves. Shouldn't you be, you know, working to correct real problems instead of spazzing out on metaquotes? Oh wait, LJ, never mind.

So to benkenobigal, unleashedfreak, veronica_rich, lillyluna and the others who get tacky, icky, tasteless humor, I'm sorry you got sucked in to that draaaaaamedy. But you're welcome here anytime, it's all good.
2 comments or Leave a comment
I bet that if she spreads her legs too quickly, she sucks her underwear halfway up to her ovaries.
1 comment or Leave a comment
Oh, the puns, they flow like molten lead over the castle walls...

So, as a way of making sure that Hammerfall's lamer attempt at continuing to infringe on other people's copyright is as full of glorious fail as it should be...here's some proof.

Here's a piece Melissa did:Collapse )

Yeah...so even if Dennis tries to play switcheroo with the files, here's the proof that he was stealing art for his own, (i'm sure) copyrighted work. Asshole.

Technorati Tags:

Leave a comment
So now, not only are the Hammerfall people stealing art, but they're renaming stuff on their photobucket account, (where all the stolen game artwork is hosted) so that the DMCA notices don't apply.

Let's be clear...first they say "Oh we have the rights", then they CHANGE FILES AROUND so as to nullify efforts of the PEOPLE THEY STOLE THE ARTWORK FROM to make them STOP USING THE ART THEY STOLE.

These are not the actions of people who think they have clear license to things. These are the actions of fucking thieves trying to avoid getting busted.

So let's be clear: Dennis Kimbell and Mark Kimbell, the creators of Hammerfall? They're thieving dicks, and need to be universally recognized as such.

Technorati Tags:

4 comments or Leave a comment
So a day or so ago, my wife discovers something that really pissed her off...her artwork, used without permission, in a Facebook RPG, Hammerfall.

So, being the industrious sort she is, she realizes that it's not just her. There's maaaaany artists who have been ripped off by Hammerfall. So they start going through the artwork. And emailing each other.

Okay, so here's the thing...nothing, and I mean NOTHING is as fierce as a bunch of artists who realize you've been stealing their work. So they start pointing it out to Hammerfall. Hammerfall starts trying to bullshit them about it. The money quote:
We have addressed this, we're not stupid here, all the art we used is either under creative commons license or similar, or we contacted the artists for permission, or was bought off of stock photography sites. If an artist thinks this isn't the case, please send us a message.

Oh Dennis, I beg to differ, you are stupid, and no, you in fact did not get permission for it all, nor was it creative commons/similar, nor did you contact all the artists for permission, etc.

See, here's the thing...that whole "my wife discovers something that really pissed her off...." thing? And your claim that you have the right to all the artwork you're using?










Now, maybe someone lied to you. Maybe you were mislead. But you have a community that holds a grudge for a long time, is really persnickety about their IP, contains quite a few of the artists worth paying for RPG art, and you're trying to bullshit them?

Does the phrase "Burning your bridges" mean anything?

So yeah, if y'all want to have some fun, let Mr. Kimbell know that artists aren't stupid, and they know who they license shit to.
Leave a comment
TED being that collection of Total Entitlementwhore Dipshits that get together once a year to listen to "inspirational" speakers, and jerk each other off while feeling better than everyone else, because they're at TED.

The gargling sounds you hear for the next few weeks will be the blogowhores sucking TEDcock like Belladonna in a throatfuck filmfest.
4 comments or Leave a comment
Face it, even Adobe can't make flash painless. I don't mean in a web browser, I mean in a local fucking application, like, oh Contribute CS4. Four times I launched that shit, and four times I watched it screw itself into SCODVille trying to display some bullshit Flash thing on the "Welcome" screen.

Here's the screenshot:Collapse )
Leave a comment
And you know, you'd think it wouldn't anymore.

You'd think that after the continual "fuck off' that the Acrobat team gives the Mac community, (Fuck your AppleScript, it's VBA or nothing, and lemme tell you, I think even with the return of VBA, it'll be nothing. The Acrobat team has continually lied about Office integration on the Mac for how many years now? You think I'll believe a fucking thing they say sans released product? Oh SHIT no! Sorry Lampwick, you'll have to work the salt mines solo), that it'd be easier to deal with.

Nope, still sucks. "Oh, just use FlashPaper to convert things to Flash for use with Acrobat Connect...wait, Mac user? Yeah, fuck off hippie, go get a real OS".

The sad thing is, Acrobat is really a decent product, but it's hampered by a team that only cares about you if you're an all-Windows company with > 30,000 seats. They could give a fuck all about you if you're a small company, and if you have Macs, you can never have enough for them to give a squirt of piss about your needs. Mac users are pretty much lucky they didn't keep the PDF spec private, and forbid its use on anything but Windows. (Yeah, I will in fact bet that there's quite a few people in that team who wish they could take that product Windows-only at every possible level.)

I imagine I'll get it high and hard a few more times from the Acrobat team while trying to use Acrobat Connect.

Too bad they aren't more like the CS team proper.

Technorati Tags:

Leave a comment