Getting Started with beaTlets

beaTunes 3 supports multiple kinds of plugins: Full-fledged plugins written in Java and so called beaTlets. The latter are small plugins, written in a scripting language. They don't need an IDE and don't need to be compiled by you. In fact, they are simple script files you can write with a free editor (e.g. JEdit). To install, test and run beaTlets, you just place them in beaTunes' plugin directory and fire up beaTunes itself. In the following short guide, we will introduce beaTlets.

Currently, beaTunes supports Groovy, JRuby and Jython. Groovy is the preferred language, because it is closest to Java and supports annotations.

Hello World

Let's dive right into it. The equivalent of Hello World in the beaTlet world is a plugin that prints the infamous words to the system error log. Here's the Jython version, which should be defined in a file called HelloWord.py:

import sys
class HelloWorld:
    def __init__(self):
        print >> sys.stderr, 'Hello World'

To install the script, we simply place it into the plugins directory and restart beaTunes. Now, let's check the system-error.log (where to find logs). If you can't find "Hello World" in the system-error.log, open the beaTunes.log file and search for "HelloWorld". You will probably find a corresponding error message.

Pretty easy, right? Let's look at the Groovy and JRuby versions of the same plugin. Again, they are saved in files named HelloWorld.groovy and HelloWorld.rb, respectively.

The important lessons to learn here are:

Lifecycle

Doing something right after the plugin class has been instantiated is fairly pointless. Typically, we want to interact with the application. To do so, we need to get a reference to it. In beaTunes, this is done according to the Hollywood principle: Don't call us, we call you.

What will beaTunes call? A couple of well defined methods. To make that possible, you have to implement an interface called ApplicationComponent. It defines, how beaTunes lets you know about itself, specifies an init() method as well as a destroy() method, and allows you to make your id known. In the JavaDocs, the interface is defined in com.tagtraum.core.app.ApplicationComponent. Let's see how we implement the interface in our three scripting languages:

import com.tagtraum.core.app.*

class BeaTunesAware implements ApplicationComponent {

    ApplicationComponent application

    def void setApplication(application) { this.application = application }
    def ApplicationComponent getApplication() { application }

    def void init() { System.err.println "init" }
    def void shutdown() { System.err.println "shutdown" }
    def String getId() { "Groovy.beaTunes.aware" }
}

Again, after installing one of the scripts, when starting and stopping beaTunes, you can see the init and shutdown print outs in the system-error.log.

The important thing to note about this example is, that it demonstrates how to implement Java interfaces in each of the scripting languages.

Aaaand... Action!

Printing messages to log files is fun, but your mom will probably not get too excited about it. What we need is something to show! Something that does something when you click on it! In beaTunes such a thing is called an Action.

beaTunes actions typically subclass com.tagtraum.beatunes.action.BaseAction. This has a couple of advantages, one of them being that BaseAction already implements the ApplicationComponent interface. One more thing, you don't have to deal with.

What you still need to do though, is to give it some id, specify where in the UI you want it to appear and of course what it should do, once it's executed. The following example does all that. We'll start with a heavily commented JRuby version.

require 'java'

java_import javax.swing.Action
java_import javax.swing.JOptionPane

java_import com.tagtraum.core.app.ActionLocation
java_import com.tagtraum.core.app.AbsoluteActionLocation

java_import com.tagtraum.beatunes.action.BeaTunesUIRegion
java_import com.tagtraum.beatunes.action.BaseAction
java_import com.tagtraum.beatunes.MessageDialog

# An action that does nothing, but to show a simple message box.
# The corresponding menu item can be found in the 'Tools' menu.

# All actions in beaTunes subclass com.tagtraum.beatunes.action.BaseAction
class DialogAction < BaseAction

    # Unique id
    def getId
        "JRuby.DialogAction"
    end

    # Is called by beaTunes as part of the lifecycle after instantiation.
    # At this point all other plugins are instantiated and registered.
    # We use this to set the menu item's (i.e. the action's) name.
    def init
        putValue(Action::NAME, "DialogAction")
    end

    # Define, where in the UI the Action should appear.
    # You can define multiple locations in an array. Here, we
    # only request to be the last item in the Tool menu.
    # If other Actions do the same thing, the last one wins.
    def getActionLocations
        # "to_java()" converts the Ruby array into a Java array of the given type
        [AbsoluteActionLocation.new(BeaTunesUIRegion::TOOL_MENU,
            AbsoluteActionLocation::LAST)].to_java(ActionLocation)
    end

    # React to a click on the menu item.
    # We show a simple dialog with the main window as the dialog's parent.
    def actionPerformed(actionEvent)
        # getApplication is defined in the super class, which is an
        # ApplicationComponent. The application in turn has a main window (a JFrame subclass).
        MessageDialog.new(
            getApplication.getMainWindow,       # parent window
            "DialogAction",                     # message
            JOptionPane::INFORMATION_MESSAGE,   # type of message dialog
            JOptionPane::DEFAULT_OPTION         # what buttons to show
        ).showDialog
    end

end

The Jython version isn't too different. For brevity, we didn't comment as much. Please check out the JRuby version for details.

Last but not least, here's the Groovy version:

Whatever your language of choice, the example should at least illustrate...

Furthermore, you should have gotten an impression of how to register Actions in the beaTunes application and pop up a dialog.

Of course that's not all that can be done... Check out how to programmatically manipulate song meta data in your whole library!