Revamped preferences handling; added support for voting for songs you

like. Eventually we'll actually make use of the voting information.


git-svn-id: https://samskivert.googlecode.com/svn/trunk@1116 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
mdb
2003-05-04 18:16:07 +00:00
parent 87fa79febe
commit a8fdad6afb
24 changed files with 1496 additions and 604 deletions
@@ -1,10 +1,9 @@
//
// $Id: BrowsePanel.java,v 1.3 2002/11/11 17:04:12 mdb Exp $
// $Id: BrowsePanel.java,v 1.4 2003/05/04 18:16:06 mdb Exp $
package robodj.chooser;
import javax.swing.*;
import robodj.repository.*;
public class BrowsePanel extends JTabbedPane
@@ -1,9 +1,10 @@
//
// $Id: CategoryEntryList.java,v 1.1 2002/02/22 07:06:33 mdb Exp $
// $Id: CategoryEntryList.java,v 1.2 2003/05/04 18:16:06 mdb Exp $
package robodj.chooser;
import com.samskivert.io.PersistenceException;
import com.samskivert.swing.Controller;
import robodj.repository.Entry;
@@ -17,13 +18,14 @@ public class CategoryEntryList extends EntryList
_categoryId = categoryId;
}
/**
* Reads in the entries for this category.
*/
public Entry[] readEntries ()
throws PersistenceException
// documentation inherited
protected Controller createController ()
{
return Chooser.model.getEntries(_categoryId);
return new EntryController(this) {
public Entry[] readEntries () throws PersistenceException {
return Chooser.model.getEntries(_categoryId);
}
};
}
protected String getEmptyString ()
@@ -1,5 +1,5 @@
//
// $Id: Chooser.java,v 1.10 2002/11/11 17:04:26 mdb Exp $
// $Id: Chooser.java,v 1.11 2003/05/04 18:16:06 mdb Exp $
package robodj.chooser;
@@ -12,6 +12,7 @@ import javax.swing.JOptionPane;
import com.samskivert.io.PersistenceException;
import com.samskivert.jdbc.StaticConnectionProvider;
import com.samskivert.util.StringUtil;
import com.samskivert.swing.util.SwingUtil;
import com.samskivert.util.ConfigUtil;
@@ -20,6 +21,8 @@ import com.samskivert.util.PropertiesUtil;
import robodj.Log;
import robodj.repository.Model;
import robodj.repository.Repository;
import robodj.util.RDJPrefs;
import robodj.util.RDJPrefsPanel;
import robodj.util.ServerControl;
/**
@@ -28,82 +31,86 @@ import robodj.util.ServerControl;
*/
public class Chooser
{
public static Properties config;
public static Repository repository;
public static Model model;
public static ServerControl scontrol;
public static ChooserFrame frame;
public static void main (String[] args)
{
// load our main configuration
String cpath = "conf/chooser.properties";
try {
config = ConfigUtil.loadProperties(cpath);
} catch (IOException ioe) {
String err = "Unable to load configuration " +
"[path=" + cpath + "]:\n" + ioe.getMessage();
reportError(err);
System.exit(-1);
}
boolean error = false;
// create an interface to the database repository
try {
StaticConnectionProvider scp =
new StaticConnectionProvider("conf/repository.properties");
repository = new Repository(scp);
model = new Model(repository);
// loop until the user provides us with a configuration that works
// or requests to exit
for (int ii = 0; ii < 100; ii++) {
String repodir = RDJPrefs.getRepositoryDirectory();
if (StringUtil.blank(repodir) || ii > 0) {
// display the initial configuration wizard if we are not
// yet properly configured
RDJPrefsPanel.display(true);
}
} catch (IOException ioe) {
Log.logStackTrace(ioe);
reportError("Error loading repository config:\n" +
ioe.getMessage());
System.exit(-1);
// create an interface to the database repository
try {
StaticConnectionProvider scp =
new StaticConnectionProvider(RDJPrefs.getJDBCConfig());
repository = new Repository(scp);
model = new Model(repository);
} catch (PersistenceException pe) {
Log.logStackTrace(pe);
pe.printStackTrace();
reportError("Unable to establish communication " +
"with database:\n" + pe.getMessage());
System.exit(-1);
}
} catch (PersistenceException pe) {
if (reportError("Unable to establish communication " +
"with database:", pe)) {
System.exit(-1);
}
continue;
}
String mhost = System.getProperty("musicd_host", "localhost");
String mportstr = System.getProperty("musicd_port", "2500");
int mport = 2500;
try {
mport = Integer.parseInt(mportstr);
} catch (NumberFormatException nfe) {
Log.warning("Invalid musicd_port value. Using default " +
"(" + mport + ").");
}
try {
String host = RDJPrefs.getMusicDaemonHost();
if (StringUtil.blank(host)) {
throw new IOException(
"No music server host specified in configuration.");
}
// establish a connection with the music server
scontrol = new ServerControl(
host, RDJPrefs.getMusicDaemonPort());
} catch (IOException ioe) {
if (reportError("Unable to establish communication with " +
"music server on host '" +
RDJPrefs.getMusicDaemonHost() + "' ", ioe)) {
System.exit(-1);
}
continue;
}
try {
// establish a connection with the music server
scontrol = new ServerControl(mhost, 2500);
} catch (IOException ioe) {
Log.logStackTrace(ioe);
reportError("Unable to establish communication with music " +
"server:\n" + ioe.getMessage());
System.exit(-1);
break;
}
// create our primary user interface frame, center the frame in
// the screen and show it
ChooserFrame frame = new ChooserFrame();
frame.setSize(650, 665);
SwingUtil.centerWindow(frame);
frame.setVisible(true);
frame = new ChooserFrame();
frame.setSize(650, 665);
SwingUtil.centerWindow(frame);
frame.setVisible(true);
}
protected static void reportError (String error)
protected static boolean reportError (String error, Exception e)
{
Object[] options = { "OK" };
JOptionPane.showOptionDialog(null, error, "Error",
JOptionPane.DEFAULT_OPTION,
JOptionPane.ERROR_MESSAGE,
null, options, options[0]);
String text = error;
if (e != null) {
text = text + "\n\n" + e.getMessage();
if (e.getCause() != null) {
text = text + "\n" + e.getCause().getMessage();
}
}
Object[] options = { "Edit configuration", "Exit" };
int choice = JOptionPane.showOptionDialog(
null, text, "Error", JOptionPane.DEFAULT_OPTION,
JOptionPane.ERROR_MESSAGE, null, options, options[0]);
System.out.println("Chose " + choice + ".");
return (choice == 1 || choice == JOptionPane.CLOSED_OPTION);
}
}
@@ -0,0 +1,76 @@
//
// $Id: ChooserController.java,v 1.1 2003/05/04 18:16:06 mdb Exp $
package robodj.chooser;
import java.awt.event.ActionEvent;
import javax.swing.JOptionPane;
import com.samskivert.swing.Controller;
import com.samskivert.swing.util.TaskMaster;
import com.samskivert.swing.util.TaskObserver;
import robodj.Log;
/**
* Handles top-level chooser UI commands.
*/
public class ChooserController extends Controller
implements TaskObserver
{
public ChooserController ()
{
// read our playing state
TaskMaster.invokeMethodTask("refreshPlaying", Chooser.scontrol, this);
}
// documentation inherited
public boolean handleAction (ActionEvent event)
{
String cmd = event.getActionCommand();
if (cmd.equals("exit")) {
System.exit(0);
} else if (cmd.equals("skip")) {
TaskMaster.invokeMethodTask("skip", Chooser.scontrol, this);
} else if (cmd.equals("back")) {
TaskMaster.invokeMethodTask("back", Chooser.scontrol, this);
} else if (cmd.equals("pause")) {
TaskMaster.invokeMethodTask("pause", Chooser.scontrol, this);
} else if (cmd.equals("play")) {
TaskMaster.invokeMethodTask("play", Chooser.scontrol, this);
} else if (cmd.equals("stop")) {
TaskMaster.invokeMethodTask("stop", Chooser.scontrol, this);
} else {
System.out.println("Unknown action event: " + cmd);
return false;
}
return true;
}
// documentation inherited from interface
public void taskCompleted (String name, Object result)
{
// nothing to do here
}
// documentation inherited from interface
public void taskFailed (String name, Throwable exception)
{
String msg;
if (Exception.class.equals(exception.getClass())) {
msg = exception.getMessage();
} else {
msg = exception.toString();
}
JOptionPane.showMessageDialog(Chooser.frame, msg, "Error",
JOptionPane.ERROR_MESSAGE);
Log.logStackTrace(exception);
}
}
@@ -1,5 +1,5 @@
//
// $Id: ChooserFrame.java,v 1.10 2002/11/11 17:04:41 mdb Exp $
// $Id: ChooserFrame.java,v 1.11 2003/05/04 18:16:06 mdb Exp $
package robodj.chooser;
@@ -10,18 +10,18 @@ import java.awt.event.ActionListener;
import javax.swing.*;
import com.samskivert.swing.*;
import com.samskivert.swing.util.TaskAdapter;
import com.samskivert.swing.util.TaskObserver;
import com.samskivert.swing.util.TaskMaster;
import com.samskivert.swing.util.*;
import com.samskivert.util.StringUtil;
import robodj.Log;
import robodj.Version;
import robodj.repository.*;
import robodj.util.ButtonUtil;
import robodj.util.RDJPrefs;
import robodj.util.ServerControl.PlayingListener;
public class ChooserFrame extends JFrame
implements ActionListener, TaskObserver, PlayingListener
implements PlayingListener, ControllerProvider
{
public ChooserFrame ()
{
@@ -59,22 +59,42 @@ public class ChooserFrame extends JFrame
bgl.setJustification(GroupLayout.RIGHT);
JPanel cbar = new JPanel(bgl);
// create a label and text field where the chooser user can
// identify themselves
cbar.add(new JLabel("Initials:"));
cbar.add(_userField = new JTextField());
Dimension ups = _userField.getPreferredSize();
ups.width = 35;
_userField.setPreferredSize(ups);
cbar.add(new Spacer(100, 10));
// enforce a maximum of 3 letters in initials
SwingUtil.setDocumentHelpers(
_userField, new SwingUtil.DocumentValidator() {
public boolean isValid (String text) {
return (text.length() <= 3);
}
}, null);
// display any previously configured initials
_userField.setText(RDJPrefs.getUser());
// add some fake control buttons for now
cbar.add(ButtonUtil.createControlButton(
BACK_TIP, "back", BACK_ICON_PATH, this, true));
BACK_TIP, "back", BACK_ICON_PATH, true));
_stop = ButtonUtil.createControlButton(
STOP_TIP, "stop", STOP_ICON_PATH, this, true);
STOP_TIP, "stop", STOP_ICON_PATH, true);
cbar.add(_stop);
_pauseIcon = ButtonUtil.getIcon(PAUSE_ICON_PATH);
_playIcon = ButtonUtil.getIcon(PLAY_ICON_PATH);
_pause = ButtonUtil.createControlButton(
"", "pause", _pauseIcon, this, true);
"", "pause", _pauseIcon, true);
cbar.add(_pause);
cbar.add(ButtonUtil.createControlButton(
SKIP_TIP, "skip", SKIP_ICON_PATH, this, true));
SKIP_TIP, "skip", SKIP_ICON_PATH, true));
cbar.add(new Spacer(50, 10));
cbar.add(ButtonUtil.createControlButton(
EXIT_TIP, "exit", EXIT_ICON_PATH, this, true));
EXIT_TIP, "exit", EXIT_ICON_PATH, true));
// stick it into the frame
top.add(cbar, GroupLayout.FIXED);
@@ -86,52 +106,44 @@ public class ChooserFrame extends JFrame
// add ourselves as a playing listener
Chooser.scontrol.addPlayingListener(this);
// read our playing state
TaskMaster.invokeMethodTask("refreshPlaying", Chooser.scontrol, this);
// create our controller
_controller = new ChooserController();
}
public void actionPerformed (ActionEvent e)
// documentation inherited from interface
public Controller getController ()
{
String cmd = e.getActionCommand();
if (cmd.equals("exit")) {
System.exit(0);
} else if (cmd.equals("skip")) {
TaskMaster.invokeMethodTask("skip", Chooser.scontrol, this);
} else if (cmd.equals("back")) {
TaskMaster.invokeMethodTask("back", Chooser.scontrol, this);
} else if (cmd.equals("pause")) {
TaskMaster.invokeMethodTask("pause", Chooser.scontrol, this);
} else if (cmd.equals("play")) {
TaskMaster.invokeMethodTask("play", Chooser.scontrol, this);
} else if (cmd.equals("stop")) {
TaskMaster.invokeMethodTask("stop", Chooser.scontrol, this);
} else {
System.out.println("Unknown action event: " + cmd);
}
return _controller;
}
public void taskCompleted (String name, Object result)
/**
* Returns the string currently entered into the "user" field.
*/
public String getUser (boolean showError)
{
// nothing to do here
}
public void taskFailed (String name, Throwable exception)
{
String msg;
if (Exception.class.equals(exception.getClass())) {
msg = exception.getMessage();
} else {
msg = exception.toString();
String user = _userField.getText();
// if they haven't supplied a user, complain
if (StringUtil.blank(user)) {
if (!showError) {
return null;
}
String errmsg = "The feature you have requested requires " +
"that you identify yourself by entering your initials in " +
"the 'Initials' box at the bottom of the window.";
JOptionPane.showMessageDialog(this, errmsg, "Initials required",
JOptionPane.ERROR_MESSAGE);
return null;
}
JOptionPane.showMessageDialog(this, msg, "Error",
JOptionPane.ERROR_MESSAGE);
Log.logStackTrace(exception);
// if they typed in mixed or upper case, lower it for them
user = user.toLowerCase();
if (!user.equals(_userField.getText())) {
_userField.setText(user);
}
// make sure we've saved these initials values
RDJPrefs.config.setValue(RDJPrefs.USER_KEY, user);
return user;
}
public void playingUpdated (int songid, boolean paused)
@@ -150,6 +162,12 @@ public class ChooserFrame extends JFrame
_stop.setEnabled(songid != -1);
}
/** Our top-level controller. */
protected Controller _controller;
/** A field where the user can identify themselves. */
protected JTextField _userField;
/** A reference to the play/pause button. */
protected JButton _pause;
@@ -0,0 +1,172 @@
//
// $Id: EntryController.java,v 1.1 2003/05/04 18:16:06 mdb Exp $
package robodj.chooser;
import java.awt.event.ActionEvent;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import com.samskivert.io.PersistenceException;
import com.samskivert.swing.util.SwingUtil;
import com.samskivert.swing.util.TaskMaster;
import robodj.Log;
import robodj.repository.Entry;
import robodj.repository.Song;
/**
* Handles default entry list behavior.
*/
public abstract class EntryController extends ItemController
implements AncestorListener
{
public EntryController (EntryList list)
{
super(list);
_list = list;
_list.addAncestorListener(this);
}
/**
* Reads the entries from the database.
*/
public void readSongs ()
throws PersistenceException
{
Chooser.repository.populateSongs(_entry);
}
/**
* Reads the entries from the database and plays them all.
*/
public void readAndPlay ()
throws PersistenceException
{
Chooser.repository.populateSongs(_entry);
}
/**
* Updates the category for the displayed entry.
*/
public void recategorizeEntry ()
throws PersistenceException
{
Chooser.model.recategorize(_entry, _newcatid);
}
// documentation inherited from interface
public void taskCompleted (String name, Object result)
{
if (name.equals("readEntries")) {
// the result should be an array of entry objects with which
// we can populate our button list
_entries = (Entry[])result;
_list.populateEntries(_entries);
} else if (name.equals("readAndPlay")) {
for (int i = 0; i < _entry.songs.length; i++) {
Chooser.scontrol.append(_entry.songs[i].entryid,
_entry.songs[i].songid,
_entry.songs[i].location);
}
} else if (name.equals("readSongs")) {
_list.populateSong(_entry);
} else {
super.taskCompleted(name, result);
}
}
/**
* Reads the entries from the database. This will be called on a
* separate thread and should be prepared to be called repeatedly
* without undue overhead. When the user browses into a song and back
* up again, this method is called to repopulate the entries.
*/
public abstract Entry[] readEntries ()
throws PersistenceException;
// documentation inherited from interface
public boolean handleAction (ActionEvent e)
{
String cmd = e.getActionCommand();
if (cmd.equals(EntryItem.BROWSE)) {
_entry = EntryItem.getEntry(e.getSource());
// start up the task that reads this CDs songs from the database
TaskMaster.invokeMethodTask("readSongs", this, this);
} else if (cmd.equals("playall")) {
_entry = EntryItem.getEntry(e.getSource());
// start up the task that reads this CDs songs from the database
TaskMaster.invokeMethodTask("readAndPlay", this, this);
} else if (cmd.equals("play")) {
Song song = SongItem.getSong(e.getSource());
Chooser.scontrol.append(song.entryid, song.songid, song.location);
} else if (cmd.equals("up")) {
// re-read the category beacuse this entry may have been
// recategorized
TaskMaster.invokeMethodTask("readEntries", this, this);
} else if (cmd.equals("edit")) {
EditDialog dialog = new EditDialog(_entry);
dialog.setSize(400, 400);
SwingUtil.centerWindow(dialog);
dialog.show();
} else if (cmd.equals("categorize")) {
JComboBox cb = (JComboBox)e.getSource();
String catname = (String)cb.getSelectedItem();
_newcatid = Chooser.model.getCategoryId(catname);
// do the recategorization in a separate task
TaskMaster.invokeMethodTask("recategorizeEntry", this, this);
} else {
return super.handleAction(e);
}
return true;
}
// documentation inherited from interface
public void ancestorAdded (AncestorEvent e)
{
// stick a "loading" label in the list to let the user know
// what's up
_list._bpanel.add(new JLabel("Loading..."));
// we need to revalidate the component because we added a child
_list.revalidate();
_list.repaint();
// start up the task that reads the CD info from the database
TaskMaster.invokeMethodTask("readEntries", this, this);
}
// documentation inherited from interface
public void ancestorRemoved (AncestorEvent e)
{
// clear out our entry ui elements
_list._bpanel.removeAll();
}
// documentation inherited from interface
public void ancestorMoved (AncestorEvent e)
{
// nothing doing
}
protected EntryList _list;
protected Entry[] _entries;
protected Entry _entry;
protected int _newcatid;
}
@@ -0,0 +1,72 @@
//
// $Id: EntryItem.java,v 1.1 2003/05/04 18:16:06 mdb Exp $
package robodj.chooser;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import com.samskivert.swing.HGroupLayout;
import robodj.repository.Entry;
import robodj.util.ButtonUtil;
/**
* Displays an entry in the browser.
*/
public class EntryItem extends Item
{
public static final String BROWSE = "entry:browse";
public static final String PLAY = "entry:play";
public EntryItem (Entry entry)
{
_entry = entry;
// set up our layout manager
HGroupLayout gl = new HGroupLayout(HGroupLayout.NONE);
gl.setOffAxisPolicy(HGroupLayout.STRETCH);
gl.setJustification(HGroupLayout.LEFT);
setLayout(gl);
// create a browse and a play button
JButton button;
// add a browse button
button = ButtonUtil.createControlButton(
BROWSE_ENTRY_TIP, BROWSE, _browseIcon, true);
button.putClientProperty(ENTRY_PROP, entry);
add(button, HGroupLayout.FIXED);
// add a play all button
button = ButtonUtil.createControlButton(
PLAY_ENTRY_TIP, PLAY, _playIcon, true);
button.putClientProperty(ENTRY_PROP, entry);
add(button, HGroupLayout.FIXED);
// add the entry title
JLabel entryLabel = new JLabel(entry.title);
entryLabel.setFont(_nameFont);
entryLabel.setToolTipText(entry.title + " (" + entry.entryid + ")");
add(entryLabel);
}
public static Entry getEntry (Object source)
{
return (Entry)((JButton)source).getClientProperty(ENTRY_PROP);
}
protected Entry _entry;
protected static final String ENTRY_PROP = "entry";
protected static final String BROWSE_ENTRY_TIP =
"Browse the songs in this album";
protected static final String PLAY_ENTRY_TIP =
"Append this album to the playlist";
protected static ImageIcon _browseIcon =
ButtonUtil.getIcon(ICON_ROOT + "browse.png");
}
@@ -1,11 +1,9 @@
//
// $Id: EntryList.java,v 1.12 2002/03/03 20:56:12 mdb Exp $
// $Id: EntryList.java,v 1.13 2003/05/04 18:16:06 mdb Exp $
package robodj.chooser;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;
@@ -21,7 +19,7 @@ import robodj.repository.*;
import robodj.util.ButtonUtil;
public abstract class EntryList extends JScrollPane
implements TaskObserver, ActionListener, AncestorListener
implements ControllerProvider
{
public EntryList ()
{
@@ -46,88 +44,22 @@ public abstract class EntryList extends JScrollPane
// use a special font for our name buttons
_titleFont = new Font("Helvetica", Font.BOLD, 14);
_nameFont = new Font("Helvetica", Font.PLAIN, 12);
// create our icons
_browseIcon = ButtonUtil.getIcon(BROWSE_ICON_PATH);
_playIcon = ButtonUtil.getIcon(PLAY_ICON_PATH);
// listen to ancestor events
addAncestorListener(this);
// create our controller
_controller = createController();
}
public void taskCompleted (String name, Object result)
public Controller getController ()
{
if (name.equals("readEntries")) {
// the result should be an array of entry objects with which
// we can populate our button list
_entries = (Entry[])result;
populateEntries(_entries);
} else if (name.equals("readAndPlay")) {
for (int i = 0; i < _entry.songs.length; i++) {
Chooser.scontrol.append(_entry.songs[i].entryid,
_entry.songs[i].songid,
_entry.songs[i].location);
}
} else if (name.equals("readSongs")) {
populateSong(_entry);
}
return _controller;
}
public void taskFailed (String name, Throwable exception)
{
String msg;
if (Exception.class.equals(exception.getClass())) {
msg = exception.getMessage();
} else {
msg = exception.toString();
}
JOptionPane.showMessageDialog(this, msg, "Error",
JOptionPane.ERROR_MESSAGE);
Log.logStackTrace(exception);
}
/**
* Reads the entries from the database. This will be called on a
* separate thread and should be prepared to be called repeatedly
* without undue overhead. When the user browses into a song and back
* up again, this method is called to repopulate the entries.
*/
public abstract Entry[] readEntries ()
throws PersistenceException;
/** Creates the controller for this entry list. */
protected abstract Controller createController ();
/** The string to display when there are no matching results. */
protected abstract String getEmptyString ();
/**
* Reads the entries from the database.
*/
public void readSongs ()
throws PersistenceException
{
Chooser.repository.populateSongs(_entry);
}
/**
* Reads the entries from the database and plays them all.
*/
public void readAndPlay ()
throws PersistenceException
{
Chooser.repository.populateSongs(_entry);
}
/**
* Updates the category for the displayed entry.
*/
public void recategorizeEntry ()
throws PersistenceException
{
Chooser.model.recategorize(_entry, _newcatid);
}
/**
* Creates the proper buttons, etc. for each entry.
*/
@@ -158,37 +90,12 @@ public abstract class EntryList extends JScrollPane
_bpanel.add(label);
}
// create a browse and a play button
GroupLayout gl = new HGroupLayout(GroupLayout.NONE);
gl.setOffAxisPolicy(GroupLayout.STRETCH);
gl.setJustification(GroupLayout.LEFT);
JPanel hpanel = new JPanel(gl);
JButton button;
// add a browse button
button = ButtonUtil.createControlButton(
BROWSE_ENTRY_TIP, "browse", _browseIcon, this, true);
button.putClientProperty("entry", entries[i]);
hpanel.add(button, GroupLayout.FIXED);
// add a play all button
button = ButtonUtil.createControlButton(
PLAY_ENTRY_TIP, "playall", _playIcon, this, true);
button.putClientProperty("entry", entries[i]);
hpanel.add(button, GroupLayout.FIXED);
// add the entry title
JLabel entryLabel = new JLabel(entries[i].title);
entryLabel.setFont(_nameFont);
entryLabel.setToolTipText(entries[i].title +
" (" + entries[i].entryid + ")");
hpanel.add(entryLabel);
_bpanel.add(hpanel);
// create an entry item for this entry
_bpanel.add(new EntryItem(entries[i]));
}
// if there were no entries, stick a label in to that effect
if (_entries.length == 0) {
if (entries.length == 0) {
_bpanel.add(new JLabel(getEmptyString()));
}
@@ -224,12 +131,12 @@ public abstract class EntryList extends JScrollPane
// add a button for getting the heck out of here
JButton upbtn = ButtonUtil.createControlButton(
UP_TIP, "up", ButtonUtil.getIcon(UP_ICON_PATH), this, true);
UP_TIP, "up", ButtonUtil.getIcon(UP_ICON_PATH), true);
header.add(upbtn, GroupLayout.FIXED);
// create an edit button
JButton ebtn = ButtonUtil.createControlButton(
EDIT_TIP, "edit", ButtonUtil.getIcon(EDIT_ICON_PATH), this, true);
EDIT_TIP, "edit", ButtonUtil.getIcon(EDIT_ICON_PATH), true);
header.add(ebtn, GroupLayout.FIXED);
// add a combo box for categorizing
@@ -238,7 +145,7 @@ public abstract class EntryList extends JScrollPane
header.add(catcombo, GroupLayout.FIXED);
// configure the combo box
catcombo.addActionListener(this);
catcombo.addActionListener(Controller.DISPATCHER);
catcombo.setActionCommand("categorize");
int catid = Chooser.model.getCategory(entry.entryid);
int catidx = ModelUtil.getCategoryIndex(Chooser.model, catid);
@@ -260,23 +167,7 @@ public abstract class EntryList extends JScrollPane
// and add buttons for every song
for (int i = 0; i < entry.songs.length; i++) {
gl = new HGroupLayout(GroupLayout.NONE);
gl.setJustification(GroupLayout.LEFT);
JPanel hpanel = new JPanel(gl);
// add a button for playing the song
JButton button = ButtonUtil.createControlButton(
PLAY_SONG_TIP, "play", _playIcon, this, true);
button.putClientProperty("song", entry.songs[i]);
hpanel.add(button, GroupLayout.FIXED);
// add the song title
JLabel trackLabel = new JLabel(entry.songs[i].title);
trackLabel.setFont(_nameFont);
trackLabel.setToolTipText(entry.songs[i].title);
hpanel.add(trackLabel);
_bpanel.add(hpanel);
_bpanel.add(new SongItem(entry.songs[i], SongItem.BROWSER));
}
// reset our scroll position so that we're displaying the top of
@@ -294,99 +185,17 @@ public abstract class EntryList extends JScrollPane
model.setValue(model.getMinimum());
}
public void actionPerformed (ActionEvent e)
{
String cmd = e.getActionCommand();
if (cmd.equals("browse")) {
JButton src = (JButton)e.getSource();
_entry = (Entry)src.getClientProperty("entry");
// start up the task that reads this CDs songs from the database
TaskMaster.invokeMethodTask("readSongs", this, this);
} else if (cmd.equals("playall")) {
JButton src = (JButton)e.getSource();
_entry = (Entry)src.getClientProperty("entry");
// start up the task that reads this CDs songs from the database
TaskMaster.invokeMethodTask("readAndPlay", this, this);
} else if (cmd.equals("play")) {
JButton src = (JButton)e.getSource();
Song song = (Song)src.getClientProperty("song");
Chooser.scontrol.append(song.entryid, song.songid, song.location);
} else if (cmd.equals("up")) {
// re-read the category beacuse this entry may have been
// recategorized
TaskMaster.invokeMethodTask("readEntries", this, this);
} else if (cmd.equals("edit")) {
EditDialog dialog = new EditDialog(_entry);
dialog.setSize(400, 400);
SwingUtil.centerWindow(dialog);
dialog.show();
} else if (cmd.equals("categorize")) {
JComboBox cb = (JComboBox)e.getSource();
String catname = (String)cb.getSelectedItem();
_newcatid = Chooser.model.getCategoryId(catname);
// do the recategorization in a separate task
TaskMaster.invokeMethodTask("recategorizeEntry", this, this);
} else {
Log.warning("Unknown action event: " + cmd);
}
}
public void ancestorAdded (AncestorEvent e)
{
// stick a "loading" label in the list to let the user know
// what's up
_bpanel.add(new JLabel("Loading..."));
// we need to revalidate the component because we added a child
revalidate();
repaint();
// start up the task that reads the CD info from the database
TaskMaster.invokeMethodTask("readEntries", this, this);
}
public void ancestorRemoved (AncestorEvent e)
{
// clear out our entry ui elements
_bpanel.removeAll();
}
public void ancestorMoved (AncestorEvent e)
{
// nothing doing
}
protected Controller _controller;
protected JPanel _bpanel;
protected Entry[] _entries;
protected Entry _entry;
protected int _newcatid;
protected Font _titleFont;
protected Font _nameFont;
protected ImageIcon _playIcon;
protected ImageIcon _browseIcon;
protected static final String PLAY_ENTRY_TIP =
"Append this album to the playlist";
protected static final String PLAY_SONG_TIP =
"Append this song to the playlist";
protected static final String BROWSE_ENTRY_TIP =
"Browse the songs in this album";
protected static final String UP_TIP =
"Back up to albums listing";
protected static final String EDIT_TIP =
"Edit the album information";
protected static final String ICON_ROOT = "/robodj/chooser/images/";
protected static final String PLAY_ICON_PATH = ICON_ROOT + "play.png";
protected static final String BROWSE_ICON_PATH = ICON_ROOT + "browse.png";
protected static final String UP_ICON_PATH = ICON_ROOT + "up.png";
protected static final String EDIT_ICON_PATH = ICON_ROOT + "edit.png";
}
@@ -0,0 +1,39 @@
//
// $Id: Item.java,v 1.1 2003/05/04 18:16:06 mdb Exp $
package robodj.chooser;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Graphics;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import com.samskivert.swing.util.SwingUtil;
import robodj.util.ButtonUtil;
/**
* The base class for items displayed in an entry list or playlist.
*/
public class Item extends JPanel
{
// documentation inherited
protected void paintChildren (Graphics g)
{
Graphics2D gfx = (Graphics2D)g;
Object key = SwingUtil.activateAntiAliasing(gfx);
super.paintChildren(g);
SwingUtil.restoreAntiAliasing(gfx, key);
}
protected static Font _nameFont = new Font("Dialog", Font.PLAIN, 12);
protected static Font _hasVotesFont =
new Font("Dialog", Font.ITALIC, 12);
protected static final String ICON_ROOT = "/robodj/chooser/images/";
protected static ImageIcon _playIcon =
ButtonUtil.getIcon(ICON_ROOT + "play.png");
}
@@ -0,0 +1,96 @@
//
// $Id: ItemController.java,v 1.1 2003/05/04 18:16:06 mdb Exp $
package robodj.chooser;
import java.awt.event.ActionEvent;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import com.samskivert.io.PersistenceException;
import com.samskivert.swing.Controller;
import com.samskivert.swing.util.TaskMaster;
import com.samskivert.swing.util.TaskObserver;
import robodj.Log;
import robodj.repository.Song;
/**
* Handles standard commands when displaying lists of items.
*/
public abstract class ItemController extends Controller
implements TaskObserver
{
public ItemController (JComponent panel)
{
_panel = panel;
}
// documentation inherited from interface
public boolean handleAction (ActionEvent e)
{
String cmd = e.getActionCommand();
if (cmd.equals(SongItem.ADD_VOTE)) {
JButton button = (JButton)e.getSource();
SongItem item = (SongItem)button.getParent();
_song = item.getSong();
if (_song.addVote(Chooser.frame.getUser(true))) {
item.update();
TaskMaster.invokeMethodTask("updateSong", this, this);
}
} else if (cmd.equals(SongItem.CLEAR_VOTE)) {
JButton button = (JButton)e.getSource();
SongItem item = (SongItem)button.getParent();
_song = item.getSong();
if (_song.clearVote(Chooser.frame.getUser(true))) {
item.update();
TaskMaster.invokeMethodTask("updateSong", this, this);
}
} else {
Log.warning("Unknown action event: " + cmd);
return false;
}
return true;
}
/**
* Updates the song referenced by {@link #_song} in the repository.
*/
public void updateSong ()
{
if (_song != null) {
try {
Chooser.repository.updateSong(_song);
} catch (PersistenceException pe) {
Chooser.reportError(
"Failure updating song '" + _song + "'", pe);
}
}
}
// documentation inherited from interface
public void taskCompleted (String name, Object result)
{
}
// documentation inherited from interface
public void taskFailed (String name, Throwable exception)
{
String msg;
if (Exception.class.equals(exception.getClass())) {
msg = exception.getMessage();
} else {
msg = exception.toString();
}
JOptionPane.showMessageDialog(_panel, msg, "Error",
JOptionPane.ERROR_MESSAGE);
Log.logStackTrace(exception);
}
protected JComponent _panel;
protected Song _song;
}
@@ -0,0 +1,222 @@
//
// $Id: PlaylistController.java,v 1.1 2003/05/04 18:16:06 mdb Exp $
package robodj.chooser;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.util.Iterator;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import com.samskivert.io.PersistenceException;
import com.samskivert.util.StringUtil;
import com.samskivert.swing.util.SwingUtil;
import com.samskivert.swing.util.TaskAdapter;
import com.samskivert.swing.util.TaskMaster;
import robodj.Log;
import robodj.chooser.PlaylistPanel.PlaylistEntry;
import robodj.repository.Entry;
import robodj.repository.Song;
import robodj.util.ServerControl.PlayingListener;
/**
* Handles playlist UI commands.
*/
public class PlaylistController extends ItemController
implements PlayingListener
{
public PlaylistController (PlaylistPanel panel)
{
super(panel);
_panel = panel;
// add ourselves as a playing listener
Chooser.scontrol.addPlayingListener(this);
}
// documentation inherited
public boolean handleAction (ActionEvent e)
{
String cmd = e.getActionCommand();
if (cmd.equals(PlaylistPanel.CLEAR)) {
TaskMaster.invokeMethodTask("clear", Chooser.scontrol, this);
} else if (cmd.equals(PlaylistPanel.REFRESH)) {
_panel.prepareForRefresh();
// start up the task that reads the CD info from the database
TaskMaster.invokeMethodTask("readPlaylist", this, this);
} else if (cmd.equals(SongItem.SKIP_TO)) {
JButton button = (JButton)e.getSource();
SongItem item = (SongItem)button.getParent();
// fire up a task to talk to the music daemon
final int songid = item.getSong().songid;
TaskMaster.invokeTask("noop", new TaskAdapter() {
public Object invoke () throws Exception {
Chooser.scontrol.skipto(songid);
return null;
}
}, this);
} else if (cmd.equals(SongItem.REMOVE)) {
JButton button = (JButton)e.getSource();
SongItem item = (SongItem)button.getParent();
// remove the entry UI elements
item.getParent().remove(item);
SwingUtil.refresh(_panel); // relayout
// remove the entry from the playlist
_panel.plist.remove((PlaylistEntry)item.extra);
// fire up a task to talk to the music daemon
final int songid = item.getSong().songid;
TaskMaster.invokeTask("noop", new TaskAdapter() {
public Object invoke () throws Exception {
Chooser.scontrol.remove(songid);
return null;
}
}, this);
} else if (cmd.equals(PlaylistPanel.REMOVE_ALL)) {
JButton src = (JButton)e.getSource();
PlaylistEntry entry =
(PlaylistEntry)src.getClientProperty("entry");
// remove all entries starting with this one until we get to
// one that has a different entryid
int count = 0;
Iterator iter = _panel.plist.iterator();
while (iter.hasNext()) {
PlaylistEntry pe = (PlaylistEntry)iter.next();
if (entry == pe) {
count++;
// remove the entry UI elements
pe.item.getParent().remove(pe.item);
// remove the entry
iter.remove();
} else if (count > 0) {
if (pe.entry.entryid == entry.entry.entryid) {
count++;
// remove the entry UI elements
pe.item.getParent().remove(pe.item);
// remove the entry
iter.remove();
} else {
// we hit an entry that doesn't match, bail
break;
}
}
}
// remove the title and stuff
JPanel tpanel = (JPanel)src.getParent();
tpanel.getParent().remove(tpanel);
_panel.revalidate(); // relayout
// fire up a task to talk to the music daemon, telling it to
// remove those entries
final int songid = entry.song.songid;
final int fcount = count;
TaskMaster.invokeTask("noop", new TaskAdapter() {
public Object invoke () throws Exception {
Chooser.scontrol.removeGroup(songid, fcount);
return null;
}
}, this);
} else {
return super.handleAction(e);
}
return true;
}
public void readPlaylist ()
throws PersistenceException
{
// clear out any previous playlist
_panel.plist.clear();
// get the playlist from the music daemon
String[] plist = Chooser.scontrol.getPlaylist();
// parse it into playlist entries
for (int i = 0; i < plist.length; i++) {
String[] toks = StringUtil.split(plist[i], "\t");
int eid, sid;
try {
eid = Integer.parseInt(toks[0]);
sid = Integer.parseInt(toks[1]);
} catch (NumberFormatException nfe) {
Log.warning("Bogus playlist record: '" + plist[i] + "'.");
continue;
}
Entry entry = Chooser.model.getEntry(eid);
if (entry != null) {
Song song = entry.getSong(sid);
_panel.plist.add(new PlaylistEntry(entry, song));
} else {
Log.warning("Unable to load entry [eid=" + eid + "].");
}
}
// now find out what's currently playing
Chooser.scontrol.refreshPlaying();
}
public void playingUpdated (int songid, boolean paused)
{
// unhighlight whoever is playing now
PlaylistEntry pentry = getPlayingEntry();
if (pentry != null && pentry.item != null) {
pentry.item.setIsPlaying(false);
}
// grab the new playing song id
_playid = songid;
// highlight the playing song
for (int i = 0; i < _panel.plist.size(); i++) {
PlaylistEntry entry = (PlaylistEntry)_panel.plist.get(i);
if (entry.song.songid == _playid && entry.item != null) {
entry.item.setIsPlaying(true);
}
}
_panel.repaint();
}
protected PlaylistEntry getPlayingEntry ()
{
for (int i = 0; i < _panel.plist.size(); i++) {
PlaylistEntry entry = (PlaylistEntry)_panel.plist.get(i);
if (entry.song.songid == _playid) {
return entry;
}
}
return null;
}
public void taskCompleted (String name, Object result)
{
if (name.equals("readPlaylist")) {
_panel.populatePlaylist(_panel.plist, _playid);
} else {
super.taskCompleted(name, result);
}
}
protected PlaylistPanel _panel;
protected int _playid;
}
@@ -1,5 +1,5 @@
//
// $Id: PlaylistPanel.java,v 1.13 2002/03/03 20:56:12 mdb Exp $
// $Id: PlaylistPanel.java,v 1.14 2003/05/04 18:16:06 mdb Exp $
package robodj.chooser;
@@ -19,11 +19,32 @@ import com.samskivert.util.StringUtil;
import robodj.Log;
import robodj.repository.*;
import robodj.util.ButtonUtil;
import robodj.util.ServerControl.PlayingListener;
public class PlaylistPanel extends JPanel
implements TaskObserver, ActionListener, PlayingListener
public class PlaylistPanel extends ControlledPanel
{
public static class PlaylistEntry
{
public Entry entry;
public Song song;
public SongItem item;
public PlaylistEntry (Entry entry, Song song)
{
this.entry = entry;
this.song = song;
}
}
public static final String REFRESH = "refresh";
public static final String CLEAR = "clear";
public static final String REMOVE_ALL = "remove_all";
public ArrayList plist = new ArrayList();
public PlaylistPanel ()
{
GroupLayout gl = new VGroupLayout(GroupLayout.STRETCH);
@@ -50,127 +71,34 @@ public class PlaylistPanel extends JPanel
// add our control buttons
_clearbut = ButtonUtil.createControlButton(
CLEAR_TIP, "clear", CLEAR_ICON_PATH, this);
CLEAR_TIP, "clear", CLEAR_ICON_PATH);
cbar.add(_clearbut);
cbar.add(ButtonUtil.createControlButton(
REFRESH_TIP, "refresh", REFRESH_ICON_PATH, this));
REFRESH_TIP, "refresh", REFRESH_ICON_PATH));
add(cbar, GroupLayout.FIXED);
// use a special font for our name buttons
_nameFont = new Font("Helvetica", Font.PLAIN, 12);
// create our icons
_skiptoIcon = ButtonUtil.getIcon(SKIPTO_ICON_PATH);
_removeSongIcon = ButtonUtil.getIcon(REMOVE_SONG_ICON_PATH);
_removeEntryIcon = ButtonUtil.getIcon(REMOVE_ENTRY_ICON_PATH);
// add ourselves as a playing listener
Chooser.scontrol.addPlayingListener(this);
// load up the playlist
refreshPlaylist();
}
public void actionPerformed (ActionEvent e)
// documentation inherited
public void addNotify ()
{
String cmd = e.getActionCommand();
if (cmd.equals("clear")) {
TaskMaster.invokeMethodTask("clear", Chooser.scontrol, this);
} else if (cmd.equals("refresh")) {
refreshPlaylist();
} else if (cmd.equals("skipto")) {
JButton src = (JButton)e.getSource();
PlaylistEntry entry =
(PlaylistEntry)src.getClientProperty("entry");
// fire up a task to talk to the music daemon
final int songid = entry.song.songid;
TaskMaster.invokeTask("noop", new TaskAdapter() {
public Object invoke () throws Exception {
Chooser.scontrol.skipto(songid);
return null;
}
}, this);
} else if (cmd.equals("remove")) {
JButton src = (JButton)e.getSource();
PlaylistEntry entry =
(PlaylistEntry)src.getClientProperty("entry");
// remove the entry UI elements
JPanel epanel = (JPanel)entry.label.getParent();
epanel.getParent().remove(epanel);
revalidate(); // relayout
// remove the entry from the playlist
_plist.remove(entry);
// fire up a task to talk to the music daemon
final int songid = entry.song.songid;
TaskMaster.invokeTask("noop", new TaskAdapter() {
public Object invoke () throws Exception {
Chooser.scontrol.remove(songid);
return null;
}
}, this);
} else if (cmd.equals("remove_all")) {
JButton src = (JButton)e.getSource();
PlaylistEntry entry =
(PlaylistEntry)src.getClientProperty("entry");
// remove all entries starting with this one until we get to
// one that has a different entryid
int count = 0;
Iterator iter = _plist.iterator();
while (iter.hasNext()) {
PlaylistEntry pe = (PlaylistEntry)iter.next();
if (entry == pe) {
count++;
// remove the entry UI elements
JPanel epanel = (JPanel)pe.label.getParent();
epanel.getParent().remove(epanel);
// remove the entry
iter.remove();
} else if (count > 0) {
if (pe.entry.entryid == entry.entry.entryid) {
count++;
// remove the entry UI elements
JPanel epanel = (JPanel)pe.label.getParent();
epanel.getParent().remove(epanel);
// remove the entry
iter.remove();
} else {
// we hit an entry that doesn't match, bail
break;
}
}
}
// remove the title and stuff
JPanel tpanel = (JPanel)src.getParent();
tpanel.getParent().remove(tpanel);
revalidate(); // relayout
// fire up a task to talk to the music daemon, telling it to
// remove those entries
final int songid = entry.song.songid;
final int fcount = count;
TaskMaster.invokeTask("noop", new TaskAdapter() {
public Object invoke () throws Exception {
Chooser.scontrol.removeGroup(songid, fcount);
return null;
}
}, this);
}
super.addNotify();
// load up the playlist
_controller.postAction(this, REFRESH);
}
protected void refreshPlaylist ()
// documentation inherited
protected Controller createController ()
{
return new PlaylistController(this);
}
protected void prepareForRefresh ()
{
// stick a "loading" label in the list to let the user know
// what's up
@@ -179,65 +107,9 @@ public class PlaylistPanel extends JPanel
// swing doesn't automatically validate after adding/removing
// children
_bpanel.revalidate();
// start up the task that reads the CD info from the database
TaskMaster.invokeMethodTask("readPlaylist", this, this);
}
public void readPlaylist ()
throws PersistenceException
{
// clear out any previous playlist
_plist.clear();
// get the playlist from the music daemon
String[] plist = Chooser.scontrol.getPlaylist();
// parse it into playlist entries
for (int i = 0; i < plist.length; i++) {
String[] toks = StringUtil.split(plist[i], "\t");
int eid, sid;
try {
eid = Integer.parseInt(toks[0]);
sid = Integer.parseInt(toks[1]);
} catch (NumberFormatException nfe) {
Log.warning("Bogus playlist record: '" + plist[i] + "'.");
continue;
}
Entry entry = Chooser.model.getEntry(eid);
if (entry != null) {
Song song = entry.getSong(sid);
_plist.add(new PlaylistEntry(entry, song));
} else {
Log.warning("Unable to load entry [eid=" + eid + "].");
}
}
// now find out what's currently playing
Chooser.scontrol.refreshPlaying();
}
public void taskCompleted (String name, Object result)
{
if (name.equals("readPlaylist")) {
populatePlaylist();
}
}
public void taskFailed (String name, Throwable exception)
{
String msg;
if (Exception.class.equals(exception.getClass())) {
msg = exception.getMessage();
} else {
msg = exception.toString();
}
JOptionPane.showMessageDialog(this, msg, "Error",
JOptionPane.ERROR_MESSAGE);
Log.logStackTrace(exception);
}
protected void populatePlaylist ()
protected void populatePlaylist (ArrayList plist, int playid)
{
// clear out any existing children
_bpanel.removeAll();
@@ -249,8 +121,8 @@ public class PlaylistPanel extends JPanel
// add buttons for every entry
String title = null;
for (int i = 0; i < _plist.size(); i++) {
PlaylistEntry entry = (PlaylistEntry)_plist.get(i);
for (int i = 0; i < plist.size(); i++) {
PlaylistEntry entry = (PlaylistEntry)plist.get(i);
JButton button;
// add record/artist indicators when the record and artist
@@ -266,41 +138,27 @@ public class PlaylistPanel extends JPanel
JLabel label = new JLabel(entry.entry.title + " - " +
entry.entry.artist);
tpanel.add(label);
tpanel.add(newButton(REMOVE_ENTRY_TIP, "remove_all",
tpanel.add(newButton(REMOVE_ENTRY_TIP, REMOVE_ALL,
_removeEntryIcon, entry));
_bpanel.add(tpanel);
}
// create a browse and a play button
JPanel hpanel = new JPanel();
gl = new HGroupLayout(GroupLayout.NONE);
gl.setOffAxisPolicy(GroupLayout.STRETCH);
gl.setJustification(GroupLayout.LEFT);
hpanel.setLayout(gl);
hpanel.add(newButton(SKIPTO_TIP, "skipto", _skiptoIcon, entry));
hpanel.add(newButton(REMOVE_SONG_TIP, "remove",
_removeSongIcon, entry));
entry.label = new JLabel(entry.song.title);
entry.label.setForeground((entry.song.songid == _playid) ?
Color.red : Color.black);
entry.label.setFont(_nameFont);
entry.label.setToolTipText(entry.song.title);
hpanel.add(entry.label);
// create a song item to display this playlist entry
entry.item = new SongItem(entry.song, SongItem.PLAYLIST);
entry.item.setIsPlaying(entry.song.songid == playid);
entry.item.extra = entry;
_bpanel.add(entry.item);
// let the bpanel know that we want to scroll the active track
// label into place once we're all laid out
if (entry.song.songid == _playid) {
_bpanel.setScrollTarget(hpanel);
if (entry.song.songid == playid) {
_bpanel.setScrollTarget(entry.item);
}
_bpanel.add(hpanel);
}
// if there were no entries, stick a label in to that effect
if (_plist.size() == 0) {
if (plist.size() == 0) {
_bpanel.add(new JLabel("Nothing playing."));
}
@@ -313,59 +171,11 @@ public class PlaylistPanel extends JPanel
ImageIcon icon, Object clientProperty)
{
JButton button = ButtonUtil.createControlButton(
tooltip, command, icon, this, true);
tooltip, command, icon, true);
button.putClientProperty("entry", clientProperty);
return button;
}
public void playingUpdated (int songid, boolean paused)
{
// unhighlight whoever is playing now
PlaylistEntry pentry = getPlayingEntry();
if (pentry != null && pentry.label != null) {
pentry.label.setForeground(Color.black);
}
// grab the new playing song id
_playid = songid;
// highlight the playing song
for (int i = 0; i < _plist.size(); i++) {
PlaylistEntry entry = (PlaylistEntry)_plist.get(i);
if (entry.song.songid == _playid &&
entry.label != null) {
entry.label.setForeground(Color.red);
}
}
repaint();
}
protected PlaylistEntry getPlayingEntry ()
{
for (int i = 0; i < _plist.size(); i++) {
PlaylistEntry entry = (PlaylistEntry)_plist.get(i);
if (entry.song.songid == _playid) {
return entry;
}
}
return null;
}
protected static class PlaylistEntry
{
public Entry entry;
public Song song;
public JLabel label;
public PlaylistEntry (Entry entry, Song song)
{
this.entry = entry;
this.song = song;
}
}
/**
* A panel that can be made to scroll a particular child into view
* when it becomes visible.
@@ -402,10 +212,6 @@ public class PlaylistPanel extends JPanel
protected SmartPanel _bpanel;
protected JButton _clearbut;
protected ArrayList _plist = new ArrayList();
protected int _playid;
protected int _oldid = -1;
protected Font _nameFont;
protected ImageIcon _skiptoIcon;
@@ -1,9 +1,10 @@
//
// $Id: QueryEntryList.java,v 1.1 2002/02/22 07:06:33 mdb Exp $
// $Id: QueryEntryList.java,v 1.2 2003/05/04 18:16:06 mdb Exp $
package robodj.chooser;
import com.samskivert.io.PersistenceException;
import com.samskivert.swing.Controller;
import robodj.repository.Entry;
@@ -17,16 +18,17 @@ public class QueryEntryList extends EntryList
_query = query;
}
/**
* Reads in the entries for this query.
*/
public Entry[] readEntries ()
throws PersistenceException
// documentation inherited
protected Controller createController ()
{
if (_entries == null) {
_entries = Chooser.repository.matchEntries(_query);
}
return _entries;
return new EntryController(this) {
public Entry[] readEntries () throws PersistenceException {
if (_entries == null) {
_entries = Chooser.repository.matchEntries(_query);
}
return _entries;
}
};
}
protected String getEmptyString ()
@@ -36,7 +38,4 @@ public class QueryEntryList extends EntryList
/** The query with which we're looking up entries. */
protected String _query;
/** A cached copy of our query results. */
protected Entry[] _entries;
}
@@ -0,0 +1,166 @@
//
// $Id: SongItem.java,v 1.1 2003/05/04 18:16:06 mdb Exp $
package robodj.chooser;
import java.awt.Color;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import com.samskivert.swing.HGroupLayout;
import com.samskivert.util.StringUtil;
import robodj.repository.Song;
import robodj.util.ButtonUtil;
/**
* Displays a particular song (used by the playlist and the entry list).
*/
public class SongItem extends Item
{
/** Configures this song item for playlist mode. */
public static final int PLAYLIST = 0;
/** Configures this song item for browser mode. */
public static final int BROWSER = 1;
// playlist mode actions
public static final String SKIP_TO = "song:skip_to";
public static final String REMOVE = "song:remove";
// browser mode actions
public static final String PLAY = "song:play";
// ubiquitous actions
public static final String ADD_VOTE = "song:add_vote";
public static final String CLEAR_VOTE = "song:clear_vote";
/** Used depending on where this song is being displayed. */
public Object extra;
/**
* Creates a song item configured with the supplied song and specified
* mode.
*/
public SongItem (Song song, int mode)
{
_song = song;
// configure our layout manager
HGroupLayout gl = new HGroupLayout(HGroupLayout.NONE);
gl.setJustification(HGroupLayout.LEFT);
setLayout(gl);
// create our control buttons
JButton button;
switch (mode) {
case PLAYLIST:
// add a button for skipping to this song
button = ButtonUtil.createControlButton(
SKIP_TO_TIP, SKIP_TO, _skipToIcon, true);
button.putClientProperty("song", _song);
add(button, HGroupLayout.FIXED);
// add a button for removing this song
button = ButtonUtil.createControlButton(
REMOVE_TIP, REMOVE, _removeIcon, true);
button.putClientProperty("song", _song);
add(button, HGroupLayout.FIXED);
break;
case BROWSER:
// add a button for playing the song
button = ButtonUtil.createControlButton(
PLAY_SONG_TIP, PLAY, _playIcon, true);
button.putClientProperty(SONG_PROP, _song);
add(button, HGroupLayout.FIXED);
break;
default:
System.err.println("Unknown song mode: " + mode);
break;
}
// add a vote button
_voteButton = ButtonUtil.createControlButton(
ADD_VOTE_TIP, ADD_VOTE, _addVoteIcon, true);
_voteButton.putClientProperty(SONG_PROP, song);
add(_voteButton, HGroupLayout.FIXED);
// add the song title
_trackLabel = new JLabel(_song.title);
add(_trackLabel);
// update our display based on our votes
update();
}
public void setIsPlaying (boolean isPlaying)
{
_trackLabel.setForeground(isPlaying ? Color.red : Color.black);
}
public void update ()
{
if (!StringUtil.blank(_song.votes)) {
_trackLabel.setFont(_hasVotesFont);
_trackLabel.setToolTipText("<html>" + _song.title +
"<br>Votes: " + _song.votes);
} else {
_trackLabel.setFont(_nameFont);
_trackLabel.setToolTipText(_song.title);
}
if (_song.hasVoted(Chooser.frame.getUser(false))) {
_voteButton.setIcon(_clearVoteIcon);
_voteButton.setActionCommand(CLEAR_VOTE);
_voteButton.setToolTipText(CLEAR_VOTE_TIP);
} else {
_voteButton.setIcon(_addVoteIcon);
_voteButton.setActionCommand(ADD_VOTE);
_voteButton.setToolTipText(ADD_VOTE_TIP);
}
repaint();
}
public Song getSong ()
{
return _song;
}
public static Song getSong (Object source)
{
return (Song)((JButton)source).getClientProperty(SONG_PROP);
}
protected Song _song;
protected JButton _voteButton;
protected JLabel _trackLabel;
protected static final String SONG_PROP = "song";
protected static final String SKIP_TO_TIP =
"Skip to this song";
protected static final String REMOVE_TIP =
"Remove this song from the playlist";
protected static final String PLAY_SONG_TIP =
"Append this song to the playlist";
protected static final String ADD_VOTE_TIP =
"Add your vote for this song";
protected static final String CLEAR_VOTE_TIP =
"Remove your vote for this song";
protected static ImageIcon _skipToIcon =
ButtonUtil.getIcon(ICON_ROOT + "skip.png");
protected static ImageIcon _removeIcon =
ButtonUtil.getIcon(ICON_ROOT + "remove_song.png");
protected static ImageIcon _addVoteIcon =
ButtonUtil.getIcon(ICON_ROOT + "add_vote.png");
protected static ImageIcon _clearVoteIcon =
ButtonUtil.getIcon(ICON_ROOT + "clear_vote.png");
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 797 B

@@ -0,0 +1,33 @@
#
# $Id: config.properties,v 1.1 2003/05/04 18:16:06 mdb Exp $
#
# Default RoboDJ configuration properties
#
# The directory in which RoboDJ will store its ripped music
# repository.basedir = /export/robodj/repository
#
# The directory in which the importer will store temp files while ripping
# repository.tmpdir = /export/robodj/tmp
#
# The JDBC configuration for accessing the music database
jdbc.default.driver = org.gjt.mm.mysql.Driver
jdbc.default.username = _USERNAME_
jdbc.default.password = _PASSWORD_
jdbc.default.url = jdbc:mysql://_DBHOST_:3306/_DBNAME_
#
# The CDDB host to which to connect
cddb.host = freedb.freedb.org
#
# The music server host and port information
# musicd.host = hostname
musicd.port = 2500
@@ -1,5 +1,5 @@
//
// $Id: CDDBLookupPanel.java,v 1.6 2002/11/11 17:05:20 mdb Exp $
// $Id: CDDBLookupPanel.java,v 1.7 2003/05/04 18:16:07 mdb Exp $
package robodj.importer;
@@ -171,6 +171,7 @@ public class CDDBLookupPanel
Song song = (entry.songs[i] = new Song());
song.title = names[i];
song.position = i+1;
song.votes = "";
}
// create the rip panel and pass the entry and info along
@@ -1,5 +1,5 @@
//
// $Id: RipPanel.java,v 1.8 2002/11/11 17:05:37 mdb Exp $
// $Id: RipPanel.java,v 1.9 2003/05/04 18:16:07 mdb Exp $
package robodj.importer;
@@ -430,8 +430,8 @@ public class RipPanel
protected Entry _entry;
protected static final String DB_FAILURE_MSG =
"An error occurred while communicating with the database. You " +
"may wish to examine the following error message, remedy the " +
"problem with the database and retry the operation. Or you can " +
"An error occurred while communicating with the database.\n" +
"You may wish to examine the following error message, remedy the\n" +
"problem with the database and retry the operation. Or you can\n" +
"abort the operation and cancel the import process entirely.";
}
@@ -1,8 +1,10 @@
//
// $Id: Song.java,v 1.1 2000/11/08 06:42:57 mdb Exp $
// $Id: Song.java,v 1.2 2003/05/04 18:16:07 mdb Exp $
package robodj.repository;
import com.samskivert.util.StringUtil;
/**
* A song maps approximately to an individual piece of music. In most
* cases repository entries are created from albums which contain some
@@ -38,10 +40,103 @@ public class Song
*/
public int duration;
/** A comma separated list of the "users" that have voted for this
* song where "users" is the short string (initials) provided by a
* client when voting for a song. */
public String votes;
/**
* Adds a voter to the votes string for this entry, not including a
* voter that already exists in the voter set.
*
* @return true if the voter set was changed as a result of this
* addition, false if not.
*/
public boolean addVote (String voter)
{
// handle our first vote separately
if (StringUtil.blank(votes)) {
votes = voter;
_votes = null; // clear our cached votes
return true;
}
// make sure this voter has not already cast their ballot
if (hasVoted(voter)) {
return false;
}
votes = (votes + "," + voter);
_votes = null; // clear our cached votes
return true;
}
/**
* Removes a voter from the votes string for this song.
*
* @return true if the voter set was changed as a result of this
* addition, false if not.
*/
public boolean clearVote (String voter)
{
String[] votes = getVotes();
StringBuffer nvotes = new StringBuffer();
boolean saw = false;
for (int ii = 0; ii < votes.length; ii++) {
if (votes[ii].equalsIgnoreCase(voter)) {
saw = true;
} else {
if (nvotes.length() > 0) {
nvotes.append(",");
}
nvotes.append(votes[ii]);
}
}
if (saw) {
this.votes = nvotes.toString();
_votes = null;
}
return saw;
}
/**
* Returns the {@link #votes} field broken up into an array of strings
* with one for each "vote".
*/
public String[] getVotes ()
{
if (_votes == null) {
if (StringUtil.blank(votes)) {
_votes = new String[0];
} else {
_votes = StringUtil.split(votes, ",");
}
}
return _votes;
}
/**
* Returns true if the specified user has voted.
*/
public boolean hasVoted (String voter)
{
String[] votes = getVotes();
for (int ii = 0; ii < votes.length; ii++) {
if (votes[ii].equalsIgnoreCase(voter)) {
return true;
}
}
return false;
}
/**
* Returns a string representation of this instance.
*/
public String toString ()
{
return "[songid=" + songid + ", entryid=" + entryid +
", position=" + position + ", title=" + title +
", location=" + location + ", duration=" + duration + "]";
return StringUtil.fieldsToString(this);
}
/** The decoded voter array for this song. */
protected transient String[] _votes;
}
@@ -1,10 +1,9 @@
//
// $Id: ButtonUtil.java,v 1.1 2002/03/03 20:56:12 mdb Exp $
// $Id: ButtonUtil.java,v 1.2 2003/05/04 18:16:07 mdb Exp $
package robodj.util;
import java.awt.Image;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.io.InputStream;
@@ -15,6 +14,8 @@ import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import com.samskivert.swing.Controller;
import robodj.Log;
/**
@@ -43,32 +44,30 @@ public class ButtonUtil
}
public static JButton createControlButton (
String tooltip, String action, String iconPath, ActionListener al)
String tooltip, String action, String iconPath)
{
return createControlButton(tooltip, action, iconPath, al, false);
return createControlButton(tooltip, action, iconPath, false);
}
public static JButton createControlButton (
String tooltip, String action, String iconPath,
ActionListener al, boolean borderless)
String tooltip, String action, String iconPath, boolean borderless)
{
return createControlButton(
tooltip, action, getIcon(iconPath), al, borderless);
tooltip, action, getIcon(iconPath), borderless);
}
public static JButton createControlButton (
String tooltip, String action, ImageIcon icon, ActionListener al)
String tooltip, String action, ImageIcon icon)
{
return createControlButton(tooltip, action, icon, al, false);
return createControlButton(tooltip, action, icon, false);
}
public static JButton createControlButton (
String tooltip, String action, ImageIcon icon,
ActionListener al, boolean borderless)
String tooltip, String action, ImageIcon icon, boolean borderless)
{
JButton cbut = new JButton(icon);
cbut.setActionCommand(action);
cbut.addActionListener(al);
cbut.addActionListener(Controller.DISPATCHER);
cbut.setToolTipText(tooltip);
// clear out that annoying fat border that swing uses
cbut.setBorder(borderless ? BorderFactory.createEmptyBorder() :
@@ -0,0 +1,93 @@
//
// $Id: RDJPrefs.java,v 1.1 2003/05/04 18:16:07 mdb Exp $
package robodj.util;
import java.util.Properties;
import com.samskivert.util.Config;
/**
* Contains settings and preferences for this RoboDJ installation.
*/
public class RDJPrefs
{
/** Configuration key for {@link #getRepositoryDirectory}. */
public static final String REPO_DIR_KEY = "repository.basedir";
/** Configuration key for {@link #getJDBCConfig}. */
public static final String JDBC_DRIVER_KEY = "jdbc.default.driver";
/** Configuration key for {@link #getJDBCConfig}. */
public static final String JDBC_USERNAME_KEY = "jdbc.default.username";
/** Configuration key for {@link #getJDBCConfig}. */
public static final String JDBC_PASSWORD_KEY = "jdbc.default.password";
/** Configuration key for {@link #getJDBCConfig}. */
public static final String JDBC_URL_KEY = "jdbc.default.url";
/** Configuration key for {@link #getMusicDaemonHost}. */
public static final String MUSICD_HOST_KEY = "musicd.host";
/** Configuration key for {@link #getMusicDaemonPort}. */
public static final String MUSICD_PORT_KEY = "musicd.port";
/** Configuration key for {@link #getCDDBHost}. */
public static final String CDDB_HOST_KEY = "cddb.host";
/** Configuration key for {@link #getUser}. */
public static final String USER_KEY = "user";
/** Provides access to configuration data. */
public static Config config = new Config("robodj/config");
/**
* Returns the repository directory name.
*/
public static String getRepositoryDirectory ()
{
return config.getValue(REPO_DIR_KEY, "");
}
/**
* Returns the JDBC configuration for this RoboDJ installation.
*/
public static Properties getJDBCConfig ()
{
return config.getSubProperties("jdbc");
}
/**
* Returns the host by which we connect to the music daemon.
*/
public static String getMusicDaemonHost ()
{
return config.getValue(MUSICD_HOST_KEY, "");
}
/**
* Returns the port by which we connect to the music daemon.
*/
public static int getMusicDaemonPort ()
{
return config.getValue(MUSICD_PORT_KEY, 2500);
}
/**
* Returns the hostname of the CDDB server we use to look up album and
* track names.
*/
public static String getCDDBHost ()
{
return config.getValue(CDDB_HOST_KEY, "");
}
/**
* Returns our locally configured username.
*/
public static String getUser ()
{
return config.getValue(USER_KEY, "");
}
}
@@ -0,0 +1,187 @@
//
// $Id: RDJPrefsPanel.java,v 1.1 2003/05/04 18:16:07 mdb Exp $
package robodj.util;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import com.samskivert.swing.Spacer;
import com.samskivert.swing.util.SwingUtil;
/**
* Displays an interface for editing our RoboDJ preferences.
*/
public class RDJPrefsPanel extends JPanel
{
/**
* Displays a frame containing a panel for editing our preferences.
*
* @param blockCaller if true the caller will be blocked until the
* user dismisses the preferences panel.
*/
public static void display (boolean blockCaller)
{
JFrame frame = new JFrame("RoboDJ Configuration");
RDJPrefsPanel panel = new RDJPrefsPanel(frame);
frame.setContentPane(panel);
frame.pack();
SwingUtil.centerWindow(frame);
frame.show();
if (blockCaller) {
panel.awaitDismiss();
}
}
protected RDJPrefsPanel (JFrame frame)
{
super(new GridBagLayout());
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
_frame = frame;
_frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
_frame.addWindowListener(new WindowAdapter() {
public void windowClosed (WindowEvent event) {
dismissed();
}
});
GridBagConstraints c = new GridBagConstraints();
c.anchor = GridBagConstraints.WEST;
c.fill = GridBagConstraints.HORIZONTAL;
c.weighty = 1;
c.gridwidth = GridBagConstraints.REMAINDER;
JLabel title = new JLabel("RoboDJ Preferences");
title.setHorizontalAlignment(JLabel.CENTER);
title.setFont(title.getFont().deriveFont(16f));
add(title, c);
add(new Spacer(10, 10), c);
for (int ii = 0; ii < PREFS.length; ii += 2) {
if (PREFS[ii].equals("")) {
c.gridwidth = GridBagConstraints.REMAINDER;
add(new Spacer(10, 10), c);
} else {
JLabel label = new JLabel(PREFS[ii]);
c.gridwidth = GridBagConstraints.RELATIVE;
c.ipadx = 5;
c.ipady = 8;
c.weightx = 1;
add(label, c);
JTextField value = new JTextField(
RDJPrefs.config.getValue(PREFS[ii+1], "")) {
public Dimension getPreferredSize () {
Dimension d = super.getPreferredSize();
d.width = Math.min(250, d.width);
return d;
}
};
PrefListener plist = new PrefListener(value, PREFS[ii+1]);
value.addFocusListener(plist);
value.addActionListener(plist);
c.ipadx = 0;
c.ipady = 2;
c.weightx = 2;
c.gridwidth = GridBagConstraints.REMAINDER;
add(value, c);
}
}
// add a dismiss button
JButton dismiss = new JButton("Dismiss");
dismiss.addActionListener(new ActionListener() {
public void actionPerformed (ActionEvent event) {
_frame.dispose();
}
});
c.fill = GridBagConstraints.NONE;
c.anchor = GridBagConstraints.EAST;
c.ipadx = c.ipady = 0;
add(new Spacer(10, 10), c);
add(dismiss, c);
}
protected synchronized void awaitDismiss ()
{
while (_frame.isShowing()) {
try {
wait();
} catch (InterruptedException ie) {
}
}
}
protected synchronized void dismissed ()
{
notify();
}
protected static class PrefListener
implements ActionListener, FocusListener
{
public PrefListener (JTextField text, String prefKey)
{
_text = text;
_prefKey = prefKey;
}
// documentation inherited from interface FocusListener
public void focusGained (FocusEvent e)
{
// nothing
}
// documentation inherited from interface FocusListener
public void focusLost (FocusEvent e)
{
actionPerformed(new ActionEvent(_text, 0, "focusLost"));
}
// documentation inherited from interface ActionListener
public void actionPerformed (ActionEvent e)
{
RDJPrefs.config.setValue(_prefKey, _text.getText());
System.out.println("Updated " + _prefKey + " -> " +
_text.getText());
}
protected JTextField _text;
protected String _prefKey;
}
protected JFrame _frame;
/** Defines our configurable preferences. */
protected static final String[] PREFS = {
"Music repository directory", RDJPrefs.REPO_DIR_KEY,
"", "",
"Music database JDBC driver", RDJPrefs.JDBC_DRIVER_KEY,
"Music database JDBC url", RDJPrefs.JDBC_URL_KEY,
"Music database JDBC username", RDJPrefs.JDBC_USERNAME_KEY,
"Music database JDBC password", RDJPrefs.JDBC_PASSWORD_KEY,
"", "",
"Music server hostname", RDJPrefs.MUSICD_HOST_KEY,
"Music server port", RDJPrefs.MUSICD_PORT_KEY,
"", "",
"CDDB server hostname", RDJPrefs.CDDB_HOST_KEY,
};
}
@@ -1,5 +1,5 @@
/**
* $Id: create_repository.mysql,v 1.1 2000/12/10 07:00:47 mdb Exp $
* $Id: create_repository.mysql,v 1.2 2003/05/04 18:16:07 mdb Exp $
*
* Creates the necessary database tables in MySQL for the music repository.
*/
@@ -27,6 +27,7 @@ CREATE TABLE songs (
title VARCHAR(200) NOT NULL,
location VARCHAR(200) NOT NULL,
duration INTEGER(10) NOT NULL,
votes VARCHAR(255) NOT NULL,
CONSTRAINT fk_songs_entries FOREIGN KEY (entryid)
REFERENCES entries (entryid) ON DELETE CASCADE