From a8fdad6afb9a0fe27d75c0a18f86d93f03079415 Mon Sep 17 00:00:00 2001 From: mdb Date: Sun, 4 May 2003 18:16:07 +0000 Subject: [PATCH] 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 --- .../src/java/robodj/chooser/BrowsePanel.java | 3 +- .../robodj/chooser/CategoryEntryList.java | 16 +- .../src/java/robodj/chooser/Chooser.java | 123 ++++---- .../robodj/chooser/ChooserController.java | 76 +++++ .../src/java/robodj/chooser/ChooserFrame.java | 116 ++++--- .../java/robodj/chooser/EntryController.java | 172 ++++++++++ .../src/java/robodj/chooser/EntryItem.java | 72 +++++ .../src/java/robodj/chooser/EntryList.java | 223 +------------ .../robodj/src/java/robodj/chooser/Item.java | 39 +++ .../java/robodj/chooser/ItemController.java | 96 ++++++ .../robodj/chooser/PlaylistController.java | 222 +++++++++++++ .../java/robodj/chooser/PlaylistPanel.java | 298 +++--------------- .../java/robodj/chooser/QueryEntryList.java | 25 +- .../src/java/robodj/chooser/SongItem.java | 166 ++++++++++ .../java/robodj/chooser/images/add_vote.png | Bin 0 -> 792 bytes .../java/robodj/chooser/images/clear_vote.png | Bin 0 -> 797 bytes .../robodj/src/java/robodj/config.properties | 33 ++ .../java/robodj/importer/CDDBLookupPanel.java | 3 +- .../src/java/robodj/importer/RipPanel.java | 8 +- .../src/java/robodj/repository/Song.java | 103 +++++- .../src/java/robodj/util/ButtonUtil.java | 23 +- .../robodj/src/java/robodj/util/RDJPrefs.java | 93 ++++++ .../src/java/robodj/util/RDJPrefsPanel.java | 187 +++++++++++ .../robodj/src/sql/create_repository.mysql | 3 +- 24 files changed, 1496 insertions(+), 604 deletions(-) create mode 100644 projects/robodj/src/java/robodj/chooser/ChooserController.java create mode 100644 projects/robodj/src/java/robodj/chooser/EntryController.java create mode 100644 projects/robodj/src/java/robodj/chooser/EntryItem.java create mode 100644 projects/robodj/src/java/robodj/chooser/Item.java create mode 100644 projects/robodj/src/java/robodj/chooser/ItemController.java create mode 100644 projects/robodj/src/java/robodj/chooser/PlaylistController.java create mode 100644 projects/robodj/src/java/robodj/chooser/SongItem.java create mode 100644 projects/robodj/src/java/robodj/chooser/images/add_vote.png create mode 100644 projects/robodj/src/java/robodj/chooser/images/clear_vote.png create mode 100644 projects/robodj/src/java/robodj/config.properties create mode 100644 projects/robodj/src/java/robodj/util/RDJPrefs.java create mode 100644 projects/robodj/src/java/robodj/util/RDJPrefsPanel.java diff --git a/projects/robodj/src/java/robodj/chooser/BrowsePanel.java b/projects/robodj/src/java/robodj/chooser/BrowsePanel.java index 60cbdc60..e130cc64 100644 --- a/projects/robodj/src/java/robodj/chooser/BrowsePanel.java +++ b/projects/robodj/src/java/robodj/chooser/BrowsePanel.java @@ -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 diff --git a/projects/robodj/src/java/robodj/chooser/CategoryEntryList.java b/projects/robodj/src/java/robodj/chooser/CategoryEntryList.java index f008a806..8333549e 100644 --- a/projects/robodj/src/java/robodj/chooser/CategoryEntryList.java +++ b/projects/robodj/src/java/robodj/chooser/CategoryEntryList.java @@ -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 () diff --git a/projects/robodj/src/java/robodj/chooser/Chooser.java b/projects/robodj/src/java/robodj/chooser/Chooser.java index a53f1c5a..1e08f340 100644 --- a/projects/robodj/src/java/robodj/chooser/Chooser.java +++ b/projects/robodj/src/java/robodj/chooser/Chooser.java @@ -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); } } diff --git a/projects/robodj/src/java/robodj/chooser/ChooserController.java b/projects/robodj/src/java/robodj/chooser/ChooserController.java new file mode 100644 index 00000000..bc9a9171 --- /dev/null +++ b/projects/robodj/src/java/robodj/chooser/ChooserController.java @@ -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); + } +} diff --git a/projects/robodj/src/java/robodj/chooser/ChooserFrame.java b/projects/robodj/src/java/robodj/chooser/ChooserFrame.java index 5f6a539b..bcc343a6 100644 --- a/projects/robodj/src/java/robodj/chooser/ChooserFrame.java +++ b/projects/robodj/src/java/robodj/chooser/ChooserFrame.java @@ -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; diff --git a/projects/robodj/src/java/robodj/chooser/EntryController.java b/projects/robodj/src/java/robodj/chooser/EntryController.java new file mode 100644 index 00000000..210b8fea --- /dev/null +++ b/projects/robodj/src/java/robodj/chooser/EntryController.java @@ -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; +} diff --git a/projects/robodj/src/java/robodj/chooser/EntryItem.java b/projects/robodj/src/java/robodj/chooser/EntryItem.java new file mode 100644 index 00000000..c92395d4 --- /dev/null +++ b/projects/robodj/src/java/robodj/chooser/EntryItem.java @@ -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"); + } diff --git a/projects/robodj/src/java/robodj/chooser/EntryList.java b/projects/robodj/src/java/robodj/chooser/EntryList.java index f2b10f70..46efb45c 100644 --- a/projects/robodj/src/java/robodj/chooser/EntryList.java +++ b/projects/robodj/src/java/robodj/chooser/EntryList.java @@ -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"; } diff --git a/projects/robodj/src/java/robodj/chooser/Item.java b/projects/robodj/src/java/robodj/chooser/Item.java new file mode 100644 index 00000000..a8569aed --- /dev/null +++ b/projects/robodj/src/java/robodj/chooser/Item.java @@ -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"); +} diff --git a/projects/robodj/src/java/robodj/chooser/ItemController.java b/projects/robodj/src/java/robodj/chooser/ItemController.java new file mode 100644 index 00000000..6913a580 --- /dev/null +++ b/projects/robodj/src/java/robodj/chooser/ItemController.java @@ -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; +} diff --git a/projects/robodj/src/java/robodj/chooser/PlaylistController.java b/projects/robodj/src/java/robodj/chooser/PlaylistController.java new file mode 100644 index 00000000..be7eda6d --- /dev/null +++ b/projects/robodj/src/java/robodj/chooser/PlaylistController.java @@ -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; +} diff --git a/projects/robodj/src/java/robodj/chooser/PlaylistPanel.java b/projects/robodj/src/java/robodj/chooser/PlaylistPanel.java index 6333469b..360cc2a0 100644 --- a/projects/robodj/src/java/robodj/chooser/PlaylistPanel.java +++ b/projects/robodj/src/java/robodj/chooser/PlaylistPanel.java @@ -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; diff --git a/projects/robodj/src/java/robodj/chooser/QueryEntryList.java b/projects/robodj/src/java/robodj/chooser/QueryEntryList.java index 1c939bad..d0573f62 100644 --- a/projects/robodj/src/java/robodj/chooser/QueryEntryList.java +++ b/projects/robodj/src/java/robodj/chooser/QueryEntryList.java @@ -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; } diff --git a/projects/robodj/src/java/robodj/chooser/SongItem.java b/projects/robodj/src/java/robodj/chooser/SongItem.java new file mode 100644 index 00000000..f3d2f619 --- /dev/null +++ b/projects/robodj/src/java/robodj/chooser/SongItem.java @@ -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("" + _song.title + + "
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"); +} diff --git a/projects/robodj/src/java/robodj/chooser/images/add_vote.png b/projects/robodj/src/java/robodj/chooser/images/add_vote.png new file mode 100644 index 0000000000000000000000000000000000000000..1f6d2b50f490fd2219c6b6f7106c6a254a902250 GIT binary patch literal 792 zcmV+z1LypSP)*A5}{g1!Zcfi&7tG3k|_Vg)WNN)`bul+n^w5!Kr zF_Fz?g|6$OQjk2r;NV~`$y#0+KrVK3%d$i~9v9JQv@8(MY9CG0T;Z8a2E#CDX=y>% z^)>fID&FLDI*s4&$F^-u(%c)YsQDGc!XZ5^=i) zq*W!Fot;HhRdR9_R+x)}!5{$V+B>P;8N|1_hVRKAe46}BmwwriyCj30MC0S*EH5u( znkE2>q5yF1`~$Xy6gF*=dH&)Vhr>tlR#npx=;q?B_O&I-#f^=P4%t*yf@2#&57fuIk5L23uczow}sMsP)e>s zps!z2)c9q7Wi33O4S((#iKRu0rFn~tIbYUvay>zkBq4<0(y4m{YkheBT$kA;!SbTT z+>d!W?w`!(mMR@i0B4R}McERdTCQRt^OM=+S8nPTDjKaTOw+V=ZbL}Zw0}$dxAq%) WIo6s39^&T!0000}5W(X)#$exe;N+g;cvJTG%3x zA_yUhphX0IKu`n~RJ6}k@Qi8^LIes91CfvmiPVa4=w(xyIn%Q*U-#Y?F~dB4>M)C- z0|(CgxaWWV|9kE`5B_)4=3T22)+}kh!sJp)yj%!DDwQIUNEBOyrzt6=%4V~ko=hf{ z<2cGRO;svL9-z0kHFH@YIy&g<>tk?mkaRkYWmyai3@qARewRi{ zp_IFzwzf6^8XFt)?=&|zGd4EH%a<=085t?MrDySjsbv_(iOM2x4RM+vh-rSEWrH5rfCw1L;#qWm;fLejWRwyPG@JQ*H=JW zQlhD;DRf;YCs$#Gxi}mS191C9i0#oZ8}uOWCNexqeB#!_tSk4T3~~|;4-YdpH-~N8 z00e_U0G?c}C$hPo6>HX`ghRu|09|_>c5WB!y*RtPM7cN?i@9Xeb)9QnwL~`8v#K`A zod<6?fAcA)4@Wt=CrqUNCua}*;_^L50h$Y7XlN*3X^ufpPY;!ql~h$#5eNhTXlV-L zU$KU=@|FBjfA4+y`WwyX$5*k6_%=V_Ed99_LNGZwxqww&T^+B?CWcZ04xV|oplfa% z3)8;!J59&G0cbvtbD2dK6&KX00000NkvXXu0mjfwWV9~ literal 0 HcmV?d00001 diff --git a/projects/robodj/src/java/robodj/config.properties b/projects/robodj/src/java/robodj/config.properties new file mode 100644 index 00000000..ae61cfbf --- /dev/null +++ b/projects/robodj/src/java/robodj/config.properties @@ -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 diff --git a/projects/robodj/src/java/robodj/importer/CDDBLookupPanel.java b/projects/robodj/src/java/robodj/importer/CDDBLookupPanel.java index 8e25688f..6395356c 100644 --- a/projects/robodj/src/java/robodj/importer/CDDBLookupPanel.java +++ b/projects/robodj/src/java/robodj/importer/CDDBLookupPanel.java @@ -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 diff --git a/projects/robodj/src/java/robodj/importer/RipPanel.java b/projects/robodj/src/java/robodj/importer/RipPanel.java index bc108778..b1b328b9 100644 --- a/projects/robodj/src/java/robodj/importer/RipPanel.java +++ b/projects/robodj/src/java/robodj/importer/RipPanel.java @@ -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."; } diff --git a/projects/robodj/src/java/robodj/repository/Song.java b/projects/robodj/src/java/robodj/repository/Song.java index 8a938f71..62e63884 100644 --- a/projects/robodj/src/java/robodj/repository/Song.java +++ b/projects/robodj/src/java/robodj/repository/Song.java @@ -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; } diff --git a/projects/robodj/src/java/robodj/util/ButtonUtil.java b/projects/robodj/src/java/robodj/util/ButtonUtil.java index 563c2a4f..be5bfaf1 100644 --- a/projects/robodj/src/java/robodj/util/ButtonUtil.java +++ b/projects/robodj/src/java/robodj/util/ButtonUtil.java @@ -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() : diff --git a/projects/robodj/src/java/robodj/util/RDJPrefs.java b/projects/robodj/src/java/robodj/util/RDJPrefs.java new file mode 100644 index 00000000..7d49b79f --- /dev/null +++ b/projects/robodj/src/java/robodj/util/RDJPrefs.java @@ -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, ""); + } +} diff --git a/projects/robodj/src/java/robodj/util/RDJPrefsPanel.java b/projects/robodj/src/java/robodj/util/RDJPrefsPanel.java new file mode 100644 index 00000000..767e587e --- /dev/null +++ b/projects/robodj/src/java/robodj/util/RDJPrefsPanel.java @@ -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, + }; +} diff --git a/projects/robodj/src/sql/create_repository.mysql b/projects/robodj/src/sql/create_repository.mysql index f20ebdd4..1933c219 100644 --- a/projects/robodj/src/sql/create_repository.mysql +++ b/projects/robodj/src/sql/create_repository.mysql @@ -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