With the lifting of the Nondisclosure Agreement on the iPhone SDK,I'm pleased to finally make available the source code to Molecules. If you go to the main page for the application,you should now find a link to download the latest source tarball (for version 1.2) on the right-hand side. You can also download the source code here. I am working on migrating my personal Subversion setup so that you can check out the latest code and so that I can authorize contributors to commit fixes and additions.
Unfortunately,I haven't documented the current code release as well as I would like. To make up for that,I'll attempt to describe the structure of the application and why I do things the way I do. I don't claim that everything here is optimal,but I hope that it can be a useful resource for those interested in OpenGL ES,sqlite,multitouch,and table views on the iPhone.
All code is released under the BSD license,so you are free to cut and paste it into your own applications without restriction. I only ask you to be sensible. If I see $0.99 AAABestMolecules applications appearing in the store under your name,I will personally come and kick you in the junk. I'm not saying you can't improve upon it and charge for that,but please don't try to scam people. There's enough of that already (I'm looking at you,bubble-level sample code guy and all those with for-pay flashlights).
The following is a walkthrough of the program as of the current version in the App Store (1.2).
Database initialization
The base class of Molecules is the SLSMoleculeAppDelegate. This controller acts as the glue of the application. Its primary function is to start up the root view controller and initialize the molecule database or load the existing one from flash memory.
On the first startup of the application,it checks to see if a molecules.sql database has been installed in the application's Documents directory. This directory exists within the application sandBox and is used to store all user data. If the database is missing,the application knows that it hasn't been run before and starts an initialization process. This process involves copying over a starter sqlite database (one that I created using the dbcreation.sh script within the project),as well as all .pdb.gz molecule files to the Documents directory. After that copying process,all .pdb.gz files are parsed and the molecules added to the sqlite database.
SLSMolecule and file loading
I use the SLSMolecule class to encapsulate all operations involving a molecule,including loading,saving to the sqlite database,displaying information in a table view,and deleting. I created a category on SLSMolecule called PDB that contains methods related to parsing files in the Protein Data Bank format. I used a category for these loading routines because I plan on adding other filetypes and the categories will help keep them separate.
The pdb.gz files are strictly-formatted text files compressed using Gzip compression. Even though I parse the files into the sqlite database for quick access,I still keep the original molecule files on the device for later reference (I can re-scan them if the database schema changes,for example). They are only ~20-200k in size,so I'm not that worried about flash memory space.
To parse these files,I used a Gzip category on NSData (contributed by the good people at CocoaDev) to unzip the file into an NSData object,then I convert that to an NSString and parse out the lines. I won't go into too much detail,but the PDB format assumes you know which atoms connect to which within amino acid residues,so I had to build a long lookup table for each of them.
In a later post,I'll spend more time on the specifics of sqlite and how I structured the molecule,Metadata,atom,and bond tables. I'll just say that the PDB files are interpreted and appropriate entries are made for all components of a molecule,with those components having a field whose ID is that of the molecule they belong to.
Molecule rendering
There is one root view for the application,managed by the SLSMoleculeRootViewController. This root view controller swaps between the OpenGL ES molecule view and the table view for managing molecules. It also handles the rendering progress bar that gets overlaid on the OpenGL view.
After initialization,or on any start of the program after this point,the last molecule the user viewed will be rendered and displayed. First,the OpenGL view,SLSMoleculeGLView (managed by SLSMoleculeGLViewController),is initialized by the root view controller. The OpenGL view does some standard GL setup calls (most of them from the starter OpenGL application in XCode) and sets up the lighting for the 3-D scene. I found that I needed to have it render a blank screen at least once on startup to prevent garbage from a prevIoUs application from being displayed before the first molecule has rendered.
The molecule rendering process is also encapsulated by the SLSMolecule class. An SLSMolecule object knows its unique ID within the sqlite database and is able to query for all atoms,bonds,and Metadata items that belong to it. Depending on the visualization mode,only some of this data may be required to generate the 3-D rendering.
I've described much of the 3-D rendering process (VBOs,etc.) in my previous post,so I won't go over it again in much detail. Basically,when rendering the ball-and-stick view,I grab the 3-D coordinate for the center of an atom from the database and render an icosahedron at that location. The icosahedron will have a color that corresponds to the element of the atom. Likewise,I load and render a bond as a Box with starting and ending coordinates corresponding to the atoms that bond links. Using sqlite for this in 1.2 made it a lot faster than directly parsing and rendering from the .pdb.gz files.
One tricky aspect was how to update the rendering progress indicator as the operation proceeds. I managed to do this by having the SLSMolecule instance which is currently being displayed fire off a separate thread for the rendering process. On the start of this thread,a MoleculeRenderingStarted notification is posted. This notification tells the SLSMoleculeRootViewController to display the rendering indicator and its accompanying text. I couldn't get this to actually show up until I read that views will not update until the end of the main thread's run loop has been reached. When I triggered the notification,and all other update notifications,using a performSelectorOnMainThread:withObject:waitUntilDone: method with waitUntilDone set to NO,the updates proceeded as planned. This was a cause of much frustration for me,so hopefully you won't have to struggle with it now.
Multitouch
At the end of the molecule rendering operation,all of the vertex buffer objects (VBOs) are properly loaded and just need to be drawn to the screen. The rotation,scaling,and translation of the VBOs change in response to multitouch events,all of which pass through the SLSMoleculeGLView.
The touchesBegan:withEvent:,touchesMoved:withEvent:,and touchesEnded:withEvent: methods are triggered by the appropriate input from the touch screen. The application keeps track of how many fingers have touched down on the screen and how many are moving.
As a word of warning,testing on the iPhone Simulator will produce very different behavior than on an actual device. In the Simulator,pinch and two-finger-move gestures always begin with two fingers simultaneously moving or lifting off the screen. In real life,your two (or more) fingers rarely contact the screen or move at the same time. You will need to account for this to get the multiple touches working properly. If you look at my code,you'll see some leftover elements from my experimentation with the touch controls. Most recently,I added a switch that prevents detection of a pinch gesture while doing a two-finger move,and vice versa. This was annoying me.
The rotation using a single finger is the simplest touch action to handle. The distance that finger moved,in pixels,is reported to the rendering routines,where the X-Y movement is converted into rotation about the center of the molecule. I described the process by which I do this in OpenGL in my previous post.
To perform a pinch zoom gesture,I detect when two fingers have touched down on the screen and calculate the distance between them,in pixels. On finger movement,if the fingers are both moving in the same direction,I process this as two-finger translation of the molecule. Otherwise,I recalculate the distance between the fingers and incrementally scale the 3-D model of the molecule to match the relative change between the current distance and the prevIoUsly measured distance.
As mentioned,if two fingers are detected as moving in the same direction,the molecule is translated,rather than scaled. The displacement of both fingers in X and Y is averaged,and the 3-D model is shifted by that amount in pixels.
Finally,the double-tap gesture that pops up the selection sheet for specifying new visualization modes for the molecule is detected by measuring the tapCount of a single touch in the touchesEnded:withEvent: method. A UIActionSheet is presented to the user,with the current visualization mode highlighted as the default. Switching modes triggers a re-render of the current molecule to generate the new geometry for the mode.
Molecule listing
If the user wishes to change the molecule to be displayed,he can tap on the small information button on the lower right of the screen. This tells the SLSMoleculeRootViewController to swap the SLSMoleculeGLView for a table view managed by a SLSMoleculeTableViewController. A builtin Core Animation view transition is used to produce the flip effect in the toggleView method of the root view controller.
As a minor aside,I had a friend test out the interface to the application and he had the hardest time getting the small information button in the lower right of the screen to activate. He's a tall Bavarian with larger fingers than me,but he wasn't the only one with that problem. Therefore,I had the SLSMoleculeGLView respond to any touches in that corner of the screen and trigger the information button's selector as well. The lesson: if your interface makes a large German man angry,you should probably fix it.
The table view that lists the molecules is generated and managed programmatically. I tried going through Interface Builder,as I was accustomed to for normal Mac application design,but found that I was unable to set things up the way I wanted. This table view is also wrapped in a UINavigationController,and I just could not make one of those work correctly in Interface Builder.
The cells in the table view are of a custom style,which is generated in the tableView:cellForRowAtIndexPath: delegate method. One of those cells,the "Download new molecule" entry at the top of the list,is hardcoded and can't be deleted. Tapping on the name of any other entry in the list switches the molecule to be displayed in the 3-D view and tapping on its disclosure triangle brings up a detailed view of a molecule's Metadata,managed by a SLSMoleculeDetailViewController. That Metadata is loaded on the fly from the sqlite database only when the detail view is displayed.
The detail view consists of a grouped table view with a few custom cells. By tapping on a cell that contains a long title or other piece of Metadata,the view switches to one managed by an SLSTextViewController,where the field is simply displayed in the full screen.
Protein Data Bank search and download
I mentioned that on the main list of molecules there was one hardcoded table element for downloading new molecules. If a user taps on this item,he will be brought to another table view with a search bar at the top. This view is governed by an SLSMoleculeSearchViewController and is what allows for keyword searching of the Protein Data Bank when looking for new molecules to download.
The keyword entry occurs through a UISearchBar,which I have set to be the tableHeaderView. I also disabled autocorrection by setting autocorrectionType to UITextAutocorrectionTypeNo,because it has problems with some scientific terms. The SLSMoleculeSearchViewController is the delegate for the search field only so that the keyboard can be hidden once a search is started using the resignFirstResponder method.
When a keyword has been entered,I generate a SOAP query to the Protein Data Bank that follows their published protocol. This query is hardcoded and is based off of one I generated using the free SOAP Client for the Mac,a very nice tool. The query is manufactured via a NSMutableURLRequest and sent using an NSURLConnection.
The NSURLConnection runs in the background,and the SLSMoleculeSearchViewController receives messages either as the download progresses or if the download Failed for some reason. Small chunks of raw data are returned in the form of NSData objects,which are appended to an NSData object that persists throughout the connection. Because the Protein Data Bank will return every result that matches the query,I actually drop the connection if too many results have been returned in order to save on bandwidth.
After all the data from the response have been compiled,the resulting NSData object is fed into an NSXMLParser. The NSXMLParser will run through the data and fire off messages to its delegate (again,the SLSMoleculeSearchViewController) as it encounters varIoUs XML structures. I only care for a particular element named keywordQueryReturn,so I use the parser:didEndElement:namespaceURI:qualifiedName: method to search only those results. This pulls out the PDB codes for each molecule that I'm interested in.
Once I have an array of PDB codes from the query,I need to grab their titles from the Protein Data Bank. This is done through a simpler URL query,again via the NSURLRequest and NSURLConnection classes. Data are retrieved in the same fashion as described above,only this response is pure unformatted text. Therefore,it is converted to an NSString and parsed,rather than being run through an NSXMLParser.
After all that,the search results are displayed in the table view. If a user wishes to download a molecule from the data bank,they can click on its name in the list. They will be taken to a view managed by an SLSMoleculeDownloadViewController. The title of the molecule is displayed,along with a button to read more about the molecule and one to download its structure.
If the user chooses to read more about the current molecule,a UIWebView flips into view and displays the web page for the given structure. The flip transition is yet another builtin Core Animation effect and is due to the SLSMoleculeDownloadViewController swapping out the main view for the web view. The web view is simply loaded with a URL that points to the page on the Protein Data Bank website for the molecule with the currently selected PDB code.
The download of a molecular structure is handled in the same fashion as the prevIoUsly described search requests. A URL is crafted based on the PDB code of the currently selected molecule. The Gzipped version of the structure is downloaded,and when the download has completed it is written to disk in the Documents directory. The file is then parsed in the manner described above for PDB structure files.
Additional info
If you have additional questions about the source code,any comments on it,or any contributions you'd like to make to the application,please visit the forums. I'm working on a series of improvements to the program for release over the next couple of months and will post about any significant additions to the code base.
- @L_301_11@