Write your own Quicksilver interface


Writing your own interface for Quicksilver is surprisingly easy, especially if you have experience in Cocoa and Xcode. Having covered the basics about the QSPlugin key in the Info.plist, as well the as the fundamentals of writing Quicksilver actions using Xcode, we are ready to start in a completely new direction. The Quicksilver interface.

Setup Xcode

[Download Quicksilver Interface Project Template] Writing an interface is very similar to writing an action, but the classes and Info.plist keys are somewhat different. Download the QS Interface Project Template. If you haven't previously downloaded a Quicksilver plugin template for Xcode, you'll have to do some basic setup.

Once you've installed the project template, you can build a new interface project "out-of-the-box" to make sure it works.

Unfortunately, development isn’t a linear process, so you’ll probably have to keep referring back to different sections of this article as you develop your interface.

Info.plist keys specifications

Info.plist iconWe tell Quicksilver that we're providing it an interface in the QSRegistration section of the Info.plist file, under QSCommandInterfaceControllers. By default, this should have been already added in when you created the project.

<key>QSRegistration</key>
<dict>
    <key>QSCommandInterfaceControllers</key>
    <dict>
        <key>testInterface</key>
        <string>testInterface</string>
    </dict>
    . . .
    . . .
</dict>

There is also a key called QSPreferencePanesTemplate inside QSRegistration. If you wish to have a preference pane for your plugin, rename the key to QSPreferencePanes and create the pref pane; otherwise you can leave it or delete it. As per the previous setting, these values should be filled in already by the project.

<key>QSRegistration</key>
<dict>
    . . .
    . . .
    <key>QSPreferencePanesTemplate</key>
    <dict>
        <key>testInterfacePrefPane</key>
        <dict>
            <key>class</key>
            <string>QSPreferencePane</string>
            <key>description</key>
            <string>testInterface interface preferences</string>
            <key>icon</key>
            <string></string>
            <key>name</key>
            <string>testInterface</string>
            <key>nibBundle</key>
            <string>com.blacktree.Quicksilver.testInterface</string>
            <key>nibName</key>
            <string>testInterfacePrefPane</string>
            <key>type</key>
            <string>hidden</string>
        </dict>
    </dict>
</dict>

If you're not having a preference pane, you should remove the PrefPane.nib file from your project when you're ready to release your plugin; it'll save bandwidth as well as reduce clutter.

You may not have an idea of the bindings you’ll be using yet (or maybe you do), but when you do use them, it's good practice to specify some default values for Quicksilver to use when the value hasn't been set by the user. These values go in the QSDefaults key.

<key>QSDefaultsTemplate</key>
<dict>
    <key>interface.bindingname</key>
    <string>value</string>
    <key>interface.bindingname2</key>
    <real>2.54328</real>
    <key>interface.bindingname2</key>
    <integer>4</integer>
</dict>

If you're using the "QSDefaults" key, make sure you’re renamed it from "QSDefaultsTemplate" to "QSDefaults".

Don’t forget there’s also a QSPlugin section in the Info.plist file that you should fill in.

Coding

That's the Info.plist file out of the way for now. It's time to start coding!

There are a few basic Quicksilver terms I hope you're familiar with:

[Quicksilver Interface Selectors]
Fig 1. The selectors are the used to pick the direct object, action, and indirect object respectively. Indirect object is often optional or not required.

Arrangement and positioning of the views using Interface Builder is left as an exercise for the reader. You don't want a lesson on how to drag and drop, do you?

Controlling the Controller

As mentioned before, the superclass (QSResizingInterfaceController) handles most of the work for you, therefore the majority of code you add will not be in new methods. Most of the code you write is (probably) going to be in existing methods that you will subclass.

Major methods

Here are the main methods you may decide to add extend:

- windowDidLoad

Anything that requires interaction with the interface window when it is first loaded. As the name implies, this method is called when the interface window is first loaded. All your setup with the window goes in this method. For example, setting the colors, adding bindings, and choosing animation effects for hiding / showing the interface etc. Please make sure you call [super windowDidLoad]; somewhere to let Quicksilver to do its setup as well.

This is not the place to add initialization code for the class itself. There's an -init method for that.

- maxIconSize

The maximum size allowed for icons in the interface selectors. Adjust this depending on how big or small your interface is. It's set at 128 x 128 by default, which should be fine in most circumstances.

- showMainWindow:

Any fancy animations or transitions you want for when the interface is being shown can go in here. You could also do something like: if ( [[self window]isVisible] ) [[self window] pulse:self];. (The pulse method is explained later). If you're having your own transition effect and don't want Quicksilver's default fade-in animation, run [window setFastShow:YES].

If you can, call the super implementation of the method where you would call -makeKeyAndOrderFront:, otherwise the option to suppress hotkeys while the command window is shown wont be honored.

- hideMainWindow:

Calling the super's implementation will hide the window with Quicksilver's effect. If you're using your own animation, you can run [window setHideEffect:nil] so Quicksilver doesn't run its own effect as well.

- activateInTextMode:

The use of the "Activate in Text Mode" trigger may be a rare occasion, but it's a cool place to throw in a surprising animation. Fumo has used Core Graphics warp and cube effects for this in the past.

Expansion

If you're having your interface expand and collapse, you'll need to subclass the following methods:

- showIndirectSelector:

Position the indirect selector in an appropriate location on the window. It may be useful to position it relative to another object, for example: [iSelector setFrame:NSOffsetRect([aSelector frame],0,-250)]; Don't forget to call super!

- expandWindow:

Expand your interface. You'll probably be using [[self window] setFrame: display: animate:] to resize the window smoothly.

- contractWindow:

As per -expandWindow:, but make the window smaller.

-hideIndirectSelector:

In most cases you wont need to subclass this. It just hides the indirect selector.

Preference Panes

The easy stuff

Creating your preference pane is easy since a lot of the work is done for you by the project template. There's a .nib file included in the interface project called "testInterfacePrefPane" (where "testInterface" is the name of your Xcode project). With this .nib file:

  1. Open it.
  2. Add controls and bindings.
  3. Save and quit.

That’s creating it, though. There's still a little more work to be done.

Info.plist keys

Remember the QSPreferencePanes key I mentioned earlier? That needs to be present under the QSRegistration key in the Info.plist file. You don’t need to configure it, but you can change some keys like description with a short summary of the preference pane, or set an icon.

(Reminder: Make sure to drop the "Template" from "QSPreferencePanesTemplate")

The "Customize" button

To set a customize button to show the preference pane for your interface, simply uncomment the -customize: method that's added in by default. It’s that easy.

- (IBAction)customize:(id)sender{
    [[NSClassFromString(@"QSPreferencesController") sharedInstance] showPaneWithIdentifier:@"testInterfacePrefPane"];
}

A note on bindings

When the user changes something in the preference pane, the binding will be updated. And that’s it. You’ll need to bind your classes / controls not only in the preference pane, but also in your code so that any changes to bindings reflect in your interface. For example, if you add a binding for "testInterface.fadeStyle", you may want to do something like:

[self bind:@"fadeStyle"
     toObject:[NSUserDefaultsController sharedUserDefaultsController]
  withKeyPath:@"values.testInterface.fadeStyle"
      options:nil];

If you’re binding an NSColor, make sure to use NSUnarchiveFromDataTransformerName

Building

Once you’ve got your code ready, build the project.

(Don’t puke if you get warnings that you can’t get rid of; there will be a few warnings regarding some methods like -setWindowProperty:, and another great error if you add a preference pane and the customize button. We’ll have to live with them for now.)

Double-click on the built product to have Quicksilver install it. If there are no serious faults with the code, you’ll see your Incredible Interface™ and be able to enjoy, use, and distribute it!

Summary

  • Download the Xcode Project Template, making sure everything else is setup correctly
  • Add code to the subclass of QSResizingInterfaceController
  • Arrange the views in Interface Builder
  • Add preference pane and bindings
  • Build project
  • Test and distribute

If you need any additional information or resources, help is always available at the Quicksilver forums or you can contact me.

Be sure to show me how you go with your interface!

Also in this series

Quicksilver plugins in Objective-C

A guide to obtaining the required resources for Quicksilver plugin development in Xcode. Also runs through setting up Xcode to make creating plugins easier.

Info.plist - QSPlugIn

The various sections of Quicksilver plugins, including the basics on how to properly configure your plugin, setup the correct options and settings, and how and where to write your plugin.

Quicksilver Actions

A quick look at QSActions in the Info.plist, followed by our first look at the code needed to get a plugin up and running.


Back to Top ↑

9 Comments so far

Leave a comment
  1. 1

    If you’re binding an NSColor, make sure to use NSUnarchiveFromDataTransformerName

    Hehe, good one, I wrote a transformer for that once myself, not knowing about this one :) .

RSS feed for comments on this post. TrackBack URI

Leave a comment

Comments may be edited for formatting.