diff options
author | Matt Windsor <mattwindsor@btinternet.com> | 2011-03-21 21:54:31 +0000 |
---|---|---|
committer | Matt Windsor <mattwindsor@btinternet.com> | 2011-03-21 21:54:31 +0000 |
commit | df7d7981b56a4560c95ea7e9b194080e93398ecf (patch) | |
tree | b3ae4f02d23ae1f7f4951c776ee8d91b0047dd6f /src/uk/org/ury/frontend/modules/library | |
parent | 2d073129857a42ab4195cd433c8be152e357033f (diff) |
GREAT PACKAGE RESHUFFLE: Everything is now organised into frontend, backend and common (to frontend and backend) packages. Things may have been broken. Doc refresh.
Diffstat (limited to 'src/uk/org/ury/frontend/modules/library')
5 files changed, 616 insertions, 0 deletions
diff --git a/src/uk/org/ury/frontend/modules/library/LibraryTableModel.java b/src/uk/org/ury/frontend/modules/library/LibraryTableModel.java new file mode 100644 index 0000000..334eb8b --- /dev/null +++ b/src/uk/org/ury/frontend/modules/library/LibraryTableModel.java @@ -0,0 +1,184 @@ +/** + * + */ +package uk.org.ury.frontend.modules.library; + +import java.util.List; + +import javax.swing.table.AbstractTableModel; + +import uk.org.ury.backend.database.exceptions.MissingPropertyException; +import uk.org.ury.common.library.item.LibraryItem; +import uk.org.ury.common.library.item.LibraryItemProperty; + + +/** + * A table model for the library viewer. + * + * @author Matt Windsor + */ + +public class LibraryTableModel extends AbstractTableModel +{ + + /** + * + */ + + private static final long serialVersionUID = -1744980619128903509L; + + private List<LibraryItem> data; + + + /** + * Construct a new table model. + * + * @param data The list of data on which the model will be based. + */ + + public + LibraryTableModel (List<LibraryItem> data) + { + this.data = data; + } + + + /* (non-Javadoc) + * @see javax.swing.table.TableModel#getColumnCount() + */ + + @Override + public int + getColumnCount () + { + return 6; + } + + + /* (non-Javadoc) + * @see javax.swing.table.TableModel#getRowCount() + */ + + @Override + public int + getRowCount () + { + return data.size (); + } + + + /** + * @param c The column whose class should be investigated. + * + * @return the column class of column c. + */ + + @Override + public Class<?> + getColumnClass (int c) + { + return getValueAt (0, c).getClass (); + } + + + /* (non-Javadoc) + * @see javax.swing.table.TableModel#getValueAt(int, int) + */ + + @Override + public Object + getValueAt (int rowIndex, int columnIndex) + { + LibraryItem li = data.get (rowIndex); + + try + { + String[] columnData = {li.get (LibraryItemProperty.TITLE), + li.get (LibraryItemProperty.ARTIST), + li.get (LibraryItemProperty.ALBUM)}; + + switch (columnIndex) + { + default: // Title, artist, album, unknown + + if (columnIndex >= columnData.length) + return "Unknown"; + else + return columnData[columnIndex]; + + case 3: // Medium + + // TODO: Make this less kludge-y + + String mediumString = li.get (LibraryItemProperty.MEDIUM); + + if (mediumString.equals ("c")) + return "Compact Disc"; + else if (mediumString.equals ("7")) + return "7\" Vinyl"; + else if (mediumString.equals ("2")) + return "12\" Vinyl"; + else + return "Unrecognised"; + + case 4: // Clean? + + // Return true if marked true, false if marked false or unknown etc. + + String cleanString = li.get (LibraryItemProperty.IS_CLEAN); + + // TODO: Nicer way of showing this + + if (cleanString.equals ("y")) + return "Yes"; + else if (cleanString.equals ("n")) + return "No"; + else + return "???"; + + case 5: // isDigitised + + // Return true if marked true, false if marked false or unknown etc. + + String digitisedString = li.get (LibraryItemProperty.IS_DIGITISED); + + if (digitisedString.equals ("t")) + return true; + else + return false; + } + } + catch (MissingPropertyException e) + { + return "Unknown"; + } + } + + + /* (non-Javadoc) + * @see javax.swing.table.TableModel#getColumnName(int, int) + */ + + @Override + public String + getColumnName (int index) + { + switch (index) + { + case 0: + return "Title"; + case 1: + return "Artist"; + case 2: + return "Album"; + case 3: + return "Medium"; + case 4: + return "Clean?"; + case 5: + return "On system?"; + default: + return "ERROR"; + } + } +} diff --git a/src/uk/org/ury/frontend/modules/library/LibraryViewer.java b/src/uk/org/ury/frontend/modules/library/LibraryViewer.java new file mode 100644 index 0000000..c4a3630 --- /dev/null +++ b/src/uk/org/ury/frontend/modules/library/LibraryViewer.java @@ -0,0 +1,159 @@ +/* + * LibraryViewer.java + * ------------------ + * + * Part of the URY Frontend Platform + * + * V0.00 2011/03/20 + * + * (C) 2011 URY Computing + */ + +package uk.org.ury.frontend.modules.library; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import uk.org.ury.common.library.exceptions.EmptySearchException; +import uk.org.ury.common.library.item.LibraryItem; +import uk.org.ury.common.library.item.LibraryItemProperty; +import uk.org.ury.common.protocol.Directive; +import uk.org.ury.common.protocol.ProtocolUtils; +import uk.org.ury.common.protocol.exceptions.DecodeFailureException; +import uk.org.ury.common.protocol.exceptions.InvalidMessageException; +import uk.org.ury.frontend.AbstractFrontendModule; +import uk.org.ury.frontend.FrontendMaster; +import uk.org.ury.frontend.FrontendModulePanel; +import uk.org.ury.frontend.client.Client; +import uk.org.ury.frontend.exceptions.UICreationFailureException; + +/** + * Module for investigating the track library. + * + * The <code>LibraryViewer</code> and its corresponding user + * interface, <code>LibraryViewerPanel</code>, provide a + * user interface for querying the server's library services + * for track information. + * + * Subclasses of this module provide editing features for + * the track library. + * + * @author Matt Windsor + */ +public class LibraryViewer extends AbstractFrontendModule { + /** + * + */ + private static final long serialVersionUID = -2782366476480563739L; + private List<LibraryItem> libraryList; + private LibraryViewerPanel panel; + + /** + * Constructs a new LibraryViewer as a frontend object. + */ + public LibraryViewer() { + libraryList = new ArrayList<LibraryItem>(); + panel = null; + } + + /** + * Runs the library viewer frontend. + */ + @Override + public FrontendModulePanel runFrontend(FrontendMaster master) { + try { + panel = new LibraryViewerPanel(this, master); + } catch (UICreationFailureException e) { + master.fatalError(e.getMessage()); + } + + return panel; + } + + /** + * Does a library search. + * + * This will update the library list to reflect the results of the search. + * + * @param search + * The string fragment to use in searches. Cannot be empty or + * null. + * + * @throws EmptySearchException + * if the search string is empty or null (from + * LibraryUtils.search). + * + * @throws InvalidMessageException + * if the response from the server is invalid. + */ + public void doSearch(String search) throws EmptySearchException, + InvalidMessageException { + // TODO: fan out? + + if (search == null || search == "") + throw new EmptySearchException(); + + Client cl = new Client(); + Map<?, ?> response = null; + libraryList.clear(); + + try { + response = cl + .get("/library/tracks?search=" + + search); + } catch (DecodeFailureException e) { + throw new InvalidMessageException(e.getMessage()); + } + + /* + * Check to see if this is Map<String, ?> by looking for the status, + * which should always be in a valid response. + */ + + if (ProtocolUtils.responseIsOK(response) == false) + throw new InvalidMessageException( + (String) response.get(Directive.REASON.toString())); + + // Should contain a list of items, even if there are no items. + if (response.containsKey(Directive.ITEMS.toString()) == false) + throw new InvalidMessageException("No item set returned."); + + if ((response.get(Directive.ITEMS.toString()) instanceof List<?>) == false) + throw new InvalidMessageException("Malformed item list."); + + for (Object obj : (List<?>) response.get(Directive.ITEMS.toString())) { + Map<LibraryItemProperty, String> properties = new HashMap<LibraryItemProperty, String>(); + + if (obj instanceof Map<?, ?> == false) + throw new InvalidMessageException("Malformed item."); + + Set<?> keySet = ((Map<?, ?>) obj).keySet(); + + // Check to make sure this item has only String-String mappings. + for (Object key : keySet) { + if ((key instanceof String && ((Map<?, ?>) obj).get(key) instanceof String) == false) + throw new InvalidMessageException("Not a valid property."); + else if (LibraryItemProperty.valueOf((String) key) == null) + throw new InvalidMessageException("Property type " + key + + " not recognised."); + else + properties.put(LibraryItemProperty.valueOf((String) key), + (String) ((Map<?, ?>) obj).get(key)); + + } + + libraryList.add(new LibraryItem(properties)); + } + } + + /** + * @return the current library list. + */ + + public List<LibraryItem> getLibraryList() { + return libraryList; + } +} diff --git a/src/uk/org/ury/frontend/modules/library/LibraryViewer.properties b/src/uk/org/ury/frontend/modules/library/LibraryViewer.properties new file mode 100644 index 0000000..bb88988 --- /dev/null +++ b/src/uk/org/ury/frontend/modules/library/LibraryViewer.properties @@ -0,0 +1,22 @@ +// Module name +MODULE_NAME = Library Viewer Demo + +// Search errors +// 1st parameter: search term, 2nd parameter: failure reason etc. +ERR_UNKNOWN = Unknown error. +ERR_SEARCH_FAILED = Search for '%1$s' failed: %2$s +ERR_EMPTY_SEARCH = Please type in a search term. +ERR_NO_RESULTS = Sorry, but no results were found for '%1$s'. + +// Search messages +// 1st parameter: search term +MSG_SEARCHING = Searching for '%1$s', please wait... + +// Labels +LBL_SEARCHFOR = Search for: + +// Buttons +BTN_SEARCH = Start Search + +// Hint +HINT = To narrow your search, type part or all of the record title or artist into the box above.
\ No newline at end of file diff --git a/src/uk/org/ury/frontend/modules/library/LibraryViewerPanel.java b/src/uk/org/ury/frontend/modules/library/LibraryViewerPanel.java new file mode 100644 index 0000000..37c134a --- /dev/null +++ b/src/uk/org/ury/frontend/modules/library/LibraryViewerPanel.java @@ -0,0 +1,219 @@ +/* + * LibraryViewerPanel.java + * ----------------------- + * + * Part of the URY Frontend Platform + * + * V0.00 2011/03/20 + * + * (C) 2011 URY Computing + */ + +package uk.org.ury.frontend.modules.library; + +import java.util.ResourceBundle; +import java.util.concurrent.ExecutionException; + +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JTextField; +import javax.swing.SwingWorker; + +import uk.org.ury.common.library.exceptions.EmptySearchException; +import uk.org.ury.common.protocol.exceptions.InvalidMessageException; +import uk.org.ury.frontend.FrontendMaster; +import uk.org.ury.frontend.FrontendModulePanel; +import uk.org.ury.frontend.HintField; +import uk.org.ury.frontend.exceptions.UICreationFailureException; + +/** + * Frontend panel providing access to an underlying library viewer. + * + * @author Matt Windsor + * @author Nathan Lasseter + */ +public class LibraryViewerPanel extends FrontendModulePanel { + /** + * + */ + private static final long serialVersionUID = -2441616418398056712L; + + /* Panel widgets exposed by the SwiXML user interface. */ + + private JTable resultsTable; + private JScrollPane resultsPane; + + private JPanel messagePanel; + private JLabel messageLabel; + + private JPanel searchingPanel; + private JLabel searchingLabel; + + private JTextField searchField; + private JButton searchButton; + private JLabel searchForLabel; + + private HintField hint; + + /* + * This contains the last search failure message, for use in letting the + * user know what happened. + */ + + private String searchFailureMessage; + + // Resource bundle. + + private ResourceBundle rb; + + /** + * Construct a new LibraryViewerPanel. + * + * @param viewer + * The LibraryViewer controlling this LibraryViewerPanel. + * + * @param master + * The FrontendMaster driving the frontend. + * + * @throws UICreationFailureException + * if the UI creation fails. + */ + + public LibraryViewerPanel(LibraryViewer viewer, FrontendMaster master) + throws UICreationFailureException { + /* + * The UI implementation is contained in library_viewer_gui.xml. + * + * See this file for more details. + */ + super(viewer, "library_viewer_gui.xml", master); + + // Fill in locale-specific data. + rb = ResourceBundle + .getBundle("uk.org.ury.frontend.modules.library.LibraryViewer"); + + searchFailureMessage = rb.getString("ERR_UNKNOWN"); + + searchingLabel.setText(rb.getString("MSG_SEARCHING")); + searchForLabel.setText(rb.getString("LBL_SEARCHFOR")); + searchButton.setText(rb.getString("BTN_SEARCH")); + hint.setText(rb.getString("HINT")); + + // Fine-tune table + resultsTable.setAutoCreateRowSorter(true); + } + + /** + * @return the name of the panel. + * + * @see uk.org.ury.frontend.FrontendModulePanel#getModuleName() + */ + @Override + public String getModuleName() { + return rb.getString("MODULE_NAME"); + } + + /** + * Action method for performing a search, bound by the UI XML manifest to + * the search field and button. + */ + public void search() { + /* + * We can't let the user search while another search is going on, but + * it's not good to let the search "freeze" the UI. + * + * Hence the search function disables all sensitive parts of the + * interface and launches a search as a background process. + * + * We also swap the results table or no-results panel out for a panel + * that says "Searching...", in the interests of user-friendliness. + */ + searchField.setEnabled(false); + searchButton.setEnabled(false); + resultsPane.setVisible(false); + messagePanel.setVisible(false); + searchingPanel.setVisible(true); + searchingLabel.setText(String.format(rb.getString("MSG_SEARCHING"), + searchField.getText())); + + final LibraryViewer master = (LibraryViewer) getModule(); + + SwingWorker<Boolean, Void> worker = new SwingWorker<Boolean, Void>() { + private String searchTerm = ""; + + /** + * Perform a task in a separate thread from event-dispatch. + * + * In this case, perform a search. + * + * @return whether or not the search was successful. + */ + @Override + public Boolean doInBackground() { + searchTerm = searchField.getText(); + + try { + master.doSearch(searchTerm); + } catch (InvalidMessageException e) { + searchFailureMessage = String.format( + rb.getString("ERR_SEARCH_FAILED"), searchTerm, + e.getMessage()); + return false; + } catch (EmptySearchException e) { + searchFailureMessage = rb.getString("ERR_EMPTY_SEARCH"); + return false; + } + + return true; + } + + /** + * Perform post-search cleanup and finalisation. + */ + @Override + public void done() { + // Figure out whether or not the search succeeded. + boolean hasSucceeded = false; + + try { + hasSucceeded = this.get(); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + searchFailureMessage = String.format( + rb.getString("ERR_SEARCH_FAILED"), searchTerm, + e.getMessage()); + } + + /* + * Re-enable widgets and swap panels according to whether or not + * results were found. + */ + searchField.setEnabled(true); + searchButton.setEnabled(true); + searchingPanel.setVisible(false); + + if (hasSucceeded == false) { + messageLabel.setText(searchFailureMessage); + messagePanel.setVisible(true); + } else if (master.getLibraryList().size() == 0) { + messageLabel.setText(String.format( + rb.getString("ERR_NO_RESULTS"), searchTerm)); + messagePanel.setVisible(true); + } else { + // Force table update with new results. + resultsTable.setModel(new LibraryTableModel(master + .getLibraryList())); + + messagePanel.setVisible(false); + resultsPane.setVisible(true); + } + } + }; + + worker.execute(); + } +} diff --git a/src/uk/org/ury/frontend/modules/library/library_viewer_gui.xml b/src/uk/org/ury/frontend/modules/library/library_viewer_gui.xml new file mode 100644 index 0000000..ce30a1d --- /dev/null +++ b/src/uk/org/ury/frontend/modules/library/library_viewer_gui.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<panel layout="BorderLayout"> + <hbox constraints="BorderLayout.NORTH" border="EmptyBorder(5,5,5,5)"> + <label id="searchForLabel" labelfor="searchField" displayedmnemonic="VK_F" /> + <hbox border="EmptyBorder(0,5,0,5)"> + <textfield id="searchField" mnemonic="VK_F" action="search" /> + </hbox> + <button id="searchButton" mnemonic="VK_S" action="search" /> + </hbox> + + <hbox constraints="BorderLayout.CENTER" border="EmptyBorder(0,5,0,5)"> + <scrollpane id="resultsPane" constraints="BorderLayout.CENTER"> + <table id="resultsTable" /> + </scrollpane> + <panel id="searchingPanel" constraints="BorderLayout.CENTER" visible="false" + layout="BorderLayout"> + <label id="searchingLabel" + constraints="BorderLayout.CENTER" horizontalalignment="CENTER" /> + <progressbar id="searchingProgressBar" indeterminate="true" + constraints="BorderLayout.SOUTH" /> + </panel> + <panel id="messagePanel" constraints="BorderLayout.CENTER" visible="false" + layout="BorderLayout"> + <label id="messageLabel" text="You shouldn't see this." + constraints="BorderLayout.CENTER" horizontalalignment="CENTER" /> + </panel> + </hbox> + + <hbox constraints="BorderLayout.SOUTH" border="EmptyBorder(5,5,5,5)"> + <hint id="hint" /> + </hbox> +</panel> |