diff --git a/projects/robodj/src/java/robodj/chooser/BrowsePanel.java b/projects/robodj/src/java/robodj/chooser/BrowsePanel.java index 5de43ca1..c2266ac1 100644 --- a/projects/robodj/src/java/robodj/chooser/BrowsePanel.java +++ b/projects/robodj/src/java/robodj/chooser/BrowsePanel.java @@ -1,14 +1,10 @@ // -// $Id: BrowsePanel.java,v 1.1 2001/07/12 23:06:55 mdb Exp $ +// $Id: BrowsePanel.java,v 1.2 2002/02/22 07:06:33 mdb Exp $ package robodj.chooser; -// import java.awt.*; -// import java.awt.event.ActionEvent; -// import java.awt.event.ActionListener; import javax.swing.*; -// import com.samskivert.swing.*; import robodj.repository.*; public class BrowsePanel extends JTabbedPane @@ -20,13 +16,13 @@ public class BrowsePanel extends JTabbedPane // create a tab for each category for (int i = 0; i < cats.length; i++) { - elist = new EntryList(cats[i].categoryid); + elist = new CategoryEntryList(cats[i].categoryid); String tip = "Browse entries in '" + cats[i].name + "' category."; addTab(cats[i].name, null, elist, tip); } // and add one for uncategorized entries - elist = new EntryList(-1); + elist = new CategoryEntryList(-1); addTab("Uncategorized", null, elist, "Browse uncategorized entries."); diff --git a/projects/robodj/src/java/robodj/chooser/CategoryEntryList.java b/projects/robodj/src/java/robodj/chooser/CategoryEntryList.java new file mode 100644 index 00000000..f008a806 --- /dev/null +++ b/projects/robodj/src/java/robodj/chooser/CategoryEntryList.java @@ -0,0 +1,36 @@ +// +// $Id: CategoryEntryList.java,v 1.1 2002/02/22 07:06:33 mdb Exp $ + +package robodj.chooser; + +import com.samskivert.io.PersistenceException; + +import robodj.repository.Entry; + +public class CategoryEntryList extends EntryList +{ + /** + * Constructs an entry list for entries in a particular category. + */ + public CategoryEntryList (int categoryId) + { + _categoryId = categoryId; + } + + /** + * Reads in the entries for this category. + */ + public Entry[] readEntries () + throws PersistenceException + { + return Chooser.model.getEntries(_categoryId); + } + + protected String getEmptyString () + { + return "No entries in this category."; + } + + /** The unique identifier of the category we are displaying. */ + protected int _categoryId; +} diff --git a/projects/robodj/src/java/robodj/chooser/ChooserFrame.java b/projects/robodj/src/java/robodj/chooser/ChooserFrame.java index 2bc23d68..22494b75 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.5 2001/07/13 00:11:05 mdb Exp $ +// $Id: ChooserFrame.java,v 1.6 2002/02/22 07:06:33 mdb Exp $ package robodj.chooser; @@ -40,6 +40,10 @@ public class ChooserFrame BrowsePanel bpanel = new BrowsePanel(); tip = "Browse and select tunes to play."; tpane.addTab("Browse", null, bpanel, tip); + + SearchPanel spanel = new SearchPanel(); + tip = "Search titles and artists."; + tpane.addTab("Search", null, spanel, tip); top.add(tpane); // the bottom is the control bar diff --git a/projects/robodj/src/java/robodj/chooser/EntryList.java b/projects/robodj/src/java/robodj/chooser/EntryList.java index d813fd9f..d9fc3982 100644 --- a/projects/robodj/src/java/robodj/chooser/EntryList.java +++ b/projects/robodj/src/java/robodj/chooser/EntryList.java @@ -1,5 +1,5 @@ // -// $Id: EntryList.java,v 1.9 2001/10/12 18:21:27 mdb Exp $ +// $Id: EntryList.java,v 1.10 2002/02/22 07:06:33 mdb Exp $ package robodj.chooser; @@ -17,11 +17,10 @@ import com.samskivert.swing.util.*; import robodj.Log; import robodj.repository.*; -public class EntryList - extends JPanel +public abstract class EntryList extends JPanel implements TaskObserver, ActionListener, AncestorListener { - public EntryList (int categoryid) + public EntryList () { GroupLayout gl = new VGroupLayout(GroupLayout.STRETCH); gl.setOffAxisPolicy(GroupLayout.STRETCH); @@ -48,10 +47,6 @@ public class EntryList // use a special font for our name buttons _nameFont = new Font("Helvetica", Font.PLAIN, 10); - // keep track of the query that we'll use to populate our entries - // display - _catid = categoryid; - // listen to ancestor events addAncestorListener(this); } @@ -90,13 +85,16 @@ public class EntryList } /** - * Reads the entries from the database. + * 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 Entry[] readEntries () - throws PersistenceException - { - return Chooser.model.getEntries(_catid); - } + public abstract Entry[] readEntries () + throws PersistenceException; + + /** The string to display when there are no matching results. */ + protected abstract String getEmptyString (); /** * Reads the entries from the database. @@ -190,7 +188,7 @@ public class EntryList // if there were no entries, stick a label in to that effect if (_entries.length == 0) { - _bpanel.add(new JLabel("No entries in this category.")); + _bpanel.add(new JLabel(getEmptyString())); } // reset our scroll position so that we're displaying the top of @@ -205,6 +203,7 @@ public class EntryList // we've removed and added components so we need to revalidate revalidate(); + repaint(); } protected void populateSong (Entry entry) @@ -356,7 +355,6 @@ public class EntryList protected JPanel _bpanel; protected JButton _upbut; - protected int _catid; protected Entry[] _entries; protected Entry _entry; diff --git a/projects/robodj/src/java/robodj/chooser/QueryEntryList.java b/projects/robodj/src/java/robodj/chooser/QueryEntryList.java new file mode 100644 index 00000000..1c939bad --- /dev/null +++ b/projects/robodj/src/java/robodj/chooser/QueryEntryList.java @@ -0,0 +1,42 @@ +// +// $Id: QueryEntryList.java,v 1.1 2002/02/22 07:06:33 mdb Exp $ + +package robodj.chooser; + +import com.samskivert.io.PersistenceException; + +import robodj.repository.Entry; + +public class QueryEntryList extends EntryList +{ + /** + * Constructs an entry list to display entries that match a query. + */ + public QueryEntryList (String query) + { + _query = query; + } + + /** + * Reads in the entries for this query. + */ + public Entry[] readEntries () + throws PersistenceException + { + if (_entries == null) { + _entries = Chooser.repository.matchEntries(_query); + } + return _entries; + } + + protected String getEmptyString () + { + return "No matches to your query."; + } + + /** 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/SearchPanel.java b/projects/robodj/src/java/robodj/chooser/SearchPanel.java new file mode 100644 index 00000000..e45c19bd --- /dev/null +++ b/projects/robodj/src/java/robodj/chooser/SearchPanel.java @@ -0,0 +1,74 @@ +// +// $Id: SearchPanel.java,v 1.1 2002/02/22 07:06:33 mdb Exp $ + +package robodj.chooser; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.*; + +import com.samskivert.swing.*; +import com.samskivert.util.StringUtil; + +import robodj.repository.*; + +public class SearchPanel extends JPanel + implements ActionListener +{ + public SearchPanel () + { + GroupLayout gl = new VGroupLayout(GroupLayout.STRETCH); + gl.setOffAxisPolicy(GroupLayout.STRETCH); + gl.setJustification(GroupLayout.TOP); + setLayout(gl); + + add(new JLabel("Enter a word to be matched against the " + + "artists, titles and songs:"), GroupLayout.FIXED); + + gl = new HGroupLayout(GroupLayout.STRETCH); + JPanel qpanel = new JPanel(gl); + _qbox = new JTextField(); + _qbox.addActionListener(this); + _qbox.setActionCommand("query"); + qpanel.add(_qbox); + + JButton qbutton = new JButton("Query"); + qbutton.addActionListener(this); + qbutton.setActionCommand("query"); + qpanel.add(qbutton, GroupLayout.FIXED); + add(qpanel, GroupLayout.FIXED); + } + + public void actionPerformed (ActionEvent e) + { + if (e.getActionCommand().equals("query")) { + String text = _qbox.getText(); + if (StringUtil.blank(text) || text.equals(_query)) { + // ignore empty text or repeat queries + return; + } + + // make a note of the query + _query = text; + + // remove any previous query results + if (_qlist != null) { + remove(_qlist); + _qlist = null; + } + + // create and add a query entry list for the query + _qlist = new QueryEntryList(_query); + add(_qlist); + } + } + + /** The most recent query. */ + protected String _query; + + /** The query text box. */ + protected JTextField _qbox; + + /** The entry list that displays the results of our query. */ + protected QueryEntryList _qlist; +} diff --git a/projects/robodj/src/java/robodj/repository/Entry.java b/projects/robodj/src/java/robodj/repository/Entry.java index dd1288d7..7150ddb3 100644 --- a/projects/robodj/src/java/robodj/repository/Entry.java +++ b/projects/robodj/src/java/robodj/repository/Entry.java @@ -1,5 +1,5 @@ // -// $Id: Entry.java,v 1.3 2001/07/13 00:10:26 mdb Exp $ +// $Id: Entry.java,v 1.4 2002/02/22 07:06:33 mdb Exp $ package robodj.repository; @@ -33,6 +33,18 @@ public class Entry /** The songs associated with this entry. */ public transient Song[] songs; + /** + * Entry equality is based on entry id. + */ + public boolean equals (Object other) + { + if (other instanceof Entry) { + return entryid == ((Entry)other).entryid; + } else { + return false; + } + } + /** * Scans the songs array for the song with the specified id and * returns it if found. Returns null otherwise. diff --git a/projects/robodj/src/java/robodj/repository/Repository.java b/projects/robodj/src/java/robodj/repository/Repository.java index 7667145f..772eb6b9 100644 --- a/projects/robodj/src/java/robodj/repository/Repository.java +++ b/projects/robodj/src/java/robodj/repository/Repository.java @@ -1,9 +1,10 @@ // -// $Id: Repository.java,v 1.8 2001/09/21 03:09:01 mdb Exp $ +// $Id: Repository.java,v 1.9 2002/02/22 07:06:34 mdb Exp $ package robodj.repository; import java.sql.*; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Properties; @@ -99,10 +100,10 @@ public class Repository extends JORARepository * @param query a WHERE clause describing which entries should be * selected (ie. 'where title like "%foo%"'). * - * @return all entries in the table matching the query - * parameters. Note: this member function does not populate - * the songs arrays of the returned entries. Call - * populateSongs on an entry by entry basis for that. + * @return all entries in the table matching the query parameters. + * Note: this member function does not populate the songs + * arrays of the returned entries. Call populateSongs on + * an entry by entry basis for that. */ public Entry[] getEntries (final String query) throws PersistenceException @@ -124,6 +125,84 @@ public class Repository extends JORARepository }); } + /** + * Returns all entries for whom the supplied word is contained in the + * title or artist text or in the text of any of that entry's songs. + * + * @param match a string to be case-insensitively matched in the title + * or artist text. + * + * @return all entries in the table matching the query parameters. + * Note: this member function does not populate the songs + * arrays of the returned entries. Call populateSongs on + * an entry by entry basis for that. + */ + public Entry[] matchEntries (final String match) + throws PersistenceException + { + return (Entry[])execute(new Operation() { + public Object invoke (Connection conn, DatabaseLiaison liaison) + throws PersistenceException, SQLException + { + System.out.println("looking up '" + match + "'."); + + PreparedStatement stmt = null; + String query; + + try { + // look up matching entries + query = "select entryid, title, artist, source " + + "from entries where title like ? or artist like ?"; + stmt = conn.prepareStatement(query); + stmt.setString(1, "%" + match + "%"); + stmt.setString(2, "%" + match + "%"); + + HashIntMap etable = new HashIntMap(); + loadEntries(stmt, etable); + JDBCUtil.close(stmt); + + // now look up matching songs + query = "select entries.entryid, entries.title, " + + "entries.artist, entries.source from " + + "songs, entries where " + + "songs.entryid = entries.entryid and " + + "songs.title like ?"; + stmt = conn.prepareStatement(query); + stmt.setString(1, "%" + match + "%"); + loadEntries(stmt, etable); + + Entry[] entries = new Entry[etable.size()]; + Iterator iter = etable.values().iterator(); + for (int i = 0; iter.hasNext(); i++) { + entries[i] = (Entry)iter.next(); + } + return entries; + + } finally { + JDBCUtil.close(stmt); + } + } + }); + } + + /** + * Loads entries from the supplied query and inserts them into the + * supplied hash map. + */ + protected void loadEntries (PreparedStatement stmt, HashIntMap entries) + throws SQLException + { + ResultSet rs = stmt.executeQuery(); + while (rs.next()) { + Entry entry = new Entry(); + entry.entryid = rs.getInt(1); + entry.title = rs.getString(2); + entry.artist = rs.getString(3); + entry.source = rs.getString(4); + entries.put(entry.entryid, entry); + } + } + /** * Loads the songs for this entry from the database and inserts them * into its songs array. If the songs are already loaded, this