Song Context View beaTlet

A SongContextView is a visual component displayed right below the main song table. beaTunes comes with two built-in instances of this component kind: the match table and the album info panel. To add your own component, you need to implement two classes.

The first class is an implementation of the com.tagtraum.beatunes.songtable.SongContextView interface. The second a subclass of com.tagtraum.beatunes.action.standard.SongContextComponentShowHideAction. Why two classes? While the view class really only deals with the view and what's happening in it, the action class is responsible for representing the little toggle button in the UI that lets your view appear and disappear.

To form a connection between instances of the two classes, the view instance references the action with the action's id. All the rest is magically done by beaTunes.

Sample SongContextView

To illustrate how such a component is written, we'll implement a song context view that displays the album art, the album title and the artist of the currently selected song. We start with the action class:

import javax.swing.Action
import com.tagtraum.beatunes.action.standard.SongContextComponentShowHideAction

class AlbumArtShowHideAction extends SongContextComponentShowHideAction {

    // This id is referenced in the corresponding SongContextView.
    def String getId() {
        "groovy.albumart.showhide"
    }

    def void init() {
        super.init()
        // register name
        putValue(Action.NAME, "Show Album Art")
        // here we could also set a different icon with key Action.SMALL_ICON
    }
}

After having written the action that shows the view, let's code up the view. In its constructor we set up a simple user interface with the two labels for artist and album and a third one for the image (unless you have a masochistic streak, you might want to ignore the layout statements).

The core of the view is really in the update(song) method. It is called by beaTunes whenever a song was selected and the selection hasn't changed for a couple of hundred milliseconds. Our implementation retrieves artist, album and image from the passed song object (which btw is of type com.tagtraum.audiokern.AudioSong) and applies those values to the labels. Because not all images have the same size, we also scale the image to 300x300 pixels.

Another little detail to pay attention to, is the getShowHideActionId() method. It must return the id of the corresponding show/hide action.

import javax.swing.*
import java.awt.*
import com.tagtraum.core.app.*
import com.tagtraum.core.image.*;
import com.tagtraum.audiokern.AudioSong
import com.tagtraum.beatunes.*
import com.tagtraum.beatunes.songtable.SongContextView

// Simple song context view (below main song table in the UI) that shows
// the selected song's artwork along with album title and artist name.
// To work, this class requires a companion class, a SongContextComponentShowHideAction.
class AlbumArt implements SongContextView {
    
    BeaTunes application
    JComponent component
    JLabel image
    JLabel album
    JLabel artist
    
    AlbumArt() {
        // set up the layout
        component = new JPanel(new GridBagLayout())
        image = new JLabel()
        album = new JLabel()
        artist = new JLabel()
        image.setVerticalAlignment(SwingConstants.TOP)
        album.setVerticalAlignment(SwingConstants.TOP)
        artist.setVerticalAlignment(SwingConstants.TOP)
        GridBagConstraints gbc = new GridBagConstraints()
        gbc.fill = GridBagConstraints.VERTICAL
        gbc.anchor = GridBagConstraints.NORTHWEST
        gbc.gridx = 0
        gbc.gridy = 0
        gbc.weighty = 2
        gbc.gridheight = 2
        component.add(image, gbc)
        gbc.fill = GridBagConstraints.HORIZONTAL
        gbc.anchor = GridBagConstraints.NORTHWEST
        gbc.gridx = 1
        gbc.gridy = 0
        gbc.gridheight = 1
        gbc.weighty = 0
        gbc.insets = new Insets(0, 10, 0, 10)
        component.add(album, gbc)
        gbc.fill = GridBagConstraints.BOTH
        gbc.anchor = GridBagConstraints.NORTHWEST
        gbc.gridx = 1
        gbc.gridy = 1
        gbc.weightx = 2
        gbc.weighty = 2
        gbc.gridheight = 1
        component.add(artist, gbc)
    }
    
    // Is called when this view should be updated.
    def void update(AudioSong song) {
        // check for null!
        if (song != null) {
            Image albumArt = song.getImage()
            if (albumArt != null) {
                image.setIcon(new ImageIcon(ImageScaler.scale(albumArt, 300, 300)));
            } else {
                image.setIcon(null);
            }
            album.setText("<html><font size='+3'>" + song.getAlbum() + "</font></html>");
            artist.setText("<html><font color='#555555' size='-1'>by " + song.getArtist() + "</font></html>");
        } else {
            image.setIcon(null);
            album.setText(null);
            artist.setText(null);
        }
    }

    // Every SongContextView needs to be accompanied by a
    // SongContextComponentShowHideAction.
    // Return the action's id here.
    def String getShowHideActionId() {
        "groovy.albumart.showhide"
    }

    // The visual component to be shown in this view.
    def JComponent getComponent() {
        component
    }

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

    def ApplicationComponent getApplication() {
        application
    }

    def String getId() {
        "groovy.albumart"
    }

    def void init() {
    }

    def void shutdown() {
    }
}

Now, if you don't really care about looks, but love dealing with audio signals, go on and learn how to integrate your own audio analysis code.