Kuro5hin.org: technology and culture, from the trenches
create account | help/FAQ | contact | links | search | IRC | site news
[ Everything | Diaries | Technology | Science | Culture | Politics | Media | News | Internet | Op-Ed | Fiction | Meta | MLP ]
We need your support: buy an ad | premium membership

[P]
Introduction To and Experiences With Mac Cocoa

By epepke in Technology
Sun Sep 01, 2002 at 05:39:22 AM EST
Tags: Software (all tags)
Software

Cocoa is the native system for developing applications on the Apple Mac. This article introduces Cocoa and describes my experiences after about a year implementing a complex application in Cocoa using Objective C.


The Target Task

About two years ago, I had a desire to replay one of my old adventure games, a SegaSoft game called The Space Bar. I became distressed when it would not work on my newest machine. I became more distressed when I went to the SegaSoft site and found no mention of it whatsoever, even though it was only three years old. I was, eventually, able to hack it into working, but the experience pointed out a bigger problem, that of abandonware.

Many people have suggested that computer games are an art form. Others think the word "art" is too big and prefer to call them entertainment. Whatever one's view of the semantics, games are a major cultural phenomenon, already comparable in revenues to the film industry. It seems likely, or at least possible, that people will want to study the history of games a hundred years from now. Yet they are now only in their infancy. The major problems with early films were twofold: the use of explosive, rapidly aging cellulose stock, and the technical complexity of entering the field. Technological improvements paved the way for films to become at least a polished form of entertainment, and in some instances, genuine art. Technology isn't the solution to every problem in the world, but removing technological obstacles is often to the good. Analogous in the computer game field are abandonware proprietary systems and the difficulty of using the tools. I set out to ameliorate these barriers by developing a new game editor and environment with the working title of CAGEE. Phase I of this development addresses cinematic adventure games; Phase II will build on this core to address 3-D geometric games.

Many people have told me this is futile and pointless. I don't much care; at least I'm having fun. Anyway, I'm presenting this only as the backdrop for my experience with Cocoa.

Although I have worked on much larger software projects before, this one has presented certain challenges. I decided that all of the runtime environment should use free software exclusively that could be made to run on many platforms but which did not restrict in any way the copyright or licensing of the actual games produced. Technically, this is easily done, as most games have stripped-down user interfaces that are easy to duplicate under a minimal API assuming a keyboard, mouse, and screen. I also decided that the editors should, on the other hand, provide the kind of user interface that is expected on the target machine. The less a user is annoyed, the better work they do. At the same time, the editor needed to share nearly all of its code with the runtime environment to avoid incompatibility issues. It could not be a toy system, like WorldBuilder, but is required to provide performance and features comparable to any commercial games, which requires a fast language.

I decided to do simultaneous development in Mac Cocoa and vanilla C with Posix file access for a VT100 terminal or even a glass teletype. I figured these two environments were far enough apart that any future planned systems (X on various UN*X flavors, Win32 and Win64, Palm OS, and whatever else I can't think of) would have a good chance of lying somewhere between these two.

History of Cocoa

The basic ideas of Cocoa came from NextStep, a system developed for the NeXT computer system. Like the Xerox Alto, the NeXT contained innovative ideas on hardware that was not up to the task. Later, this spawned OpenStep, an open specification with a free GNU implementation. When Steve Jobs came back to Apple, he brought these ideas to the Mac. Cocoa is intended to be used for all new development on the Mac, replacing Carbon, the partial substitute for the old Macintosh Toolbox routines. Its NextStep history lives on in the fact that every class and type starts with "NS." OpenStep still exists but is somewhat behind Cocoa in refinement.

Cocoa is natively programmed in Objective C, although it can also be programmed in Java. Objective C is a curious little language. It is about as old as the beginnings of C++ and was reasonably well developed some years before it was clear that C++ was going to be a big success. Unlike C++, Objective C adds little to the C language syntactically, but what it does add causes Objective C programs to look very unlike C. Although built around C, Objective C is more closely related to the ideas of Smalltalk 80. Like Java, it is a fully dynamic language with internal class introspection, a feature essential to the Cocoa API.

As a result of its history with NextStep and the free software community, Cocoa, unlike X, Win16 and the Macintosh Toolbox, has been fairly mature since the first day it appeared on the Mac.

Overview of Cocoa

Cocoa is deceptively simple. It consists of the Foundation and the Application Kit. Each has on the order of two hundred object classes and a couple dozen protocols (somewhat analogous to interfaces in Java). Nevertheless, these objects provide quite a lot of compact power. For example, three classes provide all needed OpenGL functionality, two classes handle URLs and net connectivity, one class provides Undo capability, and one class provides a multiple-font, formatted text editor that supports a good chunk of Rich Text Format (RTF). The bigger classes tend to be somewhat broad in functionality. The NSString class contains methods for formatting file names, encoding and decoding UTF-8, and getting a string via a URL.

Cocoa is claimed to rely heavily on the "model/view/controller paradigm." (For those who are more familiar with traditional C++ design, this corresponds roughly to "business class/interface class/connections between them." In reality, the situation is more complex. Controllers are used to do many things other than communicate between user-interface objects and computational objects. For example, the NSDocumentController is responsible for creating the correct NSDocument objects with opened files based on file type or extension. In addition, there is a catch-all delegate role for many objects. It is not at all uncommon for a nominal controller or other object to function as the delegate for several objects in addition to its primary role. Perl may be the epitome of TMTOWTDI (There's More than One Way to Do It), but Cocoa comes fairly close. This can cause some confusion as to which is the "correct" way, that is, the way that minimizes the chances of painting oneself into a corner in the future. In Cocoa, the way that is easiest generally turns out to be the best.

This characterizes most of my experience with Cocoa. I am used to Java, Win32, Palm OS, X, the old Macintosh Toolbox, SGI, and sloppier systems. They have trained me to intuit that, while everything is fine when you do exactly the kind of tasks in the examples given for the API, once you start to vary even a little bit as you need to do a real application, it rapidly becomes more productive to rewrite parts from scratch. Cocoa almost never gives that experience. Almost always, there is a clean way of doing anything. As a result, Cocoa takes some getting used to for the seasoned veteran but thereafter is largely delightful. Cocoa has some inconsistencies and historical problems (e.g. NSString does not properly distinguish between Unicode and UTF-16), their number is remarkably small. Implementation problems are more common than design problems.

The Development Environment

The development environment consists of the Project Builder and the Interface Builder. These are freely downloadable from Apple and are also available on a disc for about $20. They come with plenty of examples and documentation. Some of the documentation is incomplete or contradictory, but there is enough to do effective Cocoa programming with little or no recourse to other materials.

Project Builder (PB) appears as a fairly standard IDE. It has projects with a hierarchical view of files, a class browser, global find, debugging buttons that look like a VCR, just what you'd expect. It is interesting, however, in two ways. One is that it is aware of the Interface Builder and the Cocoa application structure. The other is that it is basically a skin over a modified version of the GNU gcc compiler and debugger, albeit one that is so well integrated it is hard to tell the difference between the experience of using it and of using CodeWarrior or Visual Cafe. The debugging buttons and integrated breakpoints work fine, but it is always possible for the power user to drop into the debugger using text.

Before going on to the Interface Builder, a few words about Mac applications are needed. Mac applications traditionally used resource forks, which provided some information for the running application, including strings, window positions, etc. Resources are also used in the Windows world, though not for as many uses. The traditional Macintosh resource fork was an extension to the file system, only available on the Macintosh (though the Be OS later expanded on these ideas). With the new UN*X (Mach/BSD) underpinnings of OS X, this additional fork becomes more problematic. While OS X is normally installed with a file system that supports them, UN*X is UN*X, and when talking about files, that means Posix. The resource fork goes away. In its place is a thing called a bundle. A bundle is a UN*X subdirectory that behaves as a single file to the GUI (but not to the shell, making it easy to look inside them). This bundle contains a number of files, including the following:

  • Executable code
  • Libraries (serving a similar purpose to DLLs)
  • Strings, images, and other, possibly organized by language
  • Nib files, possibly organized by language

As anyone who has every built an object system completely from scratch knows, it's the 5% that doesn't drop straight out of the object paradigm that's the real bear. Every object system has a certain amount of FM (more politely, "magic") to deal with this 5%. Nib files hold most of the Cocoa FM. A nib file specifies a set of classes and instances used as dynamic templates by the application.

Interface Builder (IB) is the program that edits nib files. It appears as an ordinary visual window layout manager. I have a violent allergy to visual layout managers in general, finding them clunky and confining. This is probably the first I have ever seen that is worth using.

While PB and IB can build many types of applications and libraries, the most common is the document-based application, which permits multiple documents, each with one window (usually). These applications usually have at least two nib files: one for the main menu and one for each distinct kind of document. They may have other nib files for dialogue windows, etc., but these (and the main menu) are easy. The document nib files are more interesting.

Document nib files typically contain at least three objects:

  • A window
  • The file's owner
  • The first responder

The window is straightforward; it can be edited and filled with views in a manner familiar from many other systems. Users of Swing or even the older Java AWT will recognize how views are laid out within windows as a simpler analogue to those systems. The file's owner is somewhat flexible. It is usually an instance of the document class but may be a controller or other object as well. It is called the file's owner because the code for this object in the project is responsible for loading or at least specifying the name of the nib file to load. The first responder, however, is pure FM. It is a placeholder for any object that is found at run time by traversing the "responder chain." This includes any object that might possibly be interested in a user interface action, such as a button press or text box change.

Objects are linked to each other two ways: through outlets and actions. When a document is loaded, the run-time system consults the nib and creates instances specified within the nib.

An outlet is a variable within an object that is linked to another object at run time. An action roughly corresponds to an event in Javascript, such as button pressed or text box changed. Each object can only produce one action, but in effect this is not a large limitation, as what that action means can be varied. (For example, a slider can report its value either while sliding or after release.

Outlets and actions are identified within code using the IBOutlet type modifier for outlets and IBAction for actions. These are typedefed to innocuous values at compile time but still alert the compiler to produce some of the FM for the run time. Beyond this, there is no hairy extra syntax that needs to be maintained in the source to have it communicate with the nib files.

This raises the question of how the Project Builder and Interface Builder keep in sync. There are two ways, both essentially manual.

One can use the Interface Builder to create files for classes that have been defined within it. This is usually the best option when starting a new nib. It provides partial source files that can be expanded into functioning code. However, if you change your mind and add a new outlet or nib later, this will ask you if you want to merge the new files with the existing files. If you say so, it will bring up Merge, a manual merging tool that looks like an inferior version of SGI's gdiff. It is inferior both because the user interface is highly confusing (arrows point to the text to get from, in the style of weather diagrams, rather than showing where text will go) and because it has a funny idea of how to find similarities. Merge is somewhat usable, especially with the option to include both, but it is dangerous if you aren't careful.

A better way is to type the new outlets or connections into the header file and have IB read the file. It then sets up its internal information correctly. One caveat is that the class and subclass structure in Interface Builder must be the same as and use the same names as the code. Otherwise, IB will complain.

It is easy to subsitute custom subclasses for existing objects. An NSView, for instance, can be of any "custom class" that makes sense, including all of the programmer's subclasses of NSView.

Adaptation

Although this system looks like it would be fine for simple, classical applications, the question is whether it can be used to produce a real application? The answer appears to be "yes."

Due to the need for platform independence, I elected to specify a narrow channel of glue to go between the computational part and the user interface. The computational part is written in vanilla ANSI C, implementing a full LISP mark-and-sweep garbage-collection memory system. Nothing in the LISP portion is specific to Cocoa. (At first, the LISP strings were allowed to use either UN*X malloced arrays or NSStrings based on conditional compilation. Fortunately, I soon realized what a bad idea that was. Now, they are UN*X malloced strings explicitly specified to be UTF-8 encodings, converted to and from NSString objects when necessary. Although this requires a minimal amount of extra overhead, it is more than made up for by not being tied to a myopic implementation of Unicode.) There are seven Objective C glue classes, most of them very simple. The most important is GlueObject, which links a LISP object to a Cocoa object. Also important is GlueBinding, which makes it easy to write a user interface element in Cocoa or another system to edit the binding of a variable within an object. Each glue has a pointer to the LISP structure, and there is a special LISP expression called a "reference" that can hold a pointer of the appropriate size for the architecture.

The differing memory management systems required some thought. LISP, of course, has a full mark-and-sweep garbage collector, much like Java. Objective C has only a scheme based on reference counts. Fortunately, there are very strict rules on the ownership of objects that, when rigorously followed, prevent memory leaks. Any object that creates another object using the alloc method is responsible for releasing it when done. There is also an autorelease mechanism that keeps an object around long enough for it to be used, which lets factory methods work. To keep the LISP system in sync with the Objective C memory, I decided that the LISP system should work with three rules:

  1. Retain the object it references from LISP in addition to any other object that retains it
  2. Only delete during mark-and-sweep if nothing outside the LISP system has retained the object
  3. If a reference should not be deleted, neither should the LISP object that refers to it

To maintain independence, how to query whether an object is owned elsewhere and how to delete it is specified in vanilla C in the glue code.

CAGEE is written both in vanilla C and in C++, the latter mostly for interfacing with external libraries such as Apache Xerces. This turned out to be easy in the Project Builder which handles vanilla C, Objective C, C++, and Objective C with C++. The last does not, of course, provide any magic to make C++ look like Objective C or vice versa; the models are too different for that to be practical. It does get the job done, though.

Because CAGEE is a project-based application, it is a little bit more complicated than the typical document-based application. The project document owns and refers to additional documents which may or may not be stored in files. Unlike PB, CAGEE does not simply refer to other files but keeps the LISP code of a subfile in memory, accessible to the project. First, I had to rewrite the functionality of the New menu item to a New Project... item that asked to save first rather than allowing an untitled document to be saved later. PB, of course, does that, but with a somewhat baroque assistant mechanism. I just used the standard file save dialog window, adding a single view to allow the user to specify whether a new subdirectory should be created for the new project. Close Save, and Save As... continued to work fine. For the subdocuments, I also had to modify New to a context-sensitive system that created a document of the right kind (I still didn't want to have an assistant, which I view primarily as an indictment that the user interface is more complex than it should be). Everything else worked fine. The standard Cocoa document methods caused a modified LISP print to produce XML on output, and Apache Xerces to read an XML file on input. To open double-clicked files automatically, I added an openDocumentForObject: fromDoc: method to the shared MasterDocumentController, a custom subclass of NSDocumentController.

Undo was a bit tricky. Using the NSUndoManager, it is very easy to do any kind of undo and redo, provided that everything is in Cocoa. Unfortunately, it was also necessary to synchronize with the LISP memory system. The NSUndoManager does not retain arguments passed to it in order to avoid making circular references. There is a method to delete all undoable and redoable actions involving a certain object, but this is an extremely dangerous cheat. Undo and redo are only mathematically justifiable when you can guarantee that no undoable action can ever possibly be affected by any other action. To get around this, I bit the bullet and maintained separate LISP undo and redo stacks, with the obvious synchronization and cleanup when a window closes. It is not particularly elegant, but it has the pleasant side-effect of providing most of the work of undo and redo in vanilla C.

Unlike previous Macintosh development systems and to a lesser extent Windows and Java systems, Cocoa is friendly about the command line. When debugging, any stdio or cin/cout activity uses a panel within the Project Builder. I have a LISP read/eval/print loop patiently waiting in a separate thread. It is handy for debugging and testing, such as forcing a garbage collection in a critical state. The input facilities go away if the application is double-clicked, however.

Conclusion

I hope this article has given an impression of what it's like to use Cocoa. Yes, it's not only possible to do a fairly complex application in Cocoa, but it's as close to being fun as any API I've seen in a long time.

Sponsors

Voxel dot net
o Managed Hosting
o VoxCAST Content Delivery
o Raw Infrastructure

Login

Poll
Cocoa?
o Good 40%
o Bad 0%
o Ugly 2%
o Never tried it 36%
o Never tried it, never will 7%
o No, thanks. I'll have a beer. 6%
o I prefer Malteasers 7%

Votes: 83
Results | Other Polls

Related Links
o Cocoa
o Posix
o VT100
o X
o Palm OS
o NextStep
o OpenStep
o Carbon
o Macintosh Toolbox
o Objective C
o Java
o C++
o Rich Text Format (RTF)
o TMTOWTDI
o SGI
o Unicode
o GNU gcc
o CodeWarrio r
o Visual Cafe
o Mach
o BSD
o LISP
o Apache Xerces
o Also by epepke


Display: Sort:
Introduction To and Experiences With Mac Cocoa | 47 comments (42 topical, 5 editorial, 0 hidden)
Can you recommend a good starter book? (4.00 / 1) (#2)
by borful on Sun Sep 01, 2002 at 01:32:16 AM EST

I just got a Mac (for digital video editing) and I'll eventually want to start writing my own apps. I've programmed for a long time, but never on a Mac. (Apple basic on a Apple][ was my last Apple programming language.) I'm learning Java at work; would it be better to go with what I'm more familiar with (but still not expert in) or with the native Objective C? I've coded in C; how different is Objective C?

-borful
Money is how people with no talent keep score.

Books and stuff (none / 0) (#4)
by epepke on Sun Sep 01, 2002 at 02:03:31 AM EST

I don't know of a good starter book. I was programming several months without a book at all. The free stuff comes with a tutorial for a fairly toy currency converter program.

I did buy Building Cocoa Applications, an O'Reilly book. It's pretty good and has some neat tricks in it. However, I don't consider it really to be a starter book. It's about half advanced.

I'm not sure that Java would be a better choice. I'm pretty much an expert in Java and have been using it (and Perl) professionally for about two years, to the exclusion of my C skills. But I like working in Objective C better. For one thing, I always know I can write some really fast code in C if push comes to shove.

Objective C looks really weird, but the number of really weird things is so small that it's easy to get used to. Here's an example from my code. I apologize if it looks bad, but K5 doesn't seem to support <pre>



- (NSString *)windowTitleForDocumentDisplayName:(NSString *)displayName
{
/* Have to override this because the Project Builder does not satisfy the Mac UI guidelines with
untitled as lower case */
NSMutableString *s = [NSMutableString stringWithString:displayName];
NSRange r;

r.location = 0;
r.length = 8;
if (NSOrderedSame == [displayName compare:@"Untitled" options:NSLiteralSearch range:r])
{
r.length = 1;
[s replaceCharactersInRange:r withString:@"u"];
}

return s;
}

So, there are three weird things here. One is the declaration of the method. Another is the stuff with brackets, names, and colons, which is a call to a method. The third is the @" construct, which just means make a literal string as an NSString. It's odd, but this example shows about 90% of what you have to get used to.


The truth may be out there, but lies are inside your head.--Terry Pratchett


[ Parent ]
Method calls? (5.00 / 1) (#6)
by DJBongHit on Sun Sep 01, 2002 at 02:28:17 AM EST

Another is the stuff with brackets, names, and colons, which is a call to a method.
Not exactly - that's sending a message, not calling a method. You can send a message to an object even if it doesn't have a handler to handle it. You'll get a runtime error (though not a fatal one), but the compiler won't care. Note that this is only the case if the object is declared as type id - if you declare the object as its actual type, the compiler will complain if you try to send it a message it doesn't handle, but this is only as a programmer safeguard.

This is necessary, for example, for when you are writing a delegate for an NSTableView. Your delegate can respond to certain messages to do things like allow or disallow selection of certain rows. When a user selects a row from an NSTableView, something to the effect of the following happens:

bool shouldSelect = true;
SEL theSelector = @selector(tableView:shouldSelectRow:);

if ([delegate respondsToSelector:theSelector])
{
  shouldSelect = [delegate tableView:self shouldSelectRow:rowIndex];
}


You can't do things like that in other languages, because the methods a class has are determined at runtime. Because passing a message isn't the same as calling a method, it allows you to do things like the above - checking if a class can do something before you try to make it do it.

~DJBongHit

--
GNU GPL: Free as in herpes.

[ Parent ]
OK (none / 0) (#8)
by epepke on Sun Sep 01, 2002 at 02:59:19 AM EST

But I know a lot more people are familiar with C++ than Objective C, so the concept of sending a message is probably not very familiar to them. I said "method calls" because it seems to me to be a reasonable linguistic compromise between "calls to member functions" and "sending a message." I cut my teeth on Smalltalk 80 and began implementing object-oriented systems around 1984, so I do know the difference. I've long held that the original ideas of O-O programming have been perverted, and there's a big cultural gap between object programming and "class" programming, but hey, when you're trying to talk to people who have been exposed to other ideas, what can you do? Note the nod to "business classes" and "interface classes" in the main text, which are phrases I normally wouldn't use.

It's like, back in the days of my youth, trying to convince people who were really into structured programming that you really could produce better code with the occasional break or return. One just can't do it; they have to find out for themselves. I once had someone tell me, quite seriously, that a program that crashed when is ran out of memory was better than a memory system that returned NULL and gave the program a chance to do something about it.

I'm not a religious warrior; I gave that up back when people argued Pascal versus C. I once posted a concept equivalence chart for Pascal programmers who wanted to understand C. I got upbraided for not beating the bongos on behalf of pointer arithmetic. Oh well, whatever. I can use pointer arithmetic. I figure that once people get into something, they'll find out what it's like.


The truth may be out there, but lies are inside your head.--Terry Pratchett


[ Parent ]
Yeah (none / 0) (#10)
by DJBongHit on Sun Sep 01, 2002 at 03:23:26 AM EST

But I know a lot more people are familiar with C++ than Objective C, so the concept of sending a message is probably not very familiar to them. I said "method calls" because it seems to me to be a reasonable linguistic compromise between "calls to member functions" and "sending a message."
Fair enough. To the average programmer, they're one and the same. I just felt like being pedantic :)

I cut my teeth on Smalltalk 80 and began implementing object-oriented systems around 1984, so I do know the difference.
Hmm. In 1984, I think I was more concerned with not shitting in my drawers.

~DJBongHit

--
GNU GPL: Free as in herpes.

[ Parent ]
better version (5.00 / 1) (#33)
by sabi on Sun Sep 01, 2002 at 05:26:06 PM EST

Don't forget that 'Untitled' is localized. (This is from my still-unreleased HostLauncher app, where the documents appear as "untitled connection", "untitled connection 2", etc...

- (NSString *)windowTitleForDocumentDisplayName:(NSString *)displ ayName {
    // only override window titles  not associated with a file
    if ([[[self document] fileName] leng th] == 0) {
        // "Untitled" >>  "untitled" (otherwise violates Aqua HIG!)
        displayName = [display Name lowercaseString];
        if ([displayName range OfString: @" "].length == 0)
            //  "untitled" >> "untitled connection"
            displayN ame = [displayName stringByAppendingString: @" connecti on"];
        else
            //  "untitled 2" >> "untitled connection 2"
            displayN ame = [displayName stringByReplacingOccurrencesOfString: @"
" withString: @" connection "];
    }
    return displayName;
}


[ Parent ]

Yeah, that was just a hack (none / 0) (#36)
by epepke on Sun Sep 01, 2002 at 08:38:47 PM EST

I put it in just because the upper-case U was bothering me. In the future, I plan to change it so that it gets the "untitled" string from the language localization system. I had to put some glue on that, too--I still want to do localization under other operating systems, even if it's klunkier.


The truth may be out there, but lies are inside your head.--Terry Pratchett


[ Parent ]
Good book (5.00 / 1) (#5)
by DJBongHit on Sun Sep 01, 2002 at 02:11:43 AM EST

Cocoa Programming for Mac OS X, by Aaron Hillegass. It's a great book, and gives a good primer on Objective C as well as the Cocoa/OpenStep APIs.

As for your question on Objective C, definitely learn it and use it in lieu of Java when writing Cocoa apps. It's not hard to learn, and the performance will be much better. There's a bit of new syntax on top of C, but not a whole lot, and it's nice stuff. There's a reason that OpenStep APIs have long been considered one of the nicest programming environments around, and the choice of language is certainly part of it.

Let me give you a little quote from the aforementioned book from the first section in the chapter "Using Java with Cocoa":

Don't Use Java to Write Cocoa Applications

To please the many people who want to write applications in Java, Apple has done a clever hack that enables programmers to access the Cocoa frameworks from Java. The system that translates Java method calls into Objective-C calls known as the Java bridge.

The Cocoa frameworks were written in and for Objective-C. If you are going to write Cocoa applications, use Objective-C, C, and C++. Your application will launch faster and take up less memory than if it were written in Java. The quirks of the Java bridge will not get in the way of your development. Also, your project will compile much faster.

If you are going to write Java applications, use Swing. Swing, although not as wonderful as Cocoa, was written from the ground up for Java. Remember: The big selling point of Java is that applications written in Java are easily portable to any operating system. If you write a program with Cocoa, it will run only on Mac OS X.

The chapter does continue on and talk about how to use Cocoa with Java, though.

~DJBongHit

--
GNU GPL: Free as in herpes.

[ Parent ]
Seconded (none / 0) (#34)
by Otter on Sun Sep 01, 2002 at 07:03:33 PM EST

That's a terrific book, far better than the Apple/O'Reilly Learning Cocoa shovelware book.

I have to say, though, that after getting about a third of the way through the Hillegaas book, Qt continues to be a much more attractive API...

[ Parent ]

Cocoa vs. Qt (none / 0) (#37)
by DJBongHit on Sun Sep 01, 2002 at 09:14:02 PM EST

I have to say, though, that after getting about a third of the way through the Hillegaas book, Qt continues to be a much more attractive API...
I used to like Qt a whole lot, until I got to know Cocoa better. It seems to me that the reason that Qt is so nice is that they use moc to fake in C++ what Objective C does natively (faking message passing with signals/slots).

Anyway, if I have to write something for any platform other than OS X I certainly use Qt since I have a full developer license for it and can license my code however I want (or SDL if I'm writing a game, which I'm currently doing). Qt is very nice, but I like Cocoa better.

~DJBongHit

--
GNU GPL: Free as in herpes.

[ Parent ]
Good Starter Book (none / 0) (#29)
by bsletten on Sun Sep 01, 2002 at 01:41:31 PM EST

I found Simson Garfinkel and Michael Mahoney's "Building Cocoa Applications : A Step-by-Step Guide" from ORA to be a good introduction. I don't normally tend toward hand-holding books but I found Cocoa different enough that jumping into existing source was more confusing than helpful. This book is a decent introduction to the tools, Objective-C and the AppKit and other libraries. You might look into whether the new Jaguar Dev Tools will require a new version of the book but I don't imagine it will.

I'm still just getting into Cocoa but it seems to be a fun, powerful and well-designed toolkit so far.

[ Parent ]
Java/Swing (4.00 / 3) (#11)
by SPasmofiT on Sun Sep 01, 2002 at 03:35:57 AM EST

If you code your application in Java, using the Swing API, Apple's SDK/JRE will map your code to native MacOSX widgets. Meanwhile, you get to run the exact same .class file on other machines too.

So, if you're looking for portability, Objective C might not be the best choice, but Java/Swing is.

Yeah, but... (none / 0) (#20)
by bigbtommy on Sun Sep 01, 2002 at 08:07:22 AM EST

for the user, Java just sucks on Mac OS 9. And if your releasing software for OS X, and not OS 9 as well, you ought to get your head tested.
-- bbCity.co.uk - When I see kids, I speed up
[ Parent ]
Considering... (none / 0) (#21)
by mech9t8 on Sun Sep 01, 2002 at 08:21:59 AM EST

...Cocoa is OS X-only, he's still better off writing for Java if he wants it to run on OS 9...

--
IMHO
[ Parent ]
But how about... (none / 0) (#22)
by bigbtommy on Sun Sep 01, 2002 at 08:39:19 AM EST

Carbon for now, until Cocoa/OSX gets wider acception.
-- bbCity.co.uk - When I see kids, I speed up
[ Parent ]
Maybe I wasn't clear (5.00 / 1) (#30)
by epepke on Sun Sep 01, 2002 at 02:51:47 PM EST

My purpose in writing a Cocoa interface is not primarily to have a Cocoa interface per se, but by having two widely different interfaces (Cocoa and dumb terminal or VT100) to force the early design decisions into a proper modularization from the core code. Carbon is a much more "classical" API, and I actually feel more comfortable with it because I'm used to it, but because it is more classical, it's much easier to get sloppy and build in hidden assumptions that will be dangerous in the future.

That's my theory, anyway. Maybe it's wrong, but that's what I'm doing. I'm heavily influenced not only by my own earlier cross-platform experiences but by an article published in Communications of the ACM around 1999 about Netscape's experience with cross-platform design, especially the things they had to rethink when they didn't work so well.

I'm also hoping that, say, 50 years from now, current popular thinking of user interfaces in terms of "widgets" on a flat screen will seem as quant as punch cards seem today, while the basics of drama (plots, characters, action, etc.) will still mean something. Actually, even now, the philosophy seems impoverished. What makes Photoshop a great program that artists love to use? It isn't the look of the "widgets." Business applications are one thing; in many cases it's quite satisfactory to throw some controls onto a "form." But I've already learned I can make a lot of money numbing my mind to write business applications. This is for fun.

Actually, even some parts of the core will need to be rewritten in the future. The artificual intelligence and language modules will necessarily be primitive due to the state of the art. For the language, I'm going to use an HPSG-based implementation based on Fillmore's deep cases for which I did some work in the late 1980's. (I haven't started this part yet, but I have all the old code in LISP/C, one of the reasons that I chose to do the LISP/C thing again.) It isn't terribly sophisticated, but it is capable of generating declarative sentences from state information. For example, if you try to open a locked drawer, it can figure out that the reason you can't open it is because it is locked and tell you so in a natural language from "knowledge" of how drawers work without any game-specific code. (Of course, you can override this behavior if you like.) HPSG will probably become obsolete some day, but I'm hoping that the deep case concept will be salvageable. For the AI, I'm watching the efforts in the game community to standardize AI interfaces.

Also, when I get to Phase II, the 3-D real-time games, something will have to give, but that's too much for my poor brain to think about just yet. I'm keeping Deus Ex in mind as an example of the breadth of things you should be able to do in a 3-D game, whether or not you choose to do them all.

I also have some experience with weird user interfaces. My previous impossible project that worked was a scientific visualization package called SciAn that I started more than ten years ago. It was originally a flat interface, influenced more by the original MacDraw than anything else, but we retrofitted a 3-D interface for the University of Illinois CAVE (essentially a 0th generation holodeck). It worked, but it sure required a lot of rather delicate surgery on the package, and I'm hoping not to paint myself into those kinds of corners. When I decided to take a break from working full-time for a while, I was tempted to rewrite SciAn for Open GL, but then I figured, nah, this time I might as well try something in art and entertainment rather than science.


The truth may be out there, but lies are inside your head.--Terry Pratchett


[ Parent ]
Netscape article (none / 0) (#40)
by unDees on Mon Sep 02, 2002 at 05:12:58 PM EST

I'm heavily influenced not only by my own earlier cross-platform experiences but by an article published in Communications of the ACM around 1999 about Netscape's experience with cross-platform design, especially the things they had to rethink when they didn't work so well.

Sounds interesting. Is it available online?

Your account balance is $0.02; to continue receiving our quality opinions, please remit payment as soon as possible.
[ Parent ]

I don't think so (none / 0) (#43)
by epepke on Mon Sep 02, 2002 at 08:27:00 PM EST

Not for free, anyway. It's probably available through the ACM digital library. You might do a search on www.acm.org.


The truth may be out there, but lies are inside your head.--Terry Pratchett


[ Parent ]
Non-free link... (none / 0) (#46)
by unDees on Tue Sep 03, 2002 at 12:49:01 PM EST

You're correct on both counts. The article appears to be at http://doi.acm.org/10.1145/317665.317678, but it requires a paid subscription. Perhaps another time....

Your account balance is $0.02; to continue receiving our quality opinions, please remit payment as soon as possible.
[ Parent ]
Native or Lookalike? (none / 0) (#26)
by The Shrubber on Sun Sep 01, 2002 at 11:07:57 AM EST

Question: is it the case that these are actually mapped on to native MacOSX widgets, or are they just look-a-likes? If i open a save-dialog box, for example, i get the standard Swing stuff (but if an Aquaish look). Does that have anything to do with anything?

[ Parent ]
Native (none / 0) (#38)
by vitriol on Sun Sep 01, 2002 at 09:15:45 PM EST

The Mac OS PLAF is implemented with calls via JDirect (a Mac-specific native interface) into the Appearance Manager. Lee Ann Rucker, a former Sun employee who worked with Apple's Java team was largely responsible for this feature, and if I understand correctly, initially targeted the Classic Mac OS with her native PLAF.

Incidentally, there isn't anything about the design that prevents anyone from implementing the same sort of thing under another OS, though JDirect sure beats JNI for ease of use.

I understand that Apple is now completely rewriting their AWT to run natively in Cocoa rather than on top of the CFM shim. The current state of things is problematic for applications that use the JNI Invocation interface. I'm not sure what this means for the future of the Carbon-based Mac OS PLAF, it may have to be rewritten as well.



[ Parent ]
Coding apps with GUI (3.66 / 6) (#12)
by Betcour on Sun Sep 01, 2002 at 03:38:01 AM EST

My opinion is that anybody who codes "manually" a GUI is a nutcase. You waste hundred of hours trying to make everything work and look pretty, while tools like Delphi/Kylix will do it in a few minutes and with at least equal if not better results (and less bugs). Writing code should be reserved for application logic. Nobody should have to write a single line of code to draw a button or a combo-box.

Cocoa (3.66 / 3) (#13)
by DJBongHit on Sun Sep 01, 2002 at 03:51:58 AM EST

My opinion is that anybody who codes "manually" a GUI is a nutcase. You waste hundred of hours trying to make everything work and look pretty, while tools like Delphi/Kylix will do it in a few minutes and with at least equal if not better results (and less bugs). Writing code should be reserved for application logic. Nobody should have to write a single line of code to draw a button or a combo-box.
With Cocoa, you don't (have to) write code to create your GUI - if you read the article, you'd see that Apple provides a program called Interface Builder where you lay out your user interface visually. You can create the interface with code, but you don't have to.

~DJBongHit

--
GNU GPL: Free as in herpes.

[ Parent ]
Not true (5.00 / 2) (#15)
by carbon on Sun Sep 01, 2002 at 05:47:38 AM EST

This is only a problem if your toolkit isn't built properly for that. For example, look at TK, which is just plain intuitive; you specify you app in terms of frames and hierarchial rows and columns. This has the effect of making the code resemble what you're thinking, and of making certain other things (determining where fixed size and dynamic borders are, setting a correct tab ordering, etc) dead easy, since they're handled by the toolkit implicitly.


Wasn't Dr. Claus the bad guy on Inspector Gadget? - dirvish
[ Parent ]
Tk and Rows and Columns (5.00 / 1) (#24)
by Matrix on Sun Sep 01, 2002 at 10:48:57 AM EST

And many modern GUI toolkits have taken a cue from Tk when it comes to widget arrangement. Java, Qt, and GTK+ all have layout managers that let you do things Tk-style, or something close to it. (GridBagLayout in Java, can't remember what it is in Qt or GTK+) You don't waste much time at all trying to get things to "look pretty". (Well, once you've learned how to use the tools in question, that is)


Matrix
"...Pulling together is the aim of despotism and tyranny. Free men pull in all kinds of directions. It's the only way to make progress."
- Lord Vetinari, pg 312 of the Truth, a Discworld novel by Terry Pratchett
[ Parent ]

Coding GUI manually (5.00 / 2) (#19)
by Znork on Sun Sep 01, 2002 at 06:47:32 AM EST

In my experience the coding of the actual GUI takes maybe a few hours, most of which is very easy and consists mostly of cut'n'paste programming. I've tried various GUI builders, but I just cant see any benefit. More often parts of the GUI just cant be implemented with the GUI builder since those parts are custom widgets anyway, which usually means you have to fight the GUI builder in the end.

The complex code is the application logic. If you cant even get a couple of hundred lines of easy GUI code right without a GUI builder, how well will the application logic work?

[ Parent ]

The Designer can handle all of this (none / 0) (#32)
by Peaker on Sun Sep 01, 2002 at 05:22:29 PM EST

Save your time and just define your custom widgets and throw them around in your widgets.

If you bother to compile your custom widgets as 'plugins' for the Designer, it can also show you how it'll look in real time, taking you minutes to code it all, without a single disadvantage.

A good GUI is also made of hierarchic layouts (Unlike Bad GUI's, Microsoft style, that just have hard-coded coordinates with 'anchoring' of pixel distances).
These hierarchic layouts are much harder to code than to draw, especially in a fantastic tool like the Qt Designer.

[ Parent ]

Custom GUI fun (4.00 / 2) (#25)
by Udderdude000 on Sun Sep 01, 2002 at 10:52:48 AM EST

If you are writing an app that uses a custom GUI, you aren't going to want standard stuff.  Usually the only solution is to rewrite it, but you don't have to make it as complicated.  This happens alot with games (surprise!) and some art programs.

- Chris

[ Parent ]

Coding GUI is practical! (5.00 / 1) (#28)
by tftp on Sun Sep 01, 2002 at 01:04:01 PM EST

You waste hundred of hours trying to make everything work and look pretty, while tools like Delphi/Kylix will do it in a few minutes and with at least equal if not better results (and less bugs).

That often is not the case. I use Qt. It comes with a program that is equivalent to Kylix and other "visual" tools. I never used it.

The biggest problem is that your interface can not be dynamic. If you need to produce 100 dialogs then maybe such a designer tool will help. But if you craft control panels for complex and flexible software and hardware (as I do), then you will find that the designer application is useless.

For example, I insert and remove controls as necessary, depending on the purpose and run-time requirements of the widget. Completely different controls are used in the same GUI container for different types of hardware. You can not easily design that in a form. Another issue is size of everything - size of fonts in particular. I have methods to resize controls as they should be, at run time, depending on what font the operator selected for the application (some users have 800x600 screens, other - 1600x1200 and bigger). All this is done with full awareness of the geometry of entire window.

Yet another reason to avoid GUI designers is programmability. I can (and do) have methods that insert predetermined controls when called. This way I can assemble a large GUI from components in matter of minutes - faster than drawing and clicking and dragging in a GUI designer.

[ Parent ]

This is all possible with the Designer (none / 0) (#31)
by Peaker on Sun Sep 01, 2002 at 05:19:47 PM EST

The designer takes care of automatic layout of font sizes/etc.

It is also possible to use Custom Widgets that can 'contain' whole setups that you build in code 'building blocks'.

Also, it may be a good idea to have the same GUI for different hardware, either by abstracting the interface a little more, or by graying out some options in some cases (Also allowing the user to know what he's missing).

Also, you can always put all of the optional controls in the container and just hide/show the proper ones. Its pretty easy to get this done in a way that the Designer will make it all seamlessly integrate beautifully.

[ Parent ]

Could be done, possibly. (none / 0) (#35)
by tftp on Sun Sep 01, 2002 at 07:05:07 PM EST

The designer takes care of automatic layout of font sizes/etc.

That is good, but I still prefer (!) to do it in code. I am not a visually oriented person, but rather a logically oriented one. Also, some of controls are my own custom widgets, and they may need some handholding before they are properly shaped. If I construct them and insert into the layout by hand, I know what I am doing. If I do that in the designer, I am not so sure. But probably this is only because I am not a designer guy... if I were, I'd find the way. I just dislike mousing around.

graying out some options in some cases

I do that if some options are just temporarily unavailable - in this mode, or with this setting of other controls. But I don't do that if the differences are too big - for example, when a shared code supports several models of hardware, and these models have even different purpose, let alone controls... then the user is not "missing" anything, he doesn't even have the right hardware for those controls.

you can always put all of the optional controls in the container and just hide/show the proper ones.

Yes, I do that in code to collapse and open some control panels. I usually don't do that just to hide unwanted controls because this is just too messy... but yes, it can be done.

[ Parent ]

Holy shit! (4.42 / 7) (#16)
by leviramsey on Sun Sep 01, 2002 at 06:11:36 AM EST

A tech article makes K5's front page!

Did the server problems last night cause a polarity shift or something?



Nah (5.00 / 1) (#17)
by carbon on Sun Sep 01, 2002 at 06:34:10 AM EST

Not unusual, my C++ article made FP a few days ago, and I'm not exactly a groundbreaking writer or brilliant coder. I think everyone just agrees that k5 is tech article starved, so any that appear are voted up like crazy. Possibly a good time for me to spread my Perl seeds of evil...


Wasn't Dr. Claus the bad guy on Inspector Gadget? - dirvish
[ Parent ]
Correction (none / 0) (#18)
by carbon on Sun Sep 01, 2002 at 06:35:01 AM EST

Editors, could you s/days/weeks?

Wasn't Dr. Claus the bad guy on Inspector Gadget? - dirvish
[ Parent ]
thanks (none / 0) (#23)
by DapperDan on Sun Sep 01, 2002 at 10:06:39 AM EST

++epepke!

Thanks for this article. It answered lots of questions I had about Cocoa. I'll be sure to spend more time learning it now. Your app sounds cool as well; hope it makes it onto sourceforge.

It's encouraging to see that it was possible (easy?) to integrate your vanilla C LISPish memory system with the Cocoa runtime. I think that shows that Cocoa isn't an island unto itself. I was playing with camelbones (Cocoa in Perl) last night and that looks cool as well.

Peace.

Hear, hear! (none / 0) (#27)
by obsidian head on Sun Sep 01, 2002 at 11:56:24 AM EST

I was also hoping a quick release would be made, even if much can't be expected from it.  Especially since I'm far more curious about the fundamentals than a polished version.  (The polished version might obscure the basics.)

I'd just appreciate a simple tarball.  Many people probably don't release early because large design changes are still being made, and dealing with CVS becomes a pain.

[ Parent ]

Well, you know (none / 0) (#45)
by epepke on Mon Sep 02, 2002 at 09:30:13 PM EST

In my previous experience with an Open Source project (done long before the term "Open Source" and just at about the time that the FSF was getting its act together), I didn't release until something was basically working. With this project, there's the special consideration that the design is so many orders of magnitude more important and harder than the implementation. Back a year ago, I tried to find people who were willing to help with the design, and I pretty much failed. The ideal people would be screenwriters/authors/hackers. If you want to sign up, please do. My address is epepke@acm.org.

Furthermore, most of the past year I have spent developing the engineering prototype in the Mythical Man Month tradition: one to throw away. I've only been working on what I might laughingly call the real code for a month or so. A lot of stuff, such as the LISP system and glue, transferred without a hitch. However, I saw several game-development panels at SIGGRAPH, and they caused me to rethink how I wanted to present the task to the user. Rather than my original idea of a tool to create and edit games, I now want a tool to enable the game-development process. There is a huge difference. So I have gone back to re-organize the program based on what someone who might want to produce a game and doesn't know how might need and want to do. I'm currently trying to get the system to the point where it can produce the Ugly Little Room, the starting point for a game I've been nursing for a while. When it gets to that point, I'll definitely do a developers' release.

But anyway, this was really just an article about Cocoa, not about the project.


The truth may be out there, but lies are inside your head.--Terry Pratchett


[ Parent ]
Cocoa vs. Aqua? (3.00 / 2) (#39)
by dachshund on Mon Sep 02, 2002 at 01:54:03 PM EST

Could anyone explain why one might choose to use the native Cocoa interface rather than the older-style Toolbox interfaces in Aqua? Are there performance differences, are there different capabilities, or is it really just an alternative? Additionally, does Apple recommend that developers port to Cocoa rather than Aqua, or are they agnostic on the issue?

Also, how much of a difference is there between Aqua and the old pre-OS/X toolbox? I notice that a number of applications still haven't been ported, so I assume there may be some non-trivial obstacle that prevents you from simply rebuilding (though I suppose many of those apps may simply need to be redesigned so that they take advantage of a true multitasking OS rather than doing tricky stuff with interrupts.)

Re: Cocoa vs. Aqua (none / 0) (#41)
by vitriol on Mon Sep 02, 2002 at 06:12:23 PM EST

Aqua is merely the name Apple has chosen for the Mac OS X UI, the name of the "theme" if you will. The API you're using is Carbon, and there is very little difference (if any) between the Mac OS 9 Appearance Manager and the one in Carbon.

The difference between Cocoa and Carbon is huge. Cocoa isn't just a set of OS interfaces, it's an application framework. One might use Cocoa for the same reasons so many Classic Mac OS developers chose to use THINK or PowerPlant, the Toolbox is not an especially friendly place for the uninitiated. Carbon preserves a great deal of the bugs and inconsistencies in the interest of compatibility.

My former employer was unable to port a number of our apps because they either lost the source code or the app was dependent upon a third party library for which they didn't own the source. Either way, we couldn't make the leap without a full rewrite, and with the tech economy in the toilet, a cash outlay of US $100,000 or more was too much to choke down with only 10% of Mac users onboard with Mac OS X at this point. I avoid proprietary libraries so I can say cheerfully that I have never been in this position myself. *crosses fingers*

Intelligently written code ports over pretty cleanly, but the sorts of dumb moves that lead to the inability to port an application to X are common.



[ Parent ]
Easier, better, faster. (none / 0) (#42)
by oscarmv on Mon Sep 02, 2002 at 06:30:53 PM EST

Unless you have a big pre-OS X code base and/or you're very familiar with the Carbon Toolbox (which is mostly the same as the old Mac Toolbox), you're far better off using Cocoa. Carbon has a lot of legacy stuff around, plus it's a behemoth and takes selling your soul to the devil to learn to get it to do fairly common tasks. That said, Apple is trying hard to keep feature parity between both APIs, and in many cases you can use Carbon calls inside of Cocoa code without problem, and it has also recently allowed to use nib files for Carbon apps. Apple actually recommends Cocoa over Carbon, but after some backlash by historical developers are now far less vocal about it (hence the feature parity and so). The porting problems may be due to a number of causes. Applications that do serious hw level dirty stuff would have to be redesigned, many others aren't due to laziness, the developers having moved on to something else, or whatever. There are big differences in the philosophy of the underlying OS that must be taken into account when designing, and they may affect many other issues (I'm not particularly proficient on the old Mac Toolbox so I can't comment). Hope that helped.

[ Parent ]
Cocoa and Carbon (none / 0) (#44)
by epepke on Mon Sep 02, 2002 at 08:59:04 PM EST

Could anyone explain why one might choose to use the native Cocoa interface rather than the older-style Toolbox interfaces in Aqua?

There seems to be some confusion here. Aqua is the look and feel of OS X, and to a lesser extent (here it becomes a bit hazy) the underlying graphics system. The more accurate comparison is between Cocoa and Carbon, which is a subset of the old Macintosh Toolbox routines with some MacApp ideas thrown in. There are, of course, other systems, such as RealBasic, but they are not native.

As with any choice, it should be subservient to an understanding of the target domain. I chose Cocoa because I wanted a system that was as unlike previous systems I'd used, while still maintaining native C compatibility and not costing me an arm and a leg.

With many business sytems, the choice of an API for the user interface is of paramount concern, because everything other than the user interface is pretty trivial. With the program I'm working on, the design of the underlying system is so much more complex that the user interface is trivial. However, what is nontrivial is making sure that the underlying system is sufficiently modular to ensure that it would be relatively easy to plug in other user interfaces.

For another purpose, the requirements might be quite different. I have always believed that the requirements should be the guide.

Are there performance differences, are there different capabilities, or is it really just an alternative?

There are only two major differences in capabilities I can think of. The first is that Cocoa has drawers, an interesting idea that I haven't really used yet. The other is that Cocoa applications generally seem to have a better look and feel about them. I have seen many Carbon applications with two Quit items, only one of which worked, or which have missed essential OS X features such as the window bar. Cocoa doesn't make it impossible to write a noncompliant user interface, but it makes it so that you have to work at it. But still, the target domain is the important thing.

Additionally, does Apple recommend that developers port to Cocoa rather than Aqua, or are they agnostic on the issue?

Apple does recommend the use of Cocoa for the future. Not that I care very much about what Apple says; they have said some things in the past that were wise to ignore.

Also, how much of a difference is there between Aqua and the old pre-OS/X toolbox?

It's pretty much completely different. Cocoa is ultimately based on NextStep, which got a lot of things right (in many cases, more right than the Cocoa implementation, but those seem to be the results of the need for some sort of compatibility).


The truth may be out there, but lies are inside your head.--Terry Pratchett


[ Parent ]
Addendum (none / 0) (#47)
by epepke on Fri Sep 13, 2002 at 10:00:14 PM EST

I don't know if anybody is reading this any more, but I just installed Jaguar, and I'm vacillating between "it's worse" and "it sucks."

On the plus side, the ruler and split screen seem to have been fixed, although the way they did vertical rulers is a bit strange.

On the minus side, the trick with the extra thread and stdio for a debugging tool seems not to work at all any more. In multithreaded applications, all threads that use stdio seem to get blocked.

In the mere annoyance category, it took me a long time to re-download Xerces, get it running, and get it linked as a framework. I started installing Jaguar at 3 PM, and it was only at about 10 PM that I was back to where I had started with developing the software.

Also, the project builder and interface builder and even the POV ray tracer seem to crash a lot more. Bleah.


The truth may be out there, but lies are inside your head.--Terry Pratchett


Introduction To and Experiences With Mac Cocoa | 47 comments (42 topical, 5 editorial, 0 hidden)
Display: Sort:

kuro5hin.org

[XML]
All trademarks and copyrights on this page are owned by their respective companies. The Rest 2000 - Present Kuro5hin.org Inc.
See our legalese page for copyright policies. Please also read our Privacy Policy.
Kuro5hin.org is powered by Free Software, including Apache, Perl, and Linux, The Scoop Engine that runs this site is freely available, under the terms of the GPL.
Need some help? Email help@kuro5hin.org.
My heart's the long stairs.

Powered by Scoop create account | help/FAQ | mission | links | search | IRC | YOU choose the stories!