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.
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 } }
from javax.swing import Action from com.tagtraum.beatunes.action.standard import SongContextComponentShowHideAction class AlbumArtShowHideAction(SongContextComponentShowHideAction): # This id is referenced in the corresponding SongContextView. def getId(self): return "jython.albumart.showhide" def init(self): SongContextComponentShowHideAction.init(self) # register name self.putValue(Action.NAME, "Show Album Art") # here we could also set a different icon with key Action.SMALL_ICON
require 'java' java_import javax.swing.Action java_import com.tagtraum.beatunes.action.standard.SongContextComponentShowHideAction class AlbumArtShowHideAction < SongContextComponentShowHideAction # This id is referenced in the corresponding SongContextView. def getId() "jruby.albumart.showhide" end def init() super() # register name putValue(Action::NAME, "Show Album Art") # here we could also set a different icon with key Action::SMALL_ICON end end
/* * These type vars basically act as imports for Java classes. */ var Action = Java.type("javax.swing.Action"); var SongContextComponentShowHideAction = Java.type("com.tagtraum.beatunes.action.standard.SongContextComponentShowHideAction"); // ApplicationComponent is an interface, which we can // implement via "new" (much like an anonymous inner Java class). // The resulting instance is stored in the "beatlet" variable. var beatlet = new SongContextComponentShowHideAction() { /* * This id is referenced in the corresponding SongContextView. */ getId: function() { return "javascript.albumart.showhide"; }, init: function() { beatletSuper.init(); // register name beatletSuper.putValue(Action.NAME, "Show Album Art"); // here we could also set a different icon with key Action.SMALL_ICON } } // Find super class of beatlet, so that we can call methods on it // in the "actionPerformed" function. var beatletSuper = Java.super(beatlet); // Put "beatlet" into the last line, so that it is returned // to beaTunes when this script is eval'd. beatlet;
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 BeaTunes getApplication() { application } def String getId() { "groovy.albumart" } def void init() { } def void shutdown() { } }
from java.awt import GridBagLayout from java.awt import GridBagConstraints from java.awt import Insets from javax.swing import ImageIcon from javax.swing import JPanel from javax.swing import JLabel from javax.swing import SwingConstants from com.tagtraum.core.image import ImageScaler from com.tagtraum.beatunes.songtable import 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(SongContextView): def __init__(self): # set up the layout self.__component = JPanel(GridBagLayout()) self.__image = JLabel() self.__album = JLabel() self.__artist = JLabel() self.__application = None self.__image.setVerticalAlignment(SwingConstants.TOP) self.__album.setVerticalAlignment(SwingConstants.TOP) self.__artist.setVerticalAlignment(SwingConstants.TOP) gbc = GridBagConstraints() gbc.fill = GridBagConstraints.VERTICAL gbc.anchor = GridBagConstraints.NORTHWEST gbc.gridx = 0 gbc.gridy = 0 gbc.weighty = 2 gbc.gridheight = 2 self.__component.add(self.__image, gbc) gbc.fill = GridBagConstraints.HORIZONTAL gbc.anchor = GridBagConstraints.NORTHWEST gbc.gridx = 1 gbc.gridy = 0 gbc.gridheight = 1 gbc.weighty = 0 gbc.insets = Insets(0, 10, 0, 10) self.__component.add(self.__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 self.__component.add(self.__artist, gbc) # Is called when this view should be updated. def update(self, song): # check for None! if (song != None): albumArt = song.getImage() if (albumArt != None): self.__image.setIcon(ImageIcon(ImageScaler.scale(albumArt, 300, 300))) else: self.__image.setIcon(None) self.__album.setText(" " + song.getAlbum() + " ") self.__artist.setText(" by " + song.getArtist() + " ") else: self.__image.setIcon(None) self.__album.setText(None) self.__artist.setText(None) # Every SongContextView needs to be accompanied by a # SongContextComponentShowHideAction. # Return the action's id here. def getShowHideActionId(self): return "jython.albumart.showhide" # The visual component to be shown in this view. def getComponent(self): return self.__component def setApplication(self, application): self.__application = application def getApplication(self): return self.__application def getId(self): return "jython.albumart" def init(self): pass def shutdown(self): pass
require 'java' java_import javax.swing.SwingConstants java_import javax.swing.JLabel java_import javax.swing.JPanel java_import javax.swing.ImageIcon java_import java.awt.GridBagLayout java_import java.awt.GridBagConstraints java_import java.awt.Image java_import java.awt.Insets java_import com.tagtraum.core.image.ImageScaler # 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 include Java::com.tagtraum.beatunes.songtable.SongContextView def initialize # set up the layout @component = JPanel.new(GridBagLayout.new()) @image = JLabel.new() @album = JLabel.new() @artist = JLabel.new() @image.setVerticalAlignment(SwingConstants::TOP) @album.setVerticalAlignment(SwingConstants::TOP) @artist.setVerticalAlignment(SwingConstants::TOP) gbc = GridBagConstraints.new() 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 = Insets.new(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) end # Is called when this view should be updated. def update(song) # check for nil! if (!song.nil?) albumArt = song.getImage() if (!albumArt.nil?) @image.setIcon(ImageIcon.new(ImageScaler.scale(albumArt, 300, 300))) else @image.setIcon(nil) end @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(nil) @album.setText(nil) @artist.setText(nil) end end # Every SongContextView needs to be accompanied by a # SongContextComponentShowHideAction. # Return the action's id here. def getShowHideActionId() "jruby.albumart.showhide" end # The visual component to be shown in this view. def getComponent() @component end def setApplication(application) @application = application end def getApplication() @application end def getId() "jruby.albumart" end def init() end def shutdown() end end
/* * These type vars basically act as imports for Java classes. */ var Insets = Java.type("java.awt.Insets"); var GridBagLayout = Java.type("java.awt.GridBagLayout"); var GridBagConstraints = Java.type("java.awt.GridBagConstraints"); var Action = Java.type("javax.swing.Action"); var SwingConstants = Java.type("javax.swing.SwingConstants"); var ImageIcon = Java.type("javax.swing.ImageIcon"); var JPanel = Java.type("javax.swing.JPanel"); var JLabel = Java.type("javax.swing.JLabel"); var ImageScaler = Java.type("com.tagtraum.core.image.ImageScaler"); var SongContextView = Java.type("com.tagtraum.beatunes.songtable.SongContextView"); // set up the layout var component = new JPanel(new GridBagLayout()); var image = new JLabel(); var album = new JLabel(); var artist = new JLabel(); var gbc = new GridBagConstraints(); image.setVerticalAlignment(SwingConstants.TOP); album.setVerticalAlignment(SwingConstants.TOP); artist.setVerticalAlignment(SwingConstants.TOP); 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); // ApplicationComponent is an interface, which we can // implement via "new" (much like an anonymous inner Java class). // The resulting instance is stored in the "beatlet" variable. var beatlet = new SongContextView() { application: null, // Is called when this view should be updated. update: function(song) { // check for null! if (song != null) { var albumArt = song.getImage() if (albumArt != null) { image.setIcon(new ImageIcon(ImageScaler.scale(albumArt, 300, 300))); } else { image.setIcon(null); } album.setText(" " + song.getAlbum() + " "); artist.setText(" by " + song.getArtist() + " "); } 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. getShowHideActionId: function() { return "javascript.albumart.showhide"; }, // The visual component to be shown in this view. getComponent: function() { return component; }, /* * The application object is injected by beaTunes * right after this script has been eval'd. */ setApplication: function(application) { this.application = application; }, /* * beaTunes application object. */ getApplication: function() { return this.application; }, /* * This id is referenced in the corresponding SongContextView. */ getId: function() { return "javascript.albumart"; }, init: function() { }, shutdown: function() { } } // Put "beatlet" into the last line, so that it is returned // to beaTunes when this script is eval'd. beatlet;
For analysing and modifying song metadata via analysis, please check out the song analysis task sample.
Other beaTlet samples:
- Getting Started
- Context Action
- Library Batch Action
- Playlist Exporter
- Song Analysis Task
- beaTlet Plugin Descriptor
- Song Property Analyzer
- Key Text Renderer
All sample beaTlets are also on GitHub .