Programming Notes
nacho4d avatar

Programming Notes

@nacho4d

Share buttons

Added buttons: Finally added tweet, like, share, burp and fart buttons (LOL) to this humble blog in blogger. So much info around there but not all of them are up to date, it was hard to find the correct information.
Got some info from here, here, here and here put the code inside my blog in the footer :)
Which one was the easiest ?
The winner is LinkedIn besides it looks great :) but GooglePlus was almost as easy as the winner.

Which one was the most difficult ... ?
Facebook, Ohh how I hate Facebook!

Twitter is OK :)
<div class='post-footer'>

 <!-- Tweeter -->
 <div style='float:left;padding:4px;'>
   <a class='twitter-share-button' data-count='none' data-lang='en' data-via='nacho4d' expr:data-text='data:post.title' expr:data-url='data:post.url' href='http://twitter.com/share' rel='me'/>
   <b:if cond='data:post.isFirstPost'><script src='http://platform.twitter.com/widgets.js' type='text/javascript'/></b:if>
 </div>
 <!-- Google plus -->
 <div style='float:left;padding:4px;'>
   <g:plusone annotation='none' expr:href='data:post.url' size='medium'/>
 </div>
 <!-- LinkedIn -->
 <div style='float:left;padding:4px;'>
    <b:if cond='data:post.isFirstPost'><script src='http://platform.linkedin.com/in.js' type='text/javascript'/></b:if>
    <script expr:data-url='data:post.url' type='in/share'/>
 </div>
 <!-- Facebook -->
 <div style='float:left;padding:5px;'>
   <a expr:share_url='data:post.url' name='fb_share' rel='nofollow' type='share'/>  
   <b:if cond='data:post.isFirstPost'><script src='http://static.ak.fbcdn.net/connect.php/js/FB.Share' type='text/javascript'/></b:if>
 </div>
 <br/>
 <br/>

Any kind of comments or criticism is welcomed here :)

CATimingFunction with bezier path


CoreAnimation is awesome! specially because it lets you define a timing function for you animation. But, somehow I always find myself googling for a interactive bezier curve creator site or something that lets me draw the curve.

Really, I don't know how many times I've done the same search and sometimes I can't find the page I used in the past.
So this time I wrote a simple but interactive HTML (using the canvas) that helps me define a bezier path I can use to create a custom timing function for CoreAnimation:
Here is the source:
$ git clone git@github.com:nacho4d/BezierJS.git


Versions in OSX 10.7 : Half tutorial, half just notes

Foreword
Last week I spent some days implementing one of the new features in MacOSX 10.7 Lion: Versions and autosave.
It was not so straight forward, APIs are too new that there is no samples yet. Documentation is not enough for newbies like me. (hehe)

I thought that watching Apple's session 107 of WWDC 2011 was going to be enough but it turns out that the video/pdfs dont show all the code you need. Besides I couldn't find the sample code specifically for that session!. TextEdit autosave implementation is just too simple, not very educational in this regard.

I think my case is not trivial. Why? Because I am using a NSWindow subclass with NSBorderlessWindowMask and I have to craft close/minimize/maximize buttons, handle window resizing by my own. I even have to add the autosave button by my own and I still don't get the "--Edited" or "--Locked" string right next to the versions button (NSWindowDocumentVersionsButton)

So here are my notes:
(I assume the app is a document-based cocoa application)

First:
The easy part, override +[NSDocument autosaveInPlace] which will enable versions by default
//MyDocument.h
+ (BOOL)autosavesInPlace{
 return YES;
}
Or override +[NSDocument preservesVersions] directly.

Now, without much effort you will get something like this:

Second:

Still easy, implement the following NSWindowDelegate methods. If you are using a subclass of NSWindowController that is a good place, if not then the NSDocument subclass should be fine :)

To customize the size of the window in the version browser:
//Bug? : In NSBorderlessWindowMask windows below method is called but has no effect!
- (NSSize)window:(NSWindow *)aWindow willResizeForVersionBrowserWithMaxPreferredSize:(NSSize)maxPreferredSize maxAllowedSize:(NSSize)maxAllowedSize{
    //Create a custom size for the window entering version browser
    //This is just an example of a size you might use. This is quite big
    float ratio = maxPreferredSize.height/maxPreferredSize.width;
    NSSize winSize = [aWindow frame].size;
    winSize.height = winSize.width * ratio;
    return winSize;
}

To customize the window when entering/exiting version browser
- (void)windowWillEnterVersionBrowser:(NSNotification *)notification
{

    //Suggested by the docs:
    //disable UI elements that will have no effect in version browser.
    //Simplify the UI, as well

    //Example a textView:
    MyWindow *win = (MyWindow *)[notification object];
    [win hideTitlebarAndStatusBar];     //method of MyWindow : simply the window
    [win setUserIntaractionEnabled:YES];//method of MyWindow : disable UI
    [myTextView setEditable:NO];        //disable UI
}

- (void)windowDidEnterVersionBrowser:(NSNotification *)notification
{
    //If needed, do your thing here
}
- (void)windowWillExitVersionBrowser:(NSNotification *)notification
{
    //If needed, do your thing here too
}

- (void)windowDidExitVersionBrowser:(NSNotification *)notification
{
    //Suggested by the docs
    //Set the window to its original settings

    MyWindow *win = (MyWindow *)[notification object];
    [win showTitlebarAndStatusBar];     //method of MyWindow : restore the window
    [win setUserInteractionEnabled:YES];//method of MyWindow : restore UI
    [myTextView setEditable:YES];       //restore UI
}

Third
In fact this step is easy too but not very well documented, I believe.

In normal cases (When using regular windows with non NSBorderLessWindowMask) doing step 1 and 2 is enough. Cocoa will coordinate buttons in the window and resized to not to work while in the version browser.

In case of NSBorderLessWindowMask windows we need to do more.
As suggested by Apple docs UI/input should be disabled and in the window should be simplified to show only relevant information.

Something that is not clear in the docs is that above methods are only called for the current document (The document in the left side of the version browser).
So in order to disable UI in windows of past versions's documents (the documents in the right side of the browser) we need other APIs:

-[NSDocument isInViewingMode] will return YES for all the documents in the right side of the browser. NO for the current document
And -[NSDocument windowForSheet] will return the windows for all these documents.

So something like this should work:
- (NSWindow *)windowForSheet{
    MyWindow *win = (MyWindow *)[super windowForSheet];
    MyDocument *doc = (MyDocument *)[[win windowController] document];
    if ([self isInViewingMode]) {
        //disable UI temporarily
        [win hideTitlebarAndStatusBar];
        [win setUserInteractionEnabled:NO];
     [doc->myTextView setEditable:NO];
    }else{
        [win showTitlebarAndStatusBar];
        [win setUserInteractionEnabled:YES];
        [doc->myTextView setEditable:YES];
    }
    return win;
}

However, in version browser, each document is created in a different thread, so if you are handling big documents it should be much more efficient to above check in the same a method that run in the same thread. (-[NSDocument windowForSheet] is called in current document thread)

So, we better write above content inside windowControllerDidLoadNib: to avoid thread changing. :)

BTW. If you wan't to debug your app's version browser set flag NSDocumentRevisionsDebugMode to YES. Each window/document in the version browser is loaded in one different thread so without this flag is difficult to debug


Fourth:
These are not mandatory but recommended to implement.
Enabling writing/saving asynchronously might improve dramatically application responsiveness, specially when working with big documents.

- (void)autosaveWithImplicitCancellability:(BOOL)autosavingIsImplicitlyCancellable completionHandler:(void (^)(NSError *errorOrNil))completionHandler;
- (BOOL)canAsynchronouslyWriteToURL:(NSURL *)url ofType:(NSString *)typeName forSaveOperation:(NSSaveOperationType)saveOperation;
- (void)continueActivityUsingBlock:(void (^)(void))block;
- (void)continueAsynchronousWorkOnMainThreadUsingBlock:(void (^)(void))block;
- (NSDocument *)duplicateAndReturnError:(NSError **)outError;
etc


Window restoration:
I wish Apple showed this part too in their examples so my life would have been easier but NO! - they like me to suffer! (Well not really...)

Implement these NSWindowDelegate methods too to save/restore some info into/from the coded window.

- (void)window:(NSWindow *)window willEncodeRestorableState:(NSCoder *)state
{
     //any additional info to save 
     //Example:
     [state encodeObject:myObj forKey:@"myObject"];
}
- (void)window:(NSWindow *)window didDecodeRestorableState:(NSCoder *)state
{
     //any additional info to restore
     //Example:
     MyObject *myObj = [state decodeObjectForKey:@"myObject"];
}

Similarly you can override below methods of NSWindow
I am saving the state of the title bar because that is not done by default by Cocoa
- (void)restoreStateWithCoder:(NSCoder *)coder
{
    [super restoreStateWithCoder:coder];
    titleBarHidden = [super decodeBoolForKey:@"titleBarHidden"];
}
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
    [super encodeRestorableStateWithCoder:coder];
    [coder encodeBool:titleBarHidden forKey:@"titleBarHidden"];
}

Basically that is all.
More info can be found in the following links and surely everywhere very soon as Lion and these features becomes more and more popular.

References

Custom Window Widgets:

This is tricky and didn't really think it would work because all the info found was more than 3 years old. But it did!
I could get a window with custom widgets (Close/Miminize/Maximize and theorically fullscreen buttons), :) . I hope someday I know photoshop as much as objc and make my own cool buttons, for now I have to play with iCal's buttons.



Normal Cocoa NSWindowButtons


Customized NSWindowButtons

The left picture shows the normal cocoa NSWindowButtons, right picture on the right shows darker and yellowish buttons, not very nice on the gray titlebar but ... Is because I don't know photoshop! (涙) But with great potential huh?! (笑)

Introduction:

In cocoa you get the buttons of a window :
NSButton *button = [window standardWindowButton:NSWindowZoomButton];

But button is not really of kind NSButton is in fact an instance of _NSThemeWidget. Furthermore, it does not use a normal NSButtonCell but it has its own _NSThemeWidgetCell. Furthermore, the close button is a subclass of them: _NSThemeCloseWidget and _NSThemeCloseWidget because it has a dirty state of the document

In this post I show how to customize the images of these private classes because setImage: does nothing :(

How this works
I tried to make a new class and pose it as an _NSThemeWidgetCell but posing is being deprecated and is not possible anymore, at least not that I know.

Crayson told me that I should better try the method swizzling approach. This was a huge hint!.
So using a couple of objective-c runtime functions I added a method dynamically to _NSThemeWidgetCell class.

Then it was very easy to exchange methods : the one newly added alt_drawWithFrame:inView: with the other that does the drawing as usual drawWithFrame:inView:.

This part the code and the whole source + the images I borrowed from Lion's iCal can be downloaded from github :)

void drawWithFrameInView(id self, SEL _cmd, NSRect frame, id view)
{   
    NSLog(@"hacking drawWithFrameInView ...");
 
    NSString *imageName = @"titlebarcontrols_regularwin";

    //Get button ID
    int buttonID = (int)[self buttonID];
    NSLog(@"%d", buttonID);
    switch (buttonID)
    {
        case 127: // Close button
            imageName = [imageName stringByAppendingFormat:@"_close"];
            break;
        case 128: // Minimize button
            imageName = [imageName stringByAppendingFormat:@"_minimize"];
            break;
        case 129: // Zoom button
            imageName = [imageName stringByAppendingFormat:@"_zoom" ];
            break;
        case 130: // Toolbar button
            imageName = [imageName stringByAppendingFormat:@"_toolbar_button" ];
            break;
    }

    //Get System preferences: Window style: (Aqua or graphite)
    NSString * const kAppleAquaColorVariant = @"AppleAquaColorVariant";
    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    [userDefaults addSuiteNamed:NSGlobalDomain]; 
    NSNumber *color = [userDefaults objectForKey:kAppleAquaColorVariant];
    if ([color intValue] == 6) {//graphite is 6 
        imageName = [imageName stringByAppendingFormat:@"_graphite"];
    }else{//defaults to aqua, (aqua is 1)
        imageName = [imageName stringByAppendingFormat:@"_colorsryg"];
    }

    //Get button state
    if ([self respondsToSelector:@selector(getState:)]) {
        int state = (int)[self getState:view];
        //NSLog(@"state %d", state);
        switch (state) {
            //Known states
            //active = 0
            //activenokey = not used?
            //disabled = not used?
            //inactive = 3
            //pressed = 2
            //rollover = 1
            case 0: 
                imageName = [imageName stringByAppendingFormat:@"_active"];
                break;
            case 1:
                imageName = [imageName stringByAppendingFormat:@"_rollover"];
                break;
            case 2:
                imageName = [imageName stringByAppendingFormat:@"_pressed"];
                break;
            case 3:
                imageName = [imageName stringByAppendingFormat:@"_inactive"];
                break;
            case 4:
                break;//disabled? activenokey?
            case 5:
                break;//disabled? activenokey?
     default:
                break;
        }

        NSImage *img = [NSImage imageNamed:imageName];
        if (img){
            [img dissolveToPoint:NSMakePoint(frame.origin.x, frame.origin.y + frame.size.height) fraction:1.0];
        }else{
            [(NSButtonCell*)self alt_drawWithFrame:frame inView:view];//original implementation 
        }
    }
}

And the part of code that does the objective-c runtime magic:

@implementation TestAppDelegate
@synthesize window;
- (void)applicationWillFinishLaunching:(NSNotification *)notification
{

    Class class = NSClassFromString(@"_NSThemeWidgetCell");
    SEL new_selector = @selector(alt_drawWithFrame:inView:);
    SEL orig_selector = @selector(drawWithFrame:inView:);

    //Add a new method dinamically because _NSThemeWidgetCell is a private class
    BOOL success = class_addMethod(class, new_selector, 
                (IMP)drawWithFrameInView, 
                "v@:{CGRect={CGPoint=dd}{CGSize=dd}}@");
    if (success) {
        //Get the methods to exchange
        Method originalMethod = class_getInstanceMethod(class, orig_selector);
        Method newMethod = class_getInstanceMethod(class, new_selector);

        // If both are found, swizzle them
        if ((originalMethod != nil) && (newMethod != nil)){
            method_exchangeImplementations(originalMethod, newMethod);
        }
    }

//TEST:a new method should appear "alt_drawWithFrame:inView:" in the console
//uint methodCount = 0;
//class = NSClassFromString(@"UIWebDocumentView");
//Method *mlist = class_copyMethodList(class, &methodCount);
//for (int i = 0; i < methodCount; ++i){
// NSLog(@"%@", NSStringFromSelector(method_getName(mlist[i])));
//}

//TEST: "hacking drawWithFrameInView ..." should appear in the console
//NSButton *but = [window standardWindowButton:NSWindowZoomButton];
//[[but cell] drawWithFrame:NSZeroRect inView:nil];
}
References and useful Links

Instrospection in Objective-C
Sometimes public APIs in cocoa/cocoa-touch frameworks is simply not enough or maybe a private API could save you some work.

So, this is not something new: If you read Objective-c runtime reference you will find really handy and hacky functions there.
These are some snippets:

List up methods of a class
uint methodCount = 0;
Class class = NSClassFromString(@"UIWebDocumentView");
Method *mlist = class_copyMethodList(class, &methodCount);
for (int i = 0; i < methodCount; ++i){
    NSLog(@"%@", NSStringFromSelector(method_getName(mlist[i])));
}
List up protocols conformed by a class
Class class = [UITextView class];
Protocol **p1 = class_copyProtocolList(class, NULL);
for (int i = 0; p1[i]; i++) {
    printf(@"%s\n", protocol_getName(p1[i]));
}
free(p1);
List up ivars of a class
Class class = [UITextView class];
uint ivarsNum = 0;
Ivar *ivars = class_copyIvarList(class, &ivarsNum);
for (int i = 0; i < ivarsNum; i++) {
    printf(@"%s\n", ivar_getName(ivars[i]));
}
Introspect arguments of a method
Class class = [UITextView class];
SEL selector = @selector(keyboardInput:shouldInsertText:isMarkedText:);
Method method = class_getInstanceMethod(class, selector);
char *arg = method_copyArgumentType(method, 0);
printf(@"_%s_\n", arg);
free(arg);

Here you will get: @ for objects, i for integers, f for floats. That is all you get.

Using GDB In Xcode set a symbolic break point to:
-[UITextView keyboardInput:shouldInsertText:isMarkedText:]
or in gdb type:
b -[UITextView keyboardInput:shouldInsertText:isMarkedText:]
so the debugger will stop at that method. When stopped is possible to show the registers values hence is possible to inspect the arguments passed :) This is a more complete list of how to call the registers in different architectures. Since the iOS Simulator is in i386 (after prolog) I can inspect this particular method doing:
(gdb) po *(id*)($ebp + 8)
<MyTextView: 0x5911270; baseClass = UITextView; frame = (80 70; 240 323); text = 'Lorem ipsum dolor sit er ...'; clipsToBounds = YES; autoresize = RM+BM; layer = <CALayer: 0x5c0c7d0>; contentOffset: {0, 0}>

(gdb) p *(SEL*)($ebp + 12)
$1 = (SEL) 0xbd19

(gdb) po *(id*)($ebp + 16)
<UIWebDocumentView: 0xa02f000; frame = (0 0; 240 457); text = 'Lorem ipsum dolor sit er ...'; opaque = NO; userInteractionEnabled = NO; layer = <UIWebLayer: 0x5c35070>>

(gdb) po *(id*)($ebp + 20)
t

(gdb) p *(id*)($ebp + 24)
$2 = (id) 0x0
and finally to get the selector name from a SEL in gdb
(gdb) p (char*)$1
$1 = 0xbd19 "keyboardInput:shouldInsertText:isMarkedText:"

I found this super useful because now I can get the list of method of any class and dig in. Yippeee!

For example I wanted to see if I can customize some shortcuts in the iOS when using a hardware keyboard. But it turns out that the UITextView is not the one who controls that. UITextView seems to be a mere client of UIWebDocumentView which is the one who handles text input and also keyboard events. So overriding private methods of UITextView is just not enough. This is not as simple as I thought to I will save this for another post :)

Links

NSWindow private methods

So, this week I tried Lion almost for the first time, is getting really cool :)

My motivation to do this is because I want to make my non-standard window enter fullscreen (although now I now this is not the way of doing it) it was interesting. So in OSX 10.6 and earlier NSWindow has: performZoom:, performMiniaturize:, performClose:, etc. So I thought there should be something like:performFullscreen: No?

I dug into 10.7 SDK documentation hoping to find something. Unfortunately no, there seems to be no new things in NSWindow?, at least not that I have noticed. Weird... Maybe I am not looking at the correct docs?

I just thought of this:
#import <objc/runtime.h>

unsigned int methodCount = 0;
Method *mlist = class_copyMethodList([NSWindow class], &methodCount);
for (int i = 0; i < methodCount; ++i){
    NSLog(@"%@", NSStringFromSelector(method_getName(mlist[i])));
}
which will print a list of all methods implemented by NSWindow and it worked! I got the whole list. Including the private methods :)

These Objective-C runtime functions must be the Swiss knife of cocoa-hackers. I still have to try this in 10.7 but This is definitely a good start.

PS: Off-course there was no performFullscreen: method. Later I realized that toggleFullScreen: will do the job :)

Bezier Paths : making rectangles with rounded corners

Learning at school Bezier paths was fun for me because is when I finally got to know where all those curves come from. I like the maths, and there are a lot of them behind Bezier paths ;) .

Once the theory is known is not complicated to imagine how a path would look like just by knowing its control points position. But, off-course a normal human like me can only make an approximation inside of his/her head. In real life, you don't approximated paths, specially if you are the kind of person who care about small details like me. So, I decided to do a small research and find out the exact position the control points should have in order to approximate an arc or circle or a rounded rectangle.

My choices were: 1. get a computer graphics book or go to wikipedia, remember the maths and then get a pencil and paper and try to solve it mathematically. 2. Trial and error approach. Even though I like maths I like even more programming so decided to go for number 2 and program test my results in javascript(and put the results here)


In order to make a rounded top-left corner I need to...

First trial:

In first place, I thought: I need a cubic bezier path with one control point in the top-left corner and the other at the end of the curve. This is what I got:

Canvas not supported.

That curve starts at point P0 and ends at P1 and use a couple of control points pc0 and pc1. I obtained a nice curve ... nothing new. However the corner I have just drawn is not exactly overlapping the circle in the picture. It's actually outside.

Second trial:

Having the control points in the middle makes sense as well :
Canvas not supported.

This also looks OK. But Why now it looks like the corner was cut or cropped. It seems logical to think that control points should be at the middle like in this case.

Third Trial:

What about quadratic bezier? This seems to be fine but since we have a circle in there we notice this is not correct too.

Canvas not supported.

Fourth Trial:

So I google it and found approximation of a circle using cubic bezier curve There is a paper that has the maths I decided to skip in first place ... In page 3 Dr. Murtaza found the value of kappa: 0.55... which is the (approximated) distance of control point 1 and 2 should have from start and end point respectively. Control points should be placed as follows:

Canvas not supported.

So, if we want to have a rounded rectangle with corner radius r we have to place pc0 and pc1at (0,k)and (k,0) where k is kappa = 0.552228474...

Here we have an almost perfect rounded corner! ;)

I have implemented this in javascript with the canvas element and all the graphics here were rendered using that function. And also I have made an extension for NSBezierPath class so now I can have paths like:

Here are the sources:
NSBezierPath+Extensions.h
NSBezierPath+Extensions.m

And BezierPaths:
Canvas+BezierPaths.js

References:
Mathworks - Approximation of circle using cubic bezier
Wikipedia - Bezier Paths
MDC Docs - Drawing shapes
And maybe this too:Cocoa Dev Central - Intro to Quartz



This work is licensed under BSD Zero Clause License | nacho4d ®