Sunday, April 11, 2010

Using your own Undo Stack in Cocoa

Having finished the Insert Frame UI have been connecting the first command Cello. This was something that proved surprisingly easy. The trick seems to be to subclass NSUndoManager and then yo use this as an adaptor for the existing undo system. I have installed my custom undo manager in my NSDocument makeWindowControllers method. Do this and magically it "just works".

The methods I have implemented are:

- (BOOL)canUndo

- (BOOL)canRedo

- (void)undo

- (void)redo

- (NSString *)undoActionName

- (NSString *)redoActionName


These call through to the underlying C++ class (currently there is no exception handling) so my Undo looks something like this:

- (void)undo

{

SOKFile *file = [self file];

SOKUndoList& undo_list = file->GetUndoList();

undo_list.UndoCommand();

}


Wednesday, April 7, 2010

iCal-like popup window Part II - MAAttachedWindow and CoreAnimation

Looking closely at the iCal dialog it behaves quite differently from my first cut - what it does is to scale an image of the dialog from a single starting point to something slightly bigger than the final size and then back down to final size. There is a movie of this on my previous post. This gives an appearance like it over-scales and snaps back. To me this is really good because that sort of "pop" makes it very noticeable. This happens with the strange (but good) yellow "found text" marker that appears in many Apple apps including Safari, and XCode.
So how to do this type of effect? The basic story seems to be that you need to:
  • Get an image of the complete window (as it will be drawn)
  • Scale it from the initial start point to the final size (via an oversize) using CoreAnimation.
The animation needs to be hosted in window - if you created a window with no frame (NSBorderlessWindowMask) this would seem to be a good place to host it. Then the animation can be performed either by resizing the view or by trickery on the underlying CALayer. Both would work.

There are as ever many ways to skin the cat. The simplest solution is actually a category of NSWindow that has been opensourced by NoodleSoft great blog can be found here and code is hosted on github.

What Paul Kim (think of him as Mr Noodle) has done is rather clever and wonderfully simple - the recipe is this:
  • Get an image of your window as it will be
  • Create a new transparent window (NSBorderlessWindowMask)
  • Set the content view of the window to be an NSImage - with the image that you want to see (the image of your window)
  • Call setFrame:display:animate: with a start and end rectangle
  • When the animation is complete destroy your window and show your finished window.

I have tickled his code a little to get the overshoot and pull-back for that "pop" effect - just call setFrame:display:animate: twice once for too big and once for the right size. But it is a tiny tickle. Also, and importantly, as the animation is done in an entirely separate window it will work with Mat Gemmel's MAAttachedWindow.

I attache a movie of this done with a demo version of IShowU

Wednesday, March 31, 2010

iCal-like popup window Part I - MAAttachedWindow and CoreAnimation

I have been working on animating the insert frames window in the same way that the iCal window animates. The iCal window does not just appear but instead sort of grows out of the point that you double clicked in.
It is a really nice effect - however rather than the 10 minuets (or so) I thought it would take to deploy it - in actual fact it has taken me a little while to get to the bottom of it.

My first port of call was Core Animation. I have not done any Core Animation before so it was my first look at it. Core Animation is beautiful - doing simple animations is pretty straight forward. An animation like changing a window's bounds or moving it's location is quite simple. However it is not possible to do this kind of animation with Mat Gemmel's MAAttachedWindow.

MAAttachedWindow is quite interesting and clever - what it does is create a pattern that is the window image (a bitmap) and then sets this pattern as the window background colour. So when the window draws it's background in it's background colour this magically draws the window. There is also some code that will keep the window geometry in sync with it's contents this makes it impossible to grow or shrink the window.

I had a solution to this which was to use Core Animation to resize the contents of the window and from this then recalculate the geometry of the window its self. This sort of worked - things animated but in a nasty lumpy fashion. Grubbing around on the web I found this reference to the problem.
The arrow is harder. There's a window with an arrow on Matt Gemmell's site http://mattgemmell.com/ but that uses an NSColor with a pattern image to fill the window background and that seems mess up with core animation, which can't cache the drawn window prior to animation.
There are other ways to draw custom windows. Apple have some excellent Sample code RoundTransparentWindow that draws a custom window. Their method is a little more straight forward than MAAttachedWindow - the window is 100% transparent and within the window is a view that draws the window background. Taking this approach and rolling my selves up for some fun and games with drawing paths gave me a window that animated smoothly.


As you can see - animates nice and smoothly - but is very different than what happens in iCal.

Wednesday, March 17, 2010

Non Modal Dialogs

I am at the point where I need to add a dialog that appears when he user wants to insert frames. the dialog is quite simple - a popup and a field. The normal way of presenting this to the user is with a modal dialog - or its close cousin the sheet. For me as a user modal dialogs are just not that nice. Suddenly "bang" there is a dialog. It is rarely where you want it and it interrupts your flow. I, personally, fild it's cousin "the sheet" even worse. The animation seems at times agonising - and whatever you wre doing you have to focus your atention at the top of the screen. It looks cool but strewth it is irritating.

For me a far nicer way of doing this a sort of cross between a menu and a dialog. Sounds awefull but is actually quite a nice way of working. This is, more or less, what happens in iCal when you click on an appointment in the calendar view.

A window appears with an arrow as a part of its frame. The slight difference that I want is to dismiss it of you click on something else. In this respect I want something that behaves like a menu. As a user you start a sequence of events but you are not trapped into it - in as much as you don't need to find the cancel button - just click somewhere else.

The first part of this is to draw a custom window. Mat Gemmel has a window that does more-or-less what I want. It is very easy to use and deploy.

There is little more to do than to change the default colour for the background and set it to have no border. It now looks something like this.

Sunday, March 7, 2010

Inline editing - NSTextView and the mysterious 5px margin

The PC version of cello has a dialog box to edit labels. You can, using a contextual menu, add a label or edit a label. Deleting a label is a matter of editing the label and removing it's text.

A more OSX way of handling the editing of a label is to be able to edit inline. The idea is that if you double click on a label then you get a text cursor in the label - and you edit the name inline.

To implement this I field the double click and then create an NSTextView. The label is an irregular shape.

I wanted it to grow and shrink as the you type. I wanted the irregular shape to change with as you type. The key bits are to set the NSTextView up as stretchy setHorizontallyResizable:YES and then to stop it from drawing a background setDrawsBackground:NO. When the edited label is drawn it gets it's size from the text field and does not draw text. The result is that the NSTextView is responsible for drawing the text in this unique situation. The final twist is to set the delegate of NSTextView to the timeline view. The textDidChange method just gets the label to redraw - so it now grows and shrinks as you type.

Getting something like this to work is a bit of a fiddle but mainly boils down to getting the right clutch of settings. Mine were these:

[_editLabelView setHorizontallyResizable:YES];

[_editLabelView setMinSize:NSMakeSize(400.0f, kLabelViewHeight)];

[_editLabelView setFieldEditor:YES];

[_editLabelView setTextContainerInset:NSMakeSize(0, 0)];

NSTextContainer *container = [_editLabelView textContainer];

[container setLineFragmentPadding:0];

[_editLabelView setDelegate:self];

[_editLabelView setString:label];

[_editLabelView setRichText:NO];

[_editLabelView setSelectedRange:NSMakeRange(0, [label length])];

[_editLabelView setDrawsBackground:NO];


My biggest problem was that I always had a 5px right shift of the start of the text from where I expected it to be. This margin or inset is the lineFragmentPadding on the NSTextContainer. It took a while to track this one down. The magic fix was

_editLabelView = [[NSTextView alloc] initWithFrame:bounds];

NSTextContainer *container = [_editLabelView textContainer];

[container setLineFragmentPadding:0];



performClickWithFrame, small menus and Señor Calviño

An advantage that MacApp had over Cocoa was that you got the source code. My experience of MacApp was that it was far more buggy but having the code meant that you could see how things work and (of course) fix bugs. Cocoa seems rather bug free - but when you find a bug you have no option but to work around it. I am not pining for the days of MacApp, things move on, Cocoa is very, very good - but I do miss the source.

My current issue is contextual menus. I am using performClickWithFrame to popup the menu - however as the Timeline is quite small I don't want the distraction of a normal size menu - it looks odd and out of place. The solution that I have is to use setAttributedTitle to set the menu items to a small, and comfortable, size. This all works just fine:
The rub come when you have a contextual menu with just one item. It can happen - contextual menus are contextual - they only show items that can be invoked - items that can't (would be disabled in normal menus) are simply omitted. This can leave you with a contextual menu with just one item. Unfortunately it looks like this:
Hello Señor Calviño - yep it is bald as an egg - not even a hint of text. If instead of setAttributedTitle I use setTitle it looks fine (but big).
This seems to be an either/or decision - big or bad. The workaround I had for this good/bad/ugly place was to give the popup a header label. The resulting menu looks like this.

Saturday, March 6, 2010

Workaround - Crash in NSPopUpCell performClickWithFrame:inView:

I have been working on contextual menus for the time line. Contextual menus appear to be quite straight forward. If you don't want your contextual menus customized with whatever can add to contextual menus you create a NSPopUpCell - this is described in Apple's developer documentation. What you need to is nicely covered and well explained in Jesper's blog. Jesper kindly even gives you a category that does the leg work for you.

All looks good - except that there is a missing release in Jesper's category. It is not a big deal and I would probably not have mentioned it except that adding the release caused things to crash. Somewhere there was a stale object - but the code is almost trivial. Hitting Google I got a few mentions of the crash (for example here) but no solution.

I did a few things. I created my own subclass of NSPopUpCell with just a dealloc so that I could put a breakpoint on the dealloc. Was the NSPopUpCell being released by performClickWithFrame:inView? It seemed unlikely and was indeded not happening. It was being called (as I would expect) by the release I added. If I did not release the menu that I set [popUpButtonCell setMenu:menu] then the crash did not happen. The next thing I did was to track the owner counts of my menu. They were as I expected incremented by [popUpButtonCell setMenu:menu] and decremented when I released the cell.

I don't know what the cause of the problem is. I suspect that it is bug in AppKit - but this is of course conjecture. The workaround I found was rather than setting the menu, to use the menu that exists in the NSPopUpCell.

NSMenu *menu = [popupCell menu];

Once you have your hands on this menu you can add and remove items - just you don't get the crash when you dispose of the NSPopUpCell.

Wednesday, March 3, 2010

Poof - Drag Deleting Items in Cocoa

OSX has an idea of drag-deleting items in the UI. You can see this in the dock amoungst other places - drag an item out the dock - and the cursor changes to a pointer adorned witha cloud. Release and "Poof" the item disappears with a small animation and a sound. It is cool and intuative. It works well when moving items does not cause a scroll. I have got used to the same way of deleteing breakpoints in XCode and wanted to add the same functionality to the labels in the timeline.

Looking at XCode the way that markers work is quite straight forward
  • Drag up or down and the breakpoint marker moves
  • Drag out of the breakpoint column and the cursor changes to the disappearingItemCursor cursor.
  • Release outside this column and the animation plays

Setting the cursor is quite simple just call [[NSCursor disappearingItemCursor] set]

The animation proved a little harder to track down. I quite quickly found a private API it is listed in a number of places that apparently did the job. My experiance of private APIs is that they disappear even quicker than the public ones.

The good news is that Apple supply it as a public API - NSShowAnimationEffect. It is explained in apple's web site. I found this link in cocoadev usefull.

Monday, March 1, 2010

TimeLine view - Labels

I am continuing to work on the header of the timeline. In addition to showing the current time frame and a selection the timeline can show labels and actions. I have been working on the visualization of labels.

In the PC version of Cello the labels are shown as flags.


The flags are named points in the timeline. I have taken this in the OSX Cello and changed it very slightly. Keeping the flags I have dropped them down to a second new marker line. This is the sort of visualization you often see for tab markers in a word processing column.


Single Frame Selected (current frame)


Multiple Frames Selected

It is a tiny change - I made it so that I could see the time-frame markers. At the expense of a modest amount of space it is a little less cluttered.

Thursday, February 25, 2010

TimeLine view - header

I have been working on the timeline header view. In Cello this is called the FrameView as it contains details of fime frames. I have just got the selection working. The selection normally shows a single frame - the current frame, but it is possible to select a contiguous strip of frames as well.

Before starting work on the selection I once again had a look at CocoaTron NSTableView to see how the mouse down - and shift selection was handled. The code was less liftable - but studying someone else's code is always useful.

Selection of a Single Frame

Selection of Multiple Frames

Friday, February 19, 2010

Drag resizable columns in a headerless NSTableView

Currently table headers are responsible for handling the dynamic resizing of table columns. I have needed to make it possible to resize table columns from within the table - so the idea is that you will be able to:
  • move your mouse to the divider between two columns
  • the cursor will change to indicate that you can resize the column
  • click and drag and your column will resize.
I already have a NSTableView sublclass that has modest, general, enrichments for the very few enhancements I need to NSTableView - so my aim is to enhance this a little.

As a start point I looked at the CocoaTron code for the NSTableHeader. My thought was the CocoaTron NSTableHeader will be much like the cocoa table header, it handles resizing so will in the least be a reasonable template for implementing my own table. As it happens I was able to more-or-less copy/paste the code for the following key methods into my NSTableView.

-(void)resetCursorRects;
-(void)resizeWithOldSuperviewSize:(NSSize)size;
-(void)mouseDown:(NSEvent *)theEvent;

Of course there is a little more to it than this - but it is small beer and this small piece of CocoaTron proved an excellent starting - and indeed took me most of the way.

Wednesday, February 17, 2010

Synchronizing the selection in two NSTableViews

I have been looking at a problem of synchronizing the selection across two NSTableViews. It took a few attempts to come to a solution that worked as I wanted it to. The idea is to have two table views with the same number of elements - as soon as you make a selection in one the other one updates to show the same items selected.

My first port of call was to hook into the tableViewSelectionDidChange notification. Then when one selection changes you just change the other. This works - but there is a lag. Click in one table the selection changes, the window updates and then the second selection changes.

The solution was to override selectRowIndexes and to use that to trigger the selection in the paired table. This works fine and produces the required results. I was able to 'hack' this in as a proof of concept fairly quickley.

Implementing it 'properly' or rather 'as I wanted it' I dod not want my two tables to carry references to each other - insead I wanted to have this in the controller class. I did this my adding a new protocol to my subclass of the table:

- (void)selectedRowIndexes:(NSIndexSet *)indexes forTableView:(NSTableView *)tableView byExtendingSelection:(BOOL)extend;

Then, by implementing this protocol, I can add the synchronization logic to the controller which is where I want it.

Monday, February 15, 2010

Interface Builder

Working with an application framework has a few parts - there is the first staggering steps of familiarity and the kind of awkwardness you have because of the difference between what you knew before and what you are looking at. Then there is the familiarity when you know more-or-less how to do what you did before and then there is that time a while later when you sort of "get it". This part is the most exciting part as that is when you can work with the flow and get the benefits of the good current.

I think have had something of a slow burn Eureka moment with interface builder. It goes like this - any class member that is declared with the magic IBOutlet in front of it can be connected up in interface builder. It is difficult to kick off in Cocoa (I started with Aaron Hillegass' book) without seeing that this is the way you connect up views and controllers - but you can connect up other things as well. If you have a table and you need to deal with the columns you can do it in two ways. You can give each column an Identifier and loop through the columns until you find the column that you want - or you can pick out the columns you want, add them to your controller with a IBOutlet - and hook them up in Interface builder. The anything part of this is quite powerful - if you need to set attributes on the NSCell derivative that powers the drawing of your column - you can hook this up as well - and so it goes on.

Thursday, February 4, 2010

BWToolKit

I have downloaded and built BWToolKit by Brandon Walkin. This is a framework that extends the existing components that are available in Inerface Builder. It is of particular interest to me at the moment as it contains an improved splitter. The BWSplitView is a subclass of the NSSplitView that handles improvements like the splitter collapsing, max min sizes etc. BWToolKit comes with interface builder plugin so you can drag out Brandon Walkin's carefully crafted goodies just as if they were a piece of appkit. BWToolKit is available under the New-BSD licence. I downloaded the code and built it myself as I feel insecure if I don't have the source code.

Sunday, January 17, 2010

Moving MFC icons to Cocoa

Working my way through the timeline MFC to Cocoa conversion I have once again bumped up against graphic resources. Various components in the UI have graphic resources (icons). Back when I was getting the basic engine to work I compiled out all things of this ilk with a huge amount of other things with a

#define _NO_UI.

A part of my approach is that the structure of the new UI follows the PC version very closely so I am bumping into the way that Icons are handled within Cello as a whole. Basically this I am gradually working my way through some of these #if _NO_UI blocks.

Existing graphic resources in the PC version are stored as .bmp files. Each file typically has a number of icons. The various components that comprise the system (such as the 20-30 effects) have factory functions that specify a name for the component, and a bitmap resource with an offset as an icon. This is achieved by all the components inheriting from a base class that maintains this. The upshot is that I have to tackle this base class and bring it into line, and because I hit the constructor, I also have to attend to the many sub-classes that use icons.

In the OSX version of Cello I use NSImage to represent an icon - and as an alternative to specifying an icon resource and offset simply specify a name. This name is the name is nothing more than the filename that of the icon that appears in the bundle. I have written a really simple wrapper based on boost intrusive_ptr that wraps the NSImage for use in C++. The existing icons are appropriate to Windows and will look really out of place in OSX but that is a problem for another day and a problem that I hope to hand over to an icon designer. For place-holder icons I take the existing icons bring them to my old Power-PC and use an old copy of Photoshop to turn them into individual PNG files.

Monday, January 11, 2010

Mutating MFC to Cocoa

I am making progress converting the view hierarchy of the timeline to cocoa. In the about face I am working through the classes and interfaces that are defined in MFC and attempting a mechanical conversion where possible. In my first cut I do the following:
  • Mutate the all 'interface' definitions to protocols.
  • Mutate all pure virtual base classes into protocols.
  • Mutate all other classes as objective C classes.
  • Where a class is based on multiple inheritance of a C++ class (typically mix-in classes) this is expressed as a protocol and I write a subclass of the mix-in class that funnels calls to an object of that protocol.
  • Comment out what is obviously not needed for a first cut.
I do the conversion bit by bit getting the compiler to guide me. It is a relentless, tireless guide. At best I get logic and code that is very close to what I need at worst I get a fill-in-the-dots template. Importantly the code dovetails in nicely into the existing code.

Sunday, January 3, 2010

C++ Observers in Objective C

Cello has various notifications that are posted to observers. This is a good and fine thing - the observers are generally UI components and in the PC version of Cello are C++ classes. The form is that through multiple inheritance of a mix-in class a class becomes an observer. It overrides the observation methods and handles the notifications. The rub comes with Objective-C.

Under OSX all the UI components are Objective C classes these can not inherit (neither multiply nor singly) from a C++ class - yet they need to be observers. So how to solve the problem? There seem to be two possibilities:
  1. Get the Objective C classes to "somehow" observe.
  2. Reroute all messages through the NSNotification system.
I have chosen the first option. Why? Well I want to use as much of the original code as possible and I don't want to have to serialize all the information that is passed to the observer as Objective C objects.

So how to do it? The method I chose is fairly simple. It goes like this:
  • For each observer write an equivalent protocol to the virtual method(s) in the C++ mix-in class.
  • Write a C++ class that is a subclass of the C++ mix-in observer and forwards the observations to an Objective C object that conforms to the protocol.
  • Now write your Objective-C class. It should conform to the protocol. It also has a member that is a pointer to the forward class that it owns (creates and destroys) and forwards the messages.
It is a modest fiddle - but works well.

Saturday, January 2, 2010

TimeLine - an about-face

Sometimes programming is about heading down a blind alley. I guess that thing to do in these situations is to know when to call it a day and head back. So this is what I am doing with my current work on the timeline.

So why the about face? What I was doing with the Timeline was to develop a slot in replacement for something a bit like a NSTableView but with all the capabilities rewired for a TimeLine. My idea was to provide a protocol that would be expressive enough to allow a fairly simple mesh between the Objective C and C++ in a controller class. Thus the controller class would be a thin shim or adaptor between the two things. This idea is workable - but the question is "how much work?" On the other hand I have a suite of classes though based on MFC do the job. This is code that is written and works. Having written the code for the row labels in the timeline I started working on the column (or timeline) header. Starting work on this I used the existing class as a guide for functionality etc. The further I worked with this the less certain I became. For example selection in this view is handled within the Cello engine and just reflected in the view. This is currently encapsulated in a C++ class. I can either
  • Wrap this in a cocoa class:
  • Serialize it into an NSDictionary
  • Project the C++ class directly into cocoa.
My approach with this project has been to aspire to the path of least resistance. This is to try and get the project up and running as quickly as reasonably possible, but also I have no desire to rewrite things that work. The original Cello source code is very well written, lovingly commented - why not use what I can?

So my revised approach is to use what I can of the MFC in the user interface. So what was a SOKTimeLineView that is an MFC based class is now an CHTimeLineView a subclass of NSView. The bulk of the methods I am rewriting as Cocoa in Objective C++. This is simply not hard - just copy and paste in the methods and fiddle them about. If I specify the views as having flipped coordinates even the coordinate calculations for drawing are more or less right.

As with all of these things there is something to loose - and what is to loose? Well mixing C++ and Objective-C is a bit of a nasty thing. Under 32-bit there are two incompatible exception models. So I will have to tread with care and make sure that I don't end up propagating C++ exceptions through AppKit. The other thing to loose is the work I have done - but the reality is that it will not be entirely lost. I imagine that the bulk of what I have written I can simply transplant.