From 37404be66bd43ec0a8d4f7f4c97363e839dcbdde Mon Sep 17 00:00:00 2001 From: mdb Date: Sun, 10 Dec 2000 07:02:09 +0000 Subject: [PATCH] Initial revision of the music importer UI. Not complete. git-svn-id: https://samskivert.googlecode.com/svn/trunk@33 6335cc39-0255-0410-8fd6-9bcaacd3b74c --- .../java/robodj/importer/CDDBLookupPanel.java | 186 ++++++++++++++++++ .../src/java/robodj/importer/CDDBUtil.java | 64 ++++++ .../java/robodj/importer/CDInfoEditor.java | 121 ++++++++++++ .../robodj/importer/DetailTableModel.java | 172 ++++++++++++++++ .../src/java/robodj/importer/Importer.java | 23 +++ .../java/robodj/importer/ImporterFrame.java | 86 ++++++++ .../java/robodj/importer/ImporterPanel.java | 25 +++ .../java/robodj/importer/InsertCDPanel.java | 61 ++++++ .../robodj/src/java/robodj/importer/Makefile | 17 ++ .../src/java/robodj/importer/Panel.java | 8 + .../src/java/robodj/importer/RipPanel.java | 86 ++++++++ .../robodj/src/java/robodj/importer/cd.jpg | Bin 0 -> 13513 bytes 12 files changed, 849 insertions(+) create mode 100644 projects/robodj/src/java/robodj/importer/CDDBLookupPanel.java create mode 100644 projects/robodj/src/java/robodj/importer/CDDBUtil.java create mode 100644 projects/robodj/src/java/robodj/importer/CDInfoEditor.java create mode 100644 projects/robodj/src/java/robodj/importer/DetailTableModel.java create mode 100644 projects/robodj/src/java/robodj/importer/Importer.java create mode 100644 projects/robodj/src/java/robodj/importer/ImporterFrame.java create mode 100644 projects/robodj/src/java/robodj/importer/ImporterPanel.java create mode 100644 projects/robodj/src/java/robodj/importer/InsertCDPanel.java create mode 100644 projects/robodj/src/java/robodj/importer/Makefile create mode 100644 projects/robodj/src/java/robodj/importer/Panel.java create mode 100644 projects/robodj/src/java/robodj/importer/RipPanel.java create mode 100644 projects/robodj/src/java/robodj/importer/cd.jpg diff --git a/projects/robodj/src/java/robodj/importer/CDDBLookupPanel.java b/projects/robodj/src/java/robodj/importer/CDDBLookupPanel.java new file mode 100644 index 00000000..7b148519 --- /dev/null +++ b/projects/robodj/src/java/robodj/importer/CDDBLookupPanel.java @@ -0,0 +1,186 @@ +// +// $Id: CDDBLookupPanel.java,v 1.1 2000/12/10 07:02:09 mdb Exp $ + +package robodj.importer; + +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.net.URL; +import java.util.ArrayList; + +import javax.swing.*; + +import com.samskivert.net.cddb.CDDB; +import com.samskivert.swing.util.*; +import com.samskivert.swing.*; +import com.samskivert.util.StringUtil; + +import robodj.convert.*; +import robodj.repository.*; + +public class CDDBLookupPanel + extends ImporterPanel + implements ActionListener, TaskObserver +{ + public CDDBLookupPanel () + { + GroupLayout gl = new HGroupLayout(GroupLayout.STRETCH); + gl.setOffAxisPolicy(GroupLayout.STRETCH); + setLayout(gl); + + // create our label for the left hand side + JLabel cdlabel = new JLabel("CDDB Lookup", JLabel.CENTER); + cdlabel.setFont(new Font("Helvetica", Font.BOLD, 24)); + add(cdlabel, GroupLayout.FIXED); + + // create the "insert cd" text label for the right hand side + _infoText = new JTextArea("Reading CD information..."); + _infoText.setLineWrap(true); + _infoText.setEditable(false); + add(_infoText); + } + + public void wasAddedToFrame (ImporterFrame frame) + { + // keep this for later + _frame = frame; + + frame.addControlButton("Cancel", "cancel", this); + _next = frame.addControlButton("Next...", "next", this); + _next.setEnabled(false); + + // create our info task and set it a running + Task infoTask = new Task() + { + public Object invoke () + throws Exception + { + Ripper ripper = new CDParanoiaRipper(); + return ripper.getTrackInfo(); + } + + public boolean abort () + { + return false; + } + }; + TaskMaster.invokeTask("getinfo", infoTask, this); + } + + public void taskCompleted (String name, Object result) + { + if (name.equals("getinfo")) { + _info = (Ripper.TrackInfo[])result; + final Ripper.TrackInfo[] info = _info; + _infoText.append("\nRead info for " + _info.length + + " tracks.\nDoing CDDB lookup..."); + + // create our cddb lookup task and start it up + Task infoTask = new Task() + { + public Object invoke () + throws Exception + { + return CDDBUtil.doCDDBLookup("us.cddb.com", info); + } + + public boolean abort () + { + return false; + } + }; + TaskMaster.invokeTask("cddb_lookup", infoTask, this); + + } else if (name.equals("cddb_lookup")) { + // get our hands on the CDDB details + CDDB.Detail[] details = (CDDB.Detail[])result; + + // replace the info text with the CD info editor + remove(_infoText); + _infoEditor = new CDInfoEditor(details, _info.length); + add(_infoEditor); + + // enable the next button + _next.setEnabled(true); + + // relay everything out + validate(); + } + } + + public void taskFailed (String name, Throwable exception) + { + if (name.equals("getinfo")) { + _infoText.append("\n\nUnable to read CD info:\n\n" + + exception.getMessage()); + + } else if (name.equals("cddb_lookup")) { + _infoText.append("\n\nCDDB lookup failed: " + + exception.getMessage()); + } + } + + public void actionPerformed (ActionEvent e) + { + String cmd = e.getActionCommand(); + if (cmd.equals("cancel")) { + System.exit(0); + + } else if (cmd.equals("next")) { + extractEntryAndContinue(); + + } else { + System.out.println("Unknown action event: " + cmd); + } + } + + protected void extractEntryAndContinue () + { + Entry entry = new Entry(); + entry.title = _infoEditor.getTitle(); + if (!validate(entry.title, "Title cannot be blank.")) { + return; + } + + entry.artist = _infoEditor.getArtist(); + if (!validate(entry.artist, "Artist cannot be blank.")) { + return; + } + + String[] names = _infoEditor.getTrackNames(); + entry.songs = new Song[names.length]; + for (int i = 0; i < names.length; i++) { + if (!validate(names[i], "Track " + (i+1) + + " cannot be blank.")) { + return; + } + Song song = (entry.songs[i] = new Song()); + song.title = names[i]; + song.position = i+1; + } + + // create the rip panel and pass the entry and info along + RipPanel panel = new RipPanel(_info, entry); + _frame.setPanel(panel); + } + + protected boolean validate (String value, String errmsg) + { + if (StringUtil.blank(value)) { + JOptionPane.showMessageDialog(SwingUtilities.getRootPane(this), + errmsg, "Invalid data provided", + JOptionPane.WARNING_MESSAGE); + return false; + } + + return true; + } + + protected ImporterFrame _frame; + protected JButton _next; + protected JTextArea _infoText; + protected CDInfoEditor _infoEditor; + + protected Ripper.TrackInfo[] _info; +} diff --git a/projects/robodj/src/java/robodj/importer/CDDBUtil.java b/projects/robodj/src/java/robodj/importer/CDDBUtil.java new file mode 100644 index 00000000..4f61ec67 --- /dev/null +++ b/projects/robodj/src/java/robodj/importer/CDDBUtil.java @@ -0,0 +1,64 @@ +// +// $Id: CDDBUtil.java,v 1.1 2000/12/10 07:02:09 mdb Exp $ + +package robodj.importer; + +import com.samskivert.net.cddb.*; +import robodj.convert.Ripper; +import robodj.convert.RipUtil; + +public class CDDBUtil +{ + /** + * Does a CDDB lookup for the supplied CD information and returns an + * array of disc info objects containing the name information returned + * from CDDB. + * + * @return An array of detail objects, one per match or a zero length + * array if no matches were found. + */ + public static CDDB.Detail[] doCDDBLookup (String hostname, + Ripper.TrackInfo[] info) + throws Exception + { + CDDB cddb = new CDDB(); + + long discid = RipUtil.computeDiscId(info); + String cdid = Long.toString(discid, 0x10); + + // create an array with the track offsets + int[] offsets = new int[info.length]; + for (int i = 0; i < info.length; i++) { + offsets[i] = info[i].offset; + } + + int length = RipUtil.computeDiscLength(info); + + try { + String rsp = cddb.connect(hostname); + + // set the timeout to 30 seconds + cddb.setTimeout(30*1000); + + // try a test query + CDDB.Entry[] entries = cddb.query(cdid, offsets, length); + CDDB.Detail[] details; + + if (entries == null || entries.length == 0) { + details = new CDDB.Detail[0]; + + } else { + details = new CDDB.Detail[entries.length]; + for (int i = 0; i < entries.length; i++) { + details[i] = cddb.read(entries[i].category, + entries[i].cdid); + } + } + + return details; + + } finally { + cddb.close(); + } + } +} diff --git a/projects/robodj/src/java/robodj/importer/CDInfoEditor.java b/projects/robodj/src/java/robodj/importer/CDInfoEditor.java new file mode 100644 index 00000000..2989d77f --- /dev/null +++ b/projects/robodj/src/java/robodj/importer/CDInfoEditor.java @@ -0,0 +1,121 @@ +// +// $Id: CDInfoEditor.java,v 1.1 2000/12/10 07:02:09 mdb Exp $ + +package robodj.importer; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.table.*; + +import com.samskivert.swing.*; +import com.samskivert.net.cddb.CDDB; + +public class CDInfoEditor + extends JPanel +{ + public CDInfoEditor (CDDB.Detail[] details, int numTracks) + { + GroupLayout gl = new VGroupLayout(GroupLayout.STRETCH); + gl.setOffAxisPolicy(GroupLayout.STRETCH); + setLayout(gl); + + // create the selection components + JPanel selPanel = new JPanel(new HGroupLayout(GroupLayout.STRETCH)); + JLabel selLabel = new JLabel("Select..."); + + // create a string array for our combo box + String[] names = new String[details.length]; + for (int i = 0; i < details.length; i++) { + names[i] = details[i].title; + } + + _selBox = new JComboBox(names); + selPanel.add(selLabel, GroupLayout.FIXED); + selPanel.add(_selBox); + + _selBox.addActionListener(new ActionListener () + { + public void actionPerformed (ActionEvent e) + { + JComboBox cb = (JComboBox)e.getSource(); + selectDetail(cb.getSelectedIndex()); + } + }); + + add(selPanel, GroupLayout.FIXED); + + // create the table model because we need it to listen to the + // title and artist text fields + _tmodel = new DetailTableModel(details, numTracks); + + // create the title editing components + JPanel titPanel = new JPanel(new HGroupLayout(GroupLayout.STRETCH)); + JLabel titLabel = new JLabel("Title"); + _titText = new JTextField(); + _titText.getDocument().addDocumentListener(_tmodel); + _titText.getDocument().putProperty("name", "title"); + titPanel.add(titLabel, GroupLayout.FIXED); + titPanel.add(_titText); + add(titPanel, GroupLayout.FIXED); + + // create the artist editing components + JPanel artPanel = new JPanel(new HGroupLayout(GroupLayout.STRETCH)); + JLabel artLabel = new JLabel("Artist"); + _artText = new JTextField(); + _artText.getDocument().addDocumentListener(_tmodel); + _artText.getDocument().putProperty("name", "artist"); + artPanel.add(artLabel, GroupLayout.FIXED); + artPanel.add(_artText); + add(artPanel, GroupLayout.FIXED); + + // create the track table + _trackTable = new JTable(_tmodel); + TableColumn tcol = _trackTable.getColumnModel().getColumn(0); + // i wish this fucking worked + // tcol.sizeWidthToFit(); + // then i wouldn't have do this nasty hack + tcol.setMaxWidth(20); + + // make something for it to scroll around in + JScrollPane tscroll = new JScrollPane(_trackTable); + add(tscroll); + + selectDetail(0); + } + + public String getTitle () + { + return _tmodel.getTitle(); + } + + public String getArtist () + { + return _tmodel.getArtist(); + } + + public String[] getTrackNames () + { + // commit any uncommitted edits the user might have in progress + TableCellEditor editor = _trackTable.getCellEditor(); + if (editor != null) { + editor.stopCellEditing(); + } + return _tmodel.getTrackNames(); + } + + protected void selectDetail (int index) + { + _tmodel.selectDetail(index); + _titText.setText(_tmodel.getTitle()); + _artText.setText(_tmodel.getArtist()); + } + + protected JComboBox _selBox; + protected JTextField _titText; + protected JTextField _artText; + protected JTable _trackTable; + + protected DetailTableModel _tmodel; +} diff --git a/projects/robodj/src/java/robodj/importer/DetailTableModel.java b/projects/robodj/src/java/robodj/importer/DetailTableModel.java new file mode 100644 index 00000000..f851471f --- /dev/null +++ b/projects/robodj/src/java/robodj/importer/DetailTableModel.java @@ -0,0 +1,172 @@ +// +// $Id: DetailTableModel.java,v 1.1 2000/12/10 07:02:09 mdb Exp $ + +package robodj.importer; + +import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.table.AbstractTableModel; +import javax.swing.text.Document; +import javax.swing.text.BadLocationException; + +import com.samskivert.util.Log; +import com.samskivert.net.cddb.CDDB; + +public class DetailTableModel + extends AbstractTableModel + implements DocumentListener +{ + public DetailTableModel (CDDB.Detail[] details, int numTracks) + { + _details = details; + _numTracks = numTracks; + _names = new String[_numTracks]; + + // copy in the names from the first detail record (or empty + // strings if there is no first detail record) + copyInfo(0); + } + + public void selectDetail (int index) + { + // copy the track names from the specified detail record into the + // names array + copyInfo(index); + + // fire a data changed event to let the table know what's up + fireTableDataChanged(); + } + + public String getTitle () + { + return _title; + } + + public String getArtist () + { + return _artist; + } + + public String[] getTrackNames () + { + return _names; + } + + public void insertUpdate (DocumentEvent e) + { + updateText(e.getDocument()); + } + + public void removeUpdate (DocumentEvent e) + { + updateText(e.getDocument()); + } + + public void changedUpdate (DocumentEvent e) + { + updateText(e.getDocument()); + } + + protected void updateText (Document doc) + { + try { + if (doc.getProperty("name").equals("title")) { + _title = doc.getText(0, doc.getLength()); + } else if (doc.getProperty("name").equals("artist")) { + _artist = doc.getText(0, doc.getLength()); + } + + } catch (BadLocationException ble) { + Importer.log.warning("Can't extract text from text field?!"); + Importer.log.logStackTrace(Log.WARNING, ble); + } + } + + protected void copyInfo (int index) + { + if (_details.length > index) { + // extract the artist and title from the combined title + // provided by the detail record + String src = _details[index].title; + int sidx = src.indexOf("/"); + if (sidx != -1) { + _artist = src.substring(0, sidx).trim(); + _title = src.substring(sidx+1).trim(); + } else { + _title = src; + _artist = "Unknown"; + } + + for (int i = 0; i < _names.length; i++) { + _names[i] = _details[index].trackNames[i]; + } + + } else { + _title = ""; + _artist = ""; + + for (int i = 0; i < _names.length; i++) { + _names[i] = ""; + } + } + } + + public int getRowCount () + { + return _numTracks; + } + + public int getColumnCount () + { + return COLUMN_NAMES.length; + } + + public Object getValueAt (int rowIndex, int columnIndex) + { + switch (columnIndex) { + case TITLE_COLUMN: + return _names[rowIndex]; + + default: + case 0: + return new Integer(rowIndex+1); + } + } + + public String getColumnName (int column) + { + return COLUMN_NAMES[column]; + } + + public Class getColumnClass (int column) + { + return COLUMN_CLASSES[column]; + } + + public boolean isCellEditable (int row, int column) + { + return column == TITLE_COLUMN; + } + + public void setValueAt (Object value, int row, int column) + { + if (column == TITLE_COLUMN) { + _names[row] = (String)value; + fireTableCellUpdated(row, column); + } + } + + protected CDDB.Detail[] _details; + protected String _title; + protected String _artist; + + protected String[] _names; + protected int _numTracks; + + protected static final String[] COLUMN_NAMES = { "T#", "Title" }; + protected static final Class[] COLUMN_CLASSES = { + Integer.class, String.class }; + + protected static final int TITLE_COLUMN = 1; +} diff --git a/projects/robodj/src/java/robodj/importer/Importer.java b/projects/robodj/src/java/robodj/importer/Importer.java new file mode 100644 index 00000000..c5917b9a --- /dev/null +++ b/projects/robodj/src/java/robodj/importer/Importer.java @@ -0,0 +1,23 @@ +// +// $Id: Importer.java,v 1.1 2000/12/10 07:02:09 mdb Exp $ + +package robodj.importer; + +import com.samskivert.util.Log; + +/** + * The importer is the GUI-based application for ripping, encoding and + * importing CDs and other music into the RoboDJ system. + */ +public class Importer +{ + public static Log log = new Log("importer"); + + public static void main (String[] args) + { + ImporterFrame frame = new ImporterFrame(); + InsertCDPanel panel = new InsertCDPanel(); + frame.setPanel(panel); + frame.setVisible(true); + } +} diff --git a/projects/robodj/src/java/robodj/importer/ImporterFrame.java b/projects/robodj/src/java/robodj/importer/ImporterFrame.java new file mode 100644 index 00000000..c1477044 --- /dev/null +++ b/projects/robodj/src/java/robodj/importer/ImporterFrame.java @@ -0,0 +1,86 @@ +// +// $Id: ImporterFrame.java,v 1.1 2000/12/10 07:02:09 mdb Exp $ + +package robodj.importer; + +import java.awt.*; +import java.awt.event.ActionListener; +import javax.swing.*; + +import com.samskivert.swing.*; + +public class ImporterFrame extends JFrame +{ + public ImporterFrame () + { + super("RoboDJ CD Importer"); + + _top = new JPanel(); + GroupLayout gl = new VGroupLayout(GroupLayout.STRETCH); + gl.setOffAxisPolicy(GroupLayout.STRETCH); + _top.setLayout(gl); + + // give ourselves a wee bit of a border + _top.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + + // create a container for our control buttons + _buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + + // stick it into the frame + _top.add(_buttonPanel, GroupLayout.FIXED); + + // now add our top-level panel (we'd not use this if we could set + // a border on the content pane returned by the frame... alas) + getContentPane().add(_top, BorderLayout.CENTER); + } + + public void setPanel (ImporterPanel panel) + { + boolean repack = true; + + // clear out any old panel + if (_panel != null) { + _top.remove(_panel); + // don't repack if there was an old panel + repack = false; + } + + // clear out old control buttons + clearControlButtons(); + + // then stick the new panel in there + if (panel != null) { + _panel = panel; + _panel.setBackground(Color.yellow); + _top.add(_panel, 0); + // let the panel know that it was added + _panel.wasAddedToFrame(this); + } + + // and possibly repack ourselves + if (repack) { + pack(); + } else { + validate(); + } + } + + public JButton addControlButton (String label, String command, + ActionListener target) + { + JButton abutton = new JButton(label); + abutton.setActionCommand(command); + abutton.addActionListener(target); + _buttonPanel.add(abutton); + return abutton; + } + + public void clearControlButtons () + { + _buttonPanel.removeAll(); + } + + protected ImporterPanel _panel; + protected JPanel _top; + protected JPanel _buttonPanel; +} diff --git a/projects/robodj/src/java/robodj/importer/ImporterPanel.java b/projects/robodj/src/java/robodj/importer/ImporterPanel.java new file mode 100644 index 00000000..8b3594eb --- /dev/null +++ b/projects/robodj/src/java/robodj/importer/ImporterPanel.java @@ -0,0 +1,25 @@ +// +// $Id: ImporterPanel.java,v 1.1 2000/12/10 07:02:09 mdb Exp $ + +package robodj.importer; + +import java.awt.LayoutManager; +import javax.swing.JPanel; + +public abstract class ImporterPanel extends JPanel +{ + public ImporterPanel () + { + } + + public ImporterPanel (LayoutManager layout) + { + super(layout); + } + + /** + * When an importer panel is added to the importer frame, it is + * notified so that it can create the appropriate control buttons. + */ + public abstract void wasAddedToFrame (ImporterFrame frame); +} diff --git a/projects/robodj/src/java/robodj/importer/InsertCDPanel.java b/projects/robodj/src/java/robodj/importer/InsertCDPanel.java new file mode 100644 index 00000000..c30fbad8 --- /dev/null +++ b/projects/robodj/src/java/robodj/importer/InsertCDPanel.java @@ -0,0 +1,61 @@ +// +// $Id: InsertCDPanel.java,v 1.1 2000/12/10 07:02:09 mdb Exp $ + +package robodj.importer; + +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.net.URL; +import javax.swing.*; + +public class InsertCDPanel + extends ImporterPanel + implements ActionListener +{ + public InsertCDPanel () + { + super(new GridLayout(1, 2)); + + // create our image for the left hand side + ClassLoader cl = getClass().getClassLoader(); + URL cdurl = cl.getResource("robodj/importer/cd.jpg"); + ImageIcon cdicon = new ImageIcon(cdurl); + JLabel cdlabel = new JLabel(cdicon); + add(cdlabel); + + // create the "insert cd" text label for the right hand side + JLabel ilabel = new JLabel("Please insert the CD...", + JLabel.CENTER); + add(ilabel); + + // give ourselves a wee bit of a border + setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + } + + public void wasAddedToFrame (ImporterFrame frame) + { + frame.addControlButton("Cancel", "cancel", this); + frame.addControlButton("Next...", "next", this); + + // keep a handle on this for later + _frame = frame; + } + + public void actionPerformed (ActionEvent e) + { + String cmd = e.getActionCommand(); + if (cmd.equals("cancel")) { + System.exit(0); + + } else if (cmd.equals("next")) { + CDDBLookupPanel panel = new CDDBLookupPanel(); + _frame.setPanel(panel); + + } else { + System.out.println("Unknown action event: " + cmd); + } + } + + protected ImporterFrame _frame; +} diff --git a/projects/robodj/src/java/robodj/importer/Makefile b/projects/robodj/src/java/robodj/importer/Makefile new file mode 100644 index 00000000..8cf51249 --- /dev/null +++ b/projects/robodj/src/java/robodj/importer/Makefile @@ -0,0 +1,17 @@ +# +# $Id: Makefile,v 1.1 2000/12/10 07:02:09 mdb Exp $ + +ROOT = ../.. + +SRCS = \ + CDDBLookupPanel.java \ + CDDBUtil.java \ + CDInfoEditor.java \ + DetailTableModel.java \ + Importer.java \ + ImporterFrame.java \ + ImporterPanel.java \ + InsertCDPanel.java \ + RipPanel.java \ + +include $(ROOT)/build/Makefile.java diff --git a/projects/robodj/src/java/robodj/importer/Panel.java b/projects/robodj/src/java/robodj/importer/Panel.java new file mode 100644 index 00000000..a2093f3b --- /dev/null +++ b/projects/robodj/src/java/robodj/importer/Panel.java @@ -0,0 +1,8 @@ +// +// $Id: Panel.java,v 1.1 2000/12/10 07:02:09 mdb Exp $ + +package robodj.importer; + +public class Panel extends JPanel +{ +} diff --git a/projects/robodj/src/java/robodj/importer/RipPanel.java b/projects/robodj/src/java/robodj/importer/RipPanel.java new file mode 100644 index 00000000..1bdb747b --- /dev/null +++ b/projects/robodj/src/java/robodj/importer/RipPanel.java @@ -0,0 +1,86 @@ +// +// $Id: RipPanel.java,v 1.1 2000/12/10 07:02:09 mdb Exp $ + +package robodj.importer; + +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.net.URL; +import java.util.ArrayList; + +import javax.swing.*; + +import com.samskivert.net.cddb.CDDB; +import com.samskivert.swing.util.*; +import com.samskivert.swing.*; +import com.samskivert.util.StringUtil; + +import robodj.convert.*; +import robodj.repository.*; + +public class RipPanel + extends ImporterPanel + implements ActionListener, TaskObserver +{ + public RipPanel (Ripper.TrackInfo[] info, Entry entry) + { + GroupLayout gl = new HGroupLayout(GroupLayout.STRETCH); + gl.setOffAxisPolicy(GroupLayout.STRETCH); + setLayout(gl); + + // create our label for the left hand side + JLabel cdlabel = new JLabel("Ripping", JLabel.CENTER); + cdlabel.setFont(new Font("Helvetica", Font.BOLD, 24)); + add(cdlabel, GroupLayout.FIXED); + + // save this stuff for later + _info = info; + _entry = entry; + } + + public void wasAddedToFrame (ImporterFrame frame) + { + // keep this for later + _frame = frame; + + frame.addControlButton("Cancel", "cancel", this); + _next = frame.addControlButton("Next...", "next", this); + _next.setEnabled(false); + } + + public void taskCompleted (String name, Object result) + { + if (name.equals("getinfo")) { + + } else if (name.equals("cddb_lookup")) { + } + } + + public void taskFailed (String name, Throwable exception) + { + if (name.equals("getinfo")) { + + } else if (name.equals("cddb_lookup")) { + } + } + + public void actionPerformed (ActionEvent e) + { + String cmd = e.getActionCommand(); + if (cmd.equals("cancel")) { + System.exit(0); + + } else if (cmd.equals("next")) { + + } else { + System.out.println("Unknown action event: " + cmd); + } + } + + protected ImporterFrame _frame; + protected JButton _next; + + protected Ripper.TrackInfo[] _info; + protected Entry _entry; +} diff --git a/projects/robodj/src/java/robodj/importer/cd.jpg b/projects/robodj/src/java/robodj/importer/cd.jpg new file mode 100644 index 0000000000000000000000000000000000000000..aa58cb6a1c8d0c4f5703dfce0a041cc4c12109fa GIT binary patch literal 13513 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 literal 0 HcmV?d00001