Heavily refactored PlayerGUI to remove coupling with library and playlist functionality.

This commit is contained in:
neviyn 2016-03-19 19:49:35 +00:00
parent 225a403de6
commit 974ff2c8c1
17 changed files with 689 additions and 561 deletions

View File

@ -101,6 +101,7 @@
<groupId>com.google.inject</groupId> <groupId>com.google.inject</groupId>
<artifactId>guice</artifactId> <artifactId>guice</artifactId>
<version>4.0</version> <version>4.0</version>
<scope>compile</scope>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -1,10 +1,11 @@
package musicplayer; package musicplayer;
import musicplayer.swingmodels.LibraryListModel; import musicplayer.util.ConfigManager;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import java.util.List; import java.util.List;
class LibraryConfigGUI { class LibraryConfigGUI {
@ -73,4 +74,74 @@ class LibraryConfigGUI {
files.forEach(listModel::removeFile); files.forEach(listModel::removeFile);
saveLibraryList(); saveLibraryList();
} }
private class LibraryListModel<T extends File> extends AbstractListModel<T> {
private List<T> libraryFolders = new ArrayList<>();
@Override
public int getSize() {
return libraryFolders.size();
}
@Override
public T getElementAt(int index) {
return libraryFolders.get(index);
}
/**
* Add a directory to the list of directories used for library indexing.
* @param folder Directory to add.
*/
public void addFolder(T folder){
if(folder.exists() && folder.isDirectory()) {
libraryFolders.add(folder);
fireAllContentsChanged();
}
}
/**
* @return List of directories currently used for library indexing.
*/
public List<T> currentFolderList(){
return new ArrayList<>(libraryFolders); // Copy? Don't want modification via here.
}
/**
* Check if this File already exists in libraryFolders.
* @param file File to find.
* @return Does file exist in libraryFolders.
*/
public boolean contains(T file){
return libraryFolders.contains(file);
}
/**
* Set the list contained in libraryFolders.
* @param folderList New directory list.
*/
public void setFolderList(List<T> folderList){
if(folderList != null && folderList.size() > 0)
libraryFolders = new ArrayList<>(folderList);
else
libraryFolders = new ArrayList<>();
fireAllContentsChanged();
}
/**
* Remove a directory from libraryFolders.
* @param file Directory to be removed.
*/
public void removeFile(T file){
libraryFolders.remove(file);
fireAllContentsChanged();
}
/**
* Fire that libraryFolders contents has changed.
*/
private void fireAllContentsChanged(){
fireContentsChanged(this, 0, libraryFolders.size() == 0 ? 0 : libraryFolders.size() - 1);
}
}
} }

View File

@ -1,33 +0,0 @@
package musicplayer;
import musicplayer.model.Album;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeCellRenderer;
import java.awt.*;
class LibraryTreeCellRenderer implements TreeCellRenderer {
private final JLabel label;
private static Icon missingIcon = new ImageIcon(LibraryTreeCellRenderer.class.getClassLoader().getResource("missing.gif"));
LibraryTreeCellRenderer() {
label = new JLabel();
}
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
Object o = ((DefaultMutableTreeNode) value).getUserObject();
label.setIcon(null);
if (o != null) {
label.setText(o.toString());
if (o instanceof Album) {
Album album = (Album) o;
label.setIcon(missingIcon);
album.getAlbumArt().ifPresent(x -> label.setIcon(new ImageIcon(x)));
}
}
return label;
}
}

View File

@ -3,17 +3,14 @@ package musicplayer;
import com.google.inject.Guice; import com.google.inject.Guice;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Injector; import com.google.inject.Injector;
import musicplayer.callbacks.LibraryCallbackInterface;
import musicplayer.callbacks.PlayerCallbackInterface; import musicplayer.callbacks.PlayerCallbackInterface;
import musicplayer.db.IDatabase; import musicplayer.db.IDatabase;
import musicplayer.model.Album; import musicplayer.library.ILibrary;
import musicplayer.model.Artist;
import musicplayer.model.HasSongs;
import musicplayer.model.Song; import musicplayer.model.Song;
import musicplayer.player.IPlayer; import musicplayer.player.IPlayer;
import musicplayer.swingmodels.PlaylistTableModel; import musicplayer.playlist.IPlaylist;
import musicplayer.util.ConfigManager;
import musicplayer.util.Icons; import musicplayer.util.Icons;
import musicplayer.util.LibraryUtils;
import musicplayer.util.PlaylistUtils; import musicplayer.util.PlaylistUtils;
import org.jnativehook.GlobalScreen; import org.jnativehook.GlobalScreen;
import org.jnativehook.NativeHookException; import org.jnativehook.NativeHookException;
@ -23,12 +20,7 @@ import org.jnativehook.keyboard.NativeKeyListener;
import javax.swing.*; import javax.swing.*;
import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkEvent;
import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import java.awt.*; import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.event.MouseListener; import java.awt.event.MouseListener;
import java.io.BufferedReader; import java.io.BufferedReader;
@ -36,35 +28,25 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.*; import java.util.*;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterface { public class PlayerGUI extends JPanel implements PlayerCallbackInterface {
private JPanel mainPanel; private final IPlaylist playlist;
private final JTree libraryView = new JTree();
private final JTable playList = new JTable();
private JSlider seekBar; private JSlider seekBar;
private final JComboBox<String> libraryDisplayType = new JComboBox<>();
private final IPlayer player; private final IPlayer player;
private final IDatabase database; private final ILibrary library;
private final PlaylistTableModel playlistTableModel = new PlaylistTableModel(new ArrayList<>());
private static final DefaultMutableTreeNode updatingNode = new DefaultMutableTreeNode();
private boolean libraryUpdating = false;
private final AtomicBoolean seeking = new AtomicBoolean(false); private final AtomicBoolean seeking = new AtomicBoolean(false);
private final Map<String, Runnable> libraryDisplayVariants = createDisplayVariantMap();
@Inject @Inject
public PlayerGUI(IPlayer player, IDatabase database) { public PlayerGUI(IPlayer player, IPlaylist playlist, ILibrary library) {
this.player = player; this.player = player;
this.database = database; this.playlist = playlist;
this.library = library;
createUI(); createUI();
resetTree();
Thread seekBarUpdater = new Thread(() -> { Thread seekBarUpdater = new Thread(() -> {
boolean running = true; boolean running = true;
while (running) { while (running) {
@ -99,15 +81,15 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
System.out.println(ex.getMessage()); System.out.println(ex.getMessage());
System.out.println(Arrays.toString(ex.getStackTrace())); System.out.println(Arrays.toString(ex.getStackTrace()));
} }
refreshLibrary(); library.refreshLibrary();
} }
public static void main(String[] args) { public static void main(String[] args) {
Injector injector = Guice.createInjector(); Injector injector = Guice.createInjector(new SwingUIModule());
PlayerGUI playerGUI = injector.getInstance(PlayerGUI.class); PlayerGUI playerGUI = injector.getInstance(PlayerGUI.class);
JFrame frame = new JFrame(); JFrame frame = new JFrame();
frame.setMinimumSize(new Dimension(600, 400)); frame.setMinimumSize(new Dimension(600, 400));
frame.setContentPane(playerGUI.mainPanel); frame.setContentPane(playerGUI);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.pack(); frame.pack();
frame.setVisible(true); frame.setVisible(true);
@ -125,61 +107,6 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
}); });
} }
/**
* Reset the libraryView to contain nothing.
*/
private void resetTree() {
libraryView.removeAll();
}
private <T extends HasSongs & Comparable<T>> void populateLibraryWithGroupedSongs(List<T> libraryData){
DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode());
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) model.getRoot();
Collections.sort(libraryData);
libraryData.forEach(x -> {
DefaultMutableTreeNode outerNode = new DefaultMutableTreeNode(x);
addNodeToTreeModel(model, parentNode, outerNode);
x.getSongs().forEach(y -> addNodeToTreeModel(model, outerNode, new DefaultMutableTreeNode(y)));
});
libraryView.setModel(model);
}
/**
* Populate the library with all songs.
*
* @param libraryData List of songs.
*/
private void populateLibraryWithSongsOnly(List<Song> libraryData) {
DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode());
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) model.getRoot();
Collections.sort(libraryData);
libraryData.forEach(x -> addNodeToTreeModel(model, parentNode, new DefaultMutableTreeNode(x)));
libraryView.setModel(model);
}
/**
* Add an item to libraryView.
*
* @param parentNode Node that should be the parent of this node.
* @param node This node.
*/
private void addNodeToTreeModel(DefaultTreeModel model, DefaultMutableTreeNode parentNode, DefaultMutableTreeNode node) {
model.insertNodeInto(node, parentNode, parentNode.getChildCount());
if (parentNode == model.getRoot()) {
model.nodeStructureChanged((TreeNode) model.getRoot());
}
}
/**
* Add a song to the current playlist.
*
* @param song Song to be added to the playlist.
*/
private void addToPlaylist(Song song) {
playlistTableModel.addSong(song);
playList.revalidate();
}
/** /**
* Set the highlighted song in the playlist to this song. * Set the highlighted song in the playlist to this song.
* *
@ -187,17 +114,14 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
*/ */
@Override @Override
public void setSongHighlighting(Song playingSong) { public void setSongHighlighting(Song playingSong) {
int index = playlistTableModel.getSongIndex(playingSong); playlist.setPlayingSong(playingSong);
if(index >= 0)
SwingUtilities.invokeLater(() -> playList.setRowSelectionInterval(index, index));
playlistTableModel.setPlayingRow(index);
} }
private void playSong(Optional<Song> song){ private void playSong(Optional<Song> song){
try { try {
player.playSong(song); player.playSong(song);
} catch (StartPlayingException e) { } catch (StartPlayingException e) {
JOptionPane.showMessageDialog(mainPanel, "Failed to play " + song.get().toString() + ".\n" + JOptionPane.showMessageDialog(this, "Failed to play " + song.get().toString() + ".\n" +
"If file path contains non-ASCII characters, please restart the application with UTF-8 encoding."); "If file path contains non-ASCII characters, please restart the application with UTF-8 encoding.");
} }
} }
@ -207,7 +131,7 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
*/ */
public void playNextSong() { public void playNextSong() {
SwingUtilities.invokeLater(() -> seekBar.setValue(0)); SwingUtilities.invokeLater(() -> seekBar.setValue(0));
playSong(playlistTableModel.nextSong(player.getCurrentSong())); playSong(playlist.getNext(player.getCurrentSong()));
} }
/** /**
@ -215,7 +139,7 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
*/ */
public void playPreviousSong() { public void playPreviousSong() {
SwingUtilities.invokeLater(() -> seekBar.setValue(0)); SwingUtilities.invokeLater(() -> seekBar.setValue(0));
playSong(playlistTableModel.previous(player.getCurrentSong())); playSong(playlist.getPrevious(player.getCurrentSong()));
} }
/** /**
@ -229,76 +153,16 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
@Override @Override
public void removeInvalidSong(Song invalidSong) { public void removeInvalidSong(Song invalidSong) {
playlistTableModel.removeSong(invalidSong); playlist.delete(invalidSong);
} }
public void playerStopped(){ public void playerStopped(){
setSongHighlighting(player.getCurrentSong()); setSongHighlighting(player.getCurrentSong());
playlistTableModel.setPlayingRow(-1); playlist.setPlayingSong(null);
}
/**
* Refresh the library with songs in the currently selected display format.
*/
private void refreshLibrary() {
DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode());
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) model.getRoot();
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("Refreshing Library...");
addNodeToTreeModel(model, parentNode, newNode);
libraryView.setModel(model);
Thread populateThread = new Thread(() -> {
libraryDisplayVariants.get(libraryDisplayType.getSelectedItem().toString()).run();
// If we get here and the model hasn't changed, the library must be empty.
if(libraryView.getModel() == model){
DefaultTreeModel failedModel = new DefaultTreeModel(new DefaultMutableTreeNode());
DefaultMutableTreeNode failedParentNode = (DefaultMutableTreeNode) failedModel.getRoot();
DefaultMutableTreeNode failedNode = new DefaultMutableTreeNode("Library is empty!");
addNodeToTreeModel(failedModel, failedParentNode, failedNode);
libraryView.setModel(failedModel);
}
}, "libraryRefresh");
populateThread.start();
}
private void updateLibrary(){
libraryUpdating = true;
DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode());
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) model.getRoot();
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("Updating Library...");
addNodeToTreeModel(model, parentNode, newNode);
addNodeToTreeModel(model, parentNode, updatingNode);
libraryView.setModel(model);
List<Path> dirs = ConfigManager.getLibraryDirectories().stream().map(File::toPath).collect(Collectors.toList());
LibraryUtils.processSongsWithCallback(database, dirs, this);
}
@Override
public void libraryUpdated(boolean successful) {
if (successful)
refreshLibrary();
libraryUpdating = false;
}
@Override
public void currentlyUpdating(String name) {
updatingNode.setUserObject(name);
((DefaultTreeModel)libraryView.getModel()).nodeChanged(updatingNode);
}
/**
* @return Map of display types for the library paired code to populate the library with data in the correct format.
*/
private Map<String, Runnable> createDisplayVariantMap() {
Map<String, Runnable> value = new HashMap<>();
value.put("Song", () -> database.listAllT(Song.class).ifPresent(this::populateLibraryWithSongsOnly));
value.put("Album/Song", () -> database.listAllT(Album.class).ifPresent(this::populateLibraryWithGroupedSongs));
value.put("Artist/Song", () -> database.listAllT(Artist.class).ifPresent(this::populateLibraryWithGroupedSongs));
return value;
} }
/* /*
mainPanel this
+--------------------+ +--------------------+
|menuBar | |menuBar |
+---------+----------+ +---------+----------+
@ -311,79 +175,22 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
+--------------------+ +--------------------+
*/ */
private void createUI() { private void createUI() {
mainPanel = new JPanel(); this.setLayout(new BorderLayout(0, 0));
mainPanel.setLayout(new BorderLayout(0, 0));
final JSplitPane libraryAndPlaylistPane = new JSplitPane(); final JSplitPane libraryAndPlaylistPane = new JSplitPane();
libraryAndPlaylistPane.setDividerLocation(240); libraryAndPlaylistPane.setDividerLocation(240);
libraryAndPlaylistPane.setRightComponent(createPlaylistArea()); libraryAndPlaylistPane.setRightComponent((JScrollPane)playlist);
mainPanel.add(libraryAndPlaylistPane, BorderLayout.CENTER); this.add(libraryAndPlaylistPane, BorderLayout.CENTER);
final JPanel libraryAndComboboxPane = new JPanel(); libraryAndPlaylistPane.setLeftComponent((JPanel)library);
libraryAndComboboxPane.setLayout(new BorderLayout(0, 0));
libraryAndPlaylistPane.setLeftComponent(libraryAndComboboxPane);
libraryDisplayVariants.keySet().forEach(libraryDisplayType::addItem);
libraryDisplayType.setSelectedIndex(ConfigManager.getLastDisplayTypeIndex());
libraryDisplayType.addItemListener(e -> {
if (e.getStateChange() == ItemEvent.SELECTED) {
ConfigManager.setLastDisplayTypeIndex(libraryDisplayType.getSelectedIndex());
if(!libraryUpdating)
libraryDisplayVariants.get(libraryDisplayType.getSelectedItem().toString()).run();
}
});
libraryAndComboboxPane.add(libraryDisplayType, BorderLayout.NORTH);
final JScrollPane libraryPane = new JScrollPane();
libraryAndComboboxPane.add(libraryPane, BorderLayout.CENTER);
libraryPane.setViewportView(createLibraryArea());
final JPanel controlPane = new JPanel(); final JPanel controlPane = new JPanel();
controlPane.setLayout(new BorderLayout(0, 0)); controlPane.setLayout(new BorderLayout(0, 0));
mainPanel.add(controlPane, BorderLayout.SOUTH); this.add(controlPane, BorderLayout.SOUTH);
seekBar = new JSlider(); seekBar = new JSlider();
seekBar.setMinorTickSpacing(1); seekBar.setMinorTickSpacing(1);
seekBar.setValue(0); seekBar.setValue(0);
seekBar.addMouseListener(new SeekbarMouseListener()); seekBar.addMouseListener(new SeekbarMouseListener());
controlPane.add(seekBar, BorderLayout.NORTH); controlPane.add(seekBar, BorderLayout.NORTH);
controlPane.add(createControlButtons(), BorderLayout.SOUTH); controlPane.add(createControlButtons(), BorderLayout.SOUTH);
mainPanel.add(createMenuBar(), BorderLayout.NORTH); this.add(createMenuBar(), BorderLayout.NORTH);
}
/**
* @return Area containing the library.
*/
private JTree createLibraryArea() {
libraryView.setRootVisible(false);
libraryView.setToggleClickCount(1);
libraryView.setCellRenderer(new LibraryTreeCellRenderer());
libraryView.addMouseListener(new LibraryMouseAdapter());
libraryView.setScrollsOnExpand(false);
return libraryView;
}
/**
* @return Area containing the playlist.
*/
private JScrollPane createPlaylistArea() {
JScrollPane playlistScroll = new JScrollPane();
playList.setRowSelectionAllowed(true);
playList.setModel(playlistTableModel);
playList.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
playList.getColumnModel().getColumn(0).setPreferredWidth(10);
playList.getColumnModel().getColumn(0).setMaxWidth(10);
playList.getColumnModel().getColumn(1).setPreferredWidth(40);
playList.getColumnModel().getColumn(1).setMaxWidth(40);
JPopupMenu popupMenu = new JPopupMenu();
JMenuItem menuItem = new JMenuItem("Remove");
menuItem.addActionListener((e) -> playlistTableModel.removeSong(playList.getSelectedRows()));
popupMenu.add(menuItem);
playList.setComponentPopupMenu(popupMenu);
popupMenu = new JPopupMenu();
menuItem = new JMenuItem("Clear all");
menuItem.addActionListener((e) -> {
playlistTableModel.removeAll();
player.stop();
});
popupMenu.add(menuItem);
playlistScroll.setComponentPopupMenu(popupMenu);
playlistScroll.setViewportView(playList);
return playlistScroll;
} }
/** /**
@ -394,16 +201,10 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
toolBar.setFloatable(false); toolBar.setFloatable(false);
JPanel controlBar = new JPanel(new GridLayout(1, 5)); JPanel controlBar = new JPanel(new GridLayout(1, 5));
JButton playButton = new JButton(); JButton playButton = new JButton();
playButton.setName("playButton");
playButton.setIcon(Icons.playIcon); playButton.setIcon(Icons.playIcon);
playButton.setMnemonic('P'); playButton.setMnemonic('P');
playButton.addActionListener(e -> { playButton.addActionListener(e -> playSong(playlist.getActive()));
if (playList.getRowCount() > 0) {
if (playList.getSelectedRowCount() > 0)
playSong(playlistTableModel.getSong(playList.getSelectedRow()));
else
playSong(playlistTableModel.getFirst());
}
});
controlBar.add(playButton); controlBar.add(playButton);
JButton pauseButton = new JButton(); JButton pauseButton = new JButton();
pauseButton.setIcon(Icons.pauseIcon); pauseButton.setIcon(Icons.pauseIcon);
@ -465,12 +266,12 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
// Tools menu // Tools menu
JMenu tools = new JMenu("Tools"); JMenu tools = new JMenu("Tools");
JMenuItem menuItem = new JMenuItem("Refresh Library"); JMenuItem menuItem = new JMenuItem("Refresh Library");
menuItem.addActionListener(e -> refreshLibrary()); menuItem.addActionListener(e -> library.refreshLibrary());
tools.add(menuItem); tools.add(menuItem);
menuItem = new JMenuItem("Update Library"); menuItem = new JMenuItem("Update Library");
menuItem.setToolTipText("Reindex and refresh library."); menuItem.setToolTipText("Reindex and refresh library.");
menuItem.addActionListener(e -> updateLibrary()); menuItem.addActionListener(e -> library.updateLibrary());
tools.add(menuItem); tools.add(menuItem);
menuItem = new JMenuItem("Library Settings"); menuItem = new JMenuItem("Library Settings");
@ -483,7 +284,7 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
JMenu playlistTools = new JMenu("Playlist"); JMenu playlistTools = new JMenu("Playlist");
menuItem = new JMenuItem("Clear All"); menuItem = new JMenuItem("Clear All");
menuItem.addActionListener((e) -> { menuItem.addActionListener((e) -> {
playlistTableModel.removeAll(); playlist.deleteAll();
player.stop(); player.stop();
}); });
playlistTools.add(menuItem); playlistTools.add(menuItem);
@ -491,17 +292,17 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
FileNameExtensionFilter m3uExtensionFilter = new FileNameExtensionFilter("m3u playlist", "m3u"); FileNameExtensionFilter m3uExtensionFilter = new FileNameExtensionFilter("m3u playlist", "m3u");
menuItem = new JMenuItem("Save Playlist"); menuItem = new JMenuItem("Save Playlist");
menuItem.addActionListener(e -> { menuItem.addActionListener(e -> {
if(!playlistTableModel.isEmpty()) { if(!playlist.isEmpty()) {
JFileChooser fileChooser = new JFileChooser(); JFileChooser fileChooser = new JFileChooser();
fileChooser.setDialogType(JFileChooser.SAVE_DIALOG); fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
fileChooser.setSelectedFile(new File("playlist.m3u")); fileChooser.setSelectedFile(new File("playlist.m3u"));
fileChooser.setFileFilter(m3uExtensionFilter); fileChooser.setFileFilter(m3uExtensionFilter);
if (fileChooser.showSaveDialog(mainPanel) == JFileChooser.APPROVE_OPTION) { if (fileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
String filename = fileChooser.getSelectedFile().toString(); String filename = fileChooser.getSelectedFile().toString();
if (!filename.endsWith(".m3u")) if (!filename.endsWith(".m3u"))
filename += ".m3u"; filename += ".m3u";
try { try {
PlaylistUtils.writePlaylistToFile(playlistTableModel.getSongList(), new File(filename)); PlaylistUtils.writePlaylistToFile(playlist.getSongList(), new File(filename));
} catch (IOException e1) { } catch (IOException e1) {
JOptionPane.showMessageDialog(null, e1.getMessage() + "\n" + Arrays.toString(e1.getStackTrace()), "Error", JOptionPane.ERROR_MESSAGE); JOptionPane.showMessageDialog(null, e1.getMessage() + "\n" + Arrays.toString(e1.getStackTrace()), "Error", JOptionPane.ERROR_MESSAGE);
} }
@ -515,8 +316,8 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
JFileChooser fileChooser = new JFileChooser(); JFileChooser fileChooser = new JFileChooser();
fileChooser.setDialogType(JFileChooser.OPEN_DIALOG); fileChooser.setDialogType(JFileChooser.OPEN_DIALOG);
fileChooser.setFileFilter(m3uExtensionFilter); fileChooser.setFileFilter(m3uExtensionFilter);
if(fileChooser.showOpenDialog(mainPanel) == JFileChooser.APPROVE_OPTION){ if(fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION){
PlaylistUtils.readPlaylistFromFile(fileChooser.getSelectedFile()).stream().forEach(playlistTableModel::addSong); PlaylistUtils.readPlaylistFromFile(fileChooser.getSelectedFile()).stream().forEach(playlist::addSong);
} }
}); });
playlistTools.add(menuItem); playlistTools.add(menuItem);
@ -540,7 +341,7 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
e1.printStackTrace(); e1.printStackTrace();
} }
}); });
JOptionPane.showMessageDialog(mainPanel, messagePane, "About", JOptionPane.INFORMATION_MESSAGE); JOptionPane.showMessageDialog(this, messagePane, "About", JOptionPane.INFORMATION_MESSAGE);
} catch (IOException e1) { } catch (IOException e1) {
e1.printStackTrace(); e1.printStackTrace();
} }
@ -551,25 +352,6 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
return menuBar; return menuBar;
} }
private class LibraryMouseAdapter extends MouseAdapter {
@Override
public void mousePressed(MouseEvent e) {
int selRow = libraryView.getRowForLocation(e.getX(), e.getY());
if(e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2 && selRow != -1)
try {
Object selectedItem = ((DefaultMutableTreeNode) libraryView.getPathForLocation(e.getX(), e.getY()).getLastPathComponent()).getUserObject();
if (selectedItem != null) {
if (selectedItem instanceof Song) {
addToPlaylist((Song) selectedItem);
} else if (selectedItem instanceof Album) {
((Album) selectedItem).getSongs().forEach(PlayerGUI.this::addToPlaylist);
}
}
} catch (NullPointerException ignored) {
}
}
}
/** /**
* MouseListener implementation for the seekbar. * MouseListener implementation for the seekbar.
* Using this instead of the ChangeListener so that the polling for the current song position * Using this instead of the ChangeListener so that the polling for the current song position
@ -624,12 +406,7 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf
setSongHighlighting(player.getCurrentSong()); // Resume won't function if a different song is selected. setSongHighlighting(player.getCurrentSong()); // Resume won't function if a different song is selected.
} }
else{ else{
if (playList.getRowCount() > 0) { playSong(playlist.getActive());
if (playList.getSelectedRowCount() > 0)
playSong(playlistTableModel.getSong(playList.getSelectedRow()));
else
playSong(playlistTableModel.getFirst());
}
} }
break; break;
case stop: case stop:

View File

@ -0,0 +1,23 @@
package musicplayer;
import com.google.inject.AbstractModule;
import com.google.inject.Singleton;
import musicplayer.db.HibernateDatabase;
import musicplayer.db.IDatabase;
import musicplayer.library.ILibrary;
import musicplayer.library.JTreeLibrary;
import musicplayer.player.GStreamerPlayer;
import musicplayer.player.IPlayer;
import musicplayer.playlist.IPlaylist;
import musicplayer.playlist.JTablePlaylist;
public class SwingUIModule extends AbstractModule {
@Override
protected void configure() {
bind(IPlayer.class).to(GStreamerPlayer.class).in(Singleton.class);
bind(IDatabase.class).to(HibernateDatabase.class).in(Singleton.class);
bind(IPlaylist.class).to(JTablePlaylist.class).in(Singleton.class);
bind(ILibrary.class).to(JTreeLibrary.class).in(Singleton.class);
}
}

View File

@ -1,6 +1,6 @@
package musicplayer.db; package musicplayer.db;
import musicplayer.ConfigManager; import musicplayer.util.ConfigManager;
import musicplayer.model.*; import musicplayer.model.*;
import musicplayer.util.AlbumArtExtractor; import musicplayer.util.AlbumArtExtractor;
import org.hibernate.Criteria; import org.hibernate.Criteria;
@ -18,7 +18,7 @@ import java.util.Properties;
/** /**
* Class for managing connection to Hibernate. * Class for managing connection to Hibernate.
*/ */
class HibernateDatabase implements IDatabase{ public class HibernateDatabase implements IDatabase{
private static SessionFactory sessionFactory; private static SessionFactory sessionFactory;

View File

@ -1,12 +1,10 @@
package musicplayer.db; package musicplayer.db;
import com.google.inject.ImplementedBy;
import musicplayer.model.*; import musicplayer.model.*;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ImplementedBy(HibernateDatabase.class)
public interface IDatabase { public interface IDatabase {
Optional<Album> getOneAlbum(String name); Optional<Album> getOneAlbum(String name);
Optional<Artist> getOneArtist(String name); Optional<Artist> getOneArtist(String name);

View File

@ -0,0 +1,10 @@
package musicplayer.library;
import musicplayer.model.HasSongs;
public interface ILibrary {
void showSongs();
<T extends HasSongs & Comparable<T>> void showGroupedSongs(Class<T> grouping);
void updateLibrary();
void refreshLibrary();
}

View File

@ -0,0 +1,222 @@
package musicplayer.library;
import com.google.inject.Inject;
import musicplayer.util.ConfigManager;
import musicplayer.callbacks.LibraryCallbackInterface;
import musicplayer.db.IDatabase;
import musicplayer.model.Album;
import musicplayer.model.Artist;
import musicplayer.model.HasSongs;
import musicplayer.model.Song;
import musicplayer.playlist.IPlaylist;
import musicplayer.util.LibraryUtils;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeNode;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.nio.file.Path;
import java.util.*;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
public class JTreeLibrary extends JPanel implements ILibrary, LibraryCallbackInterface{
IDatabase database;
IPlaylist playlist;
private static final DefaultMutableTreeNode updatingNode = new DefaultMutableTreeNode();
private final AtomicBoolean libraryUpdating = new AtomicBoolean(false);
private final JComboBox<String> libraryDisplayType = new JComboBox<>();
private final JTree libraryTree = new JTree();
private final Map<String, Runnable> libraryDisplayVariants = createDisplayVariantMap();
/**
* @return Map of display types for the library paired code to populate the library with data in the correct format.
*/
private Map<String, Runnable> createDisplayVariantMap() {
Map<String, Runnable> value = new HashMap<>();
value.put("Song", this::showSongs);
value.put("Album/Song", () -> this.showGroupedSongs(Album.class));
value.put("Artist/Song", () -> this.showGroupedSongs(Artist.class));
return value;
}
@Inject
public JTreeLibrary(IDatabase database, IPlaylist playlist){
this.database = database;
this.playlist = playlist;
this.setLayout(new BorderLayout(0, 0));
libraryTree.setRootVisible(false);
libraryTree.setToggleClickCount(1);
libraryTree.setCellRenderer(new LibraryTreeCellRenderer());
libraryTree.addMouseListener(new LibraryMouseAdapter());
libraryTree.setScrollsOnExpand(false);
final JScrollPane libraryPane = new JScrollPane(libraryTree);
this.add(libraryPane, BorderLayout.CENTER);
libraryDisplayVariants.keySet().forEach(libraryDisplayType::addItem);
libraryDisplayType.setSelectedIndex(ConfigManager.getLastDisplayTypeIndex());
libraryDisplayType.addItemListener(e -> {
if (e.getStateChange() == ItemEvent.SELECTED) {
ConfigManager.setLastDisplayTypeIndex(libraryDisplayType.getSelectedIndex());
refreshLibrary();
}
});
this.add(libraryDisplayType, BorderLayout.NORTH);
}
/**
* Add an item to libraryView.
*
* @param parentNode Node that should be the parent of this node.
* @param node This node.
*/
private void addNodeToTreeModel(DefaultTreeModel model, DefaultMutableTreeNode parentNode, DefaultMutableTreeNode node) {
model.insertNodeInto(node, parentNode, parentNode.getChildCount());
if (parentNode == model.getRoot()) {
model.nodeStructureChanged((TreeNode) model.getRoot());
}
}
private void showEmpty(){
DefaultTreeModel failedModel = new DefaultTreeModel(new DefaultMutableTreeNode());
DefaultMutableTreeNode failedParentNode = (DefaultMutableTreeNode) failedModel.getRoot();
DefaultMutableTreeNode failedNode = new DefaultMutableTreeNode("Library is empty!");
addNodeToTreeModel(failedModel, failedParentNode, failedNode);
libraryTree.setModel(failedModel);
}
@Override
public void showSongs() {
Optional<List<Song>> dbQuery = database.listAllT(Song.class);
dbQuery.ifPresent(x -> {
DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode());
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) model.getRoot();
Collections.sort(x);
x.forEach(y -> addNodeToTreeModel(model, parentNode, new DefaultMutableTreeNode(y)));
libraryTree.setModel(model);
});
if (!dbQuery.isPresent())
showEmpty();
}
@Override
public <T extends HasSongs & Comparable<T>> void showGroupedSongs(Class<T> grouping) {
Optional<List<T>> dbQuery = database.listAllT(grouping);
dbQuery.ifPresent(x -> {
DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode());
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) model.getRoot();
Collections.sort(x);
x.forEach(y -> {
DefaultMutableTreeNode outerNode = new DefaultMutableTreeNode(y);
addNodeToTreeModel(model, parentNode, outerNode);
y.getSongs().forEach(z -> addNodeToTreeModel(model, outerNode, new DefaultMutableTreeNode(z)));
});
libraryTree.setModel(model);
});
if (!dbQuery.isPresent())
showEmpty();
}
@Override
public void updateLibrary() {
if(!libraryUpdating.get()) {
libraryUpdating.set(true);
DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode());
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) model.getRoot();
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("Updating Library...");
addNodeToTreeModel(model, parentNode, newNode);
addNodeToTreeModel(model, parentNode, updatingNode);
libraryTree.setModel(model);
List<Path> dirs = ConfigManager.getLibraryDirectories().stream().map(File::toPath).collect(Collectors.toList());
LibraryUtils.processSongsWithCallback(database, dirs, this);
}
}
@Override
public void refreshLibrary() {
if(!libraryUpdating.get()) { // Don't try to refresh while updating!!
DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode());
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) model.getRoot();
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("Refreshing Library...");
addNodeToTreeModel(model, parentNode, newNode);
libraryTree.setModel(model);
Thread populateThread = new Thread(() -> {
libraryDisplayVariants.get(libraryDisplayType.getSelectedItem().toString()).run();
// If we get here and the model hasn't changed, the library must be empty.
if (libraryTree.getModel() == model) {
DefaultTreeModel failedModel = new DefaultTreeModel(new DefaultMutableTreeNode());
DefaultMutableTreeNode failedParentNode = (DefaultMutableTreeNode) failedModel.getRoot();
DefaultMutableTreeNode failedNode = new DefaultMutableTreeNode("Library is empty!");
addNodeToTreeModel(failedModel, failedParentNode, failedNode);
libraryTree.setModel(failedModel);
}
}, "libraryRefresh");
populateThread.start();
}
}
@Override
public void libraryUpdated(boolean successful) {
libraryUpdating.set(false);
refreshLibrary();
}
@Override
public void currentlyUpdating(String name) {
updatingNode.setUserObject(name);
((DefaultTreeModel)libraryTree.getModel()).nodeChanged(updatingNode);
}
private class LibraryMouseAdapter extends MouseAdapter {
@Override
public void mousePressed(MouseEvent e) {
int selRow = libraryTree.getRowForLocation(e.getX(), e.getY());
if(e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2 && selRow != -1)
try {
Object selectedItem = ((DefaultMutableTreeNode) libraryTree.getPathForLocation(e.getX(), e.getY()).getLastPathComponent()).getUserObject();
if (selectedItem != null) {
if (selectedItem instanceof Song) {
playlist.addSong((Song) selectedItem);
} else if (selectedItem instanceof HasSongs) {
((HasSongs) selectedItem).getSongs().forEach(playlist::addSong);
}
}
} catch (NullPointerException ignored) {
}
}
}
private class LibraryTreeCellRenderer implements TreeCellRenderer {
private final JLabel label;
private final Icon missingIcon = new ImageIcon(JTreeLibrary.class.getClassLoader().getResource("missing.gif"));
LibraryTreeCellRenderer() {
label = new JLabel();
}
@Override
public java.awt.Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
Object o = ((DefaultMutableTreeNode) value).getUserObject();
label.setIcon(null);
if (o != null) {
label.setText(o.toString());
if (o instanceof Album) {
Album album = (Album) o;
label.setIcon(missingIcon);
album.getAlbumArt().ifPresent(x -> label.setIcon(new ImageIcon(x)));
}
}
return label;
}
}
}

View File

@ -2,7 +2,7 @@ package musicplayer.model;
import java.util.Set; import java.util.Set;
public interface HasSongs { public interface HasSongs extends IDBType {
Set<Song> getSongs(); Set<Song> getSongs();
} }

View File

@ -13,7 +13,7 @@ import org.gstreamer.elements.PlayBin2;
import java.io.File; import java.io.File;
import java.util.Optional; import java.util.Optional;
class GStreamerPlayer implements IPlayer{ public class GStreamerPlayer implements IPlayer{
private final PlayBin2 playBin; private final PlayBin2 playBin;

View File

@ -1,12 +1,10 @@
package musicplayer.player; package musicplayer.player;
import com.google.inject.ImplementedBy;
import musicplayer.StartPlayingException; import musicplayer.StartPlayingException;
import musicplayer.model.Song; import musicplayer.model.Song;
import java.util.Optional; import java.util.Optional;
@ImplementedBy(GStreamerPlayer.class)
public interface IPlayer { public interface IPlayer {
void playSong(Optional<Song> inputSong) throws StartPlayingException; void playSong(Optional<Song> inputSong) throws StartPlayingException;
void stop(); void stop();

View File

@ -0,0 +1,24 @@
package musicplayer.playlist;
import musicplayer.model.Song;
import java.util.List;
import java.util.Optional;
public interface IPlaylist {
void addSong(Song song);
Optional<Song> getFirst();
Optional<Song> getNext(Song currentSong);
Optional<Song> getPrevious(Song currentSong);
Optional<Song> get(int index);
Optional<Song> getActive();
List<Song> getSongList();
int getIndex(Song song);
void delete(Song song);
void delete(int index);
void delete(int[] indices);
void deleteAll();
boolean isEmpty();
void setPlayingSong(Song song);
}

View File

@ -0,0 +1,295 @@
package musicplayer.playlist;
import musicplayer.model.Song;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class JTablePlaylist extends JScrollPane implements IPlaylist {
private final PlaylistTableModel playlistTableModel;
private final JTable playList;
public JTablePlaylist(){
playList = new JTable();
playlistTableModel = new PlaylistTableModel();
this.setViewportView(playList);
setupMouse();
playList.setRowSelectionAllowed(true);
playList.setModel(playlistTableModel);
playList.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
playList.getColumnModel().getColumn(0).setPreferredWidth(10);
playList.getColumnModel().getColumn(0).setMaxWidth(10);
playList.getColumnModel().getColumn(1).setPreferredWidth(40);
playList.getColumnModel().getColumn(1).setMaxWidth(40);
}
private void setupMouse(){
JPopupMenu popupMenu = new JPopupMenu();
JMenuItem menuItem = new JMenuItem("Remove");
menuItem.addActionListener((e) -> delete(playList.getSelectedRows()));
popupMenu.add(menuItem);
playList.setComponentPopupMenu(popupMenu);
popupMenu = new JPopupMenu();
menuItem = new JMenuItem("Clear all");
menuItem.addActionListener((e) -> deleteAll());
popupMenu.add(menuItem);
this.setComponentPopupMenu(popupMenu);
this.setViewportView(playList);
}
@Override
public void addSong(Song song) {
playlistTableModel.addSong(song);
playList.revalidate();
}
@Override
public Optional<Song> getFirst() {
return playlistTableModel.getFirst();
}
@Override
public Optional<Song> getNext(Song currentSong) {
return playlistTableModel.nextSong(currentSong);
}
@Override
public Optional<Song> getPrevious(Song currentSong) {
return playlistTableModel.previous(currentSong);
}
@Override
public Optional<Song> get(int index) {
return playlistTableModel.getSong(index);
}
@Override
public Optional<Song> getActive() {
if (playList.getRowCount() > 0) {
if (playList.getSelectedRowCount() > 0){
return get(playList.getSelectedRow());
}
}
return getFirst();
}
@Override
public List<Song> getSongList() {
return playlistTableModel.getSongList();
}
@Override
public int getIndex(Song song) {
return playlistTableModel.getSongIndex(song);
}
@Override
public void delete(Song song) {
playlistTableModel.removeSong(song);
}
@Override
public void delete(int index) {
playlistTableModel.removeSong(index);
}
@Override
public void delete(int[] indices) {
playlistTableModel.removeSong(indices);
}
@Override
public void deleteAll() {
playlistTableModel.removeAll();
}
@Override
public boolean isEmpty() {
return playlistTableModel.isEmpty();
}
@Override
public void setPlayingSong(Song song){
int index = playlistTableModel.getSongIndex(song);
if(index >= 0)
SwingUtilities.invokeLater(() -> playList.setRowSelectionInterval(index, index));
playlistTableModel.setPlayingRow(index);
}
private class PlaylistTableModel extends AbstractTableModel {
private List<Song> songList = new ArrayList<>();
private int playingRow = -1; // -1 means no song is currently playing.
public PlaylistTableModel(){ }
@Override
public int getRowCount() {
return songList.size();
}
@Override
public int getColumnCount() {
return 5;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
Object o = "?";
Song song = songList.get(rowIndex);
switch (columnIndex){
case 0: o = (playingRow == rowIndex) ? "" : " "; break;
case 1: o = song.getTrackNumber(); break;
case 2: o = song.getTitle(); break;
case 3: o = song.getArtist(); break;
case 4: o = song.getAlbum(); break;
}
return o;
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return Song.class;
}
@Override
public String getColumnName(int column) {
String name = "";
switch (column){
case 0 : name = " "; break;
case 1: name = "Track"; break;
case 2: name = "Title"; break;
case 3: name = "Artist"; break;
case 4: name = "Album"; break;
}
return name;
}
/**
* Add a song to the current playlist.
* @param song Song to add to the playlist.
*/
public void addSong(Song song){
songList.add(song);
fireTableDataChanged();
}
/**
* @return The first song in the playlist.
*/
public Optional<Song> getFirst(){
return songList.size() > 0 ? Optional.of(songList.get(0)) : Optional.<Song>empty();
}
/**
* Get the next song in the playlist. (Wraps around)
* @param song The current song.
* @return The song after the current song.
*/
public Optional<Song> nextSong(Song song){
int index = songList.indexOf(song) + 1;
if(index >= songList.size()) index = 0;
return songList.size() > 0 ? Optional.of(songList.get(index)) : Optional.empty();
}
/**
* Get the previous song in the playlist. (Wraps around)
* @param song The current song.
* @return The song before the current song.
*/
public Optional<Song> previous(Song song){
int index = songList.indexOf(song) - 1;
if(index < 0) index = songList.size() - 1;
return songList.size() > 0 ? Optional.of(songList.get(index)) : Optional.empty();
}
/**
* @param song Song to find in the current playlist.
* @return Index of song in the playlist.
*/
public int getSongIndex(Song song){
return song != null ? songList.indexOf(song) : -1;
}
/**
* Remove the song at index from the playlist.
* @param index List index of the song to be removed.
*/
public void removeSong(int index){
if(songList.size() > index && index >= 0){
songList.remove(index);
fireTableRowsDeleted(index, index);
if(index < playingRow)
playingRow--;
else if(playingRow == index)
playingRow = -1;
}
}
/**
* Remove a number of songs from the playlist.
* @param index Indices of the songs to be removed.
*/
public void removeSong(int[] index){
List<Integer> indices = IntStream.of(index).boxed().collect(Collectors.toList());
Collections.reverse(indices); // Do removals in reverse order to prevent mis-deletion.
indices.forEach(this::removeSong);
}
/**
* Remove a song from the playlist.
* @param song Song to be removed.
*/
public void removeSong(Song song){
songList.remove(song);
fireTableDataChanged();
}
/**
* @return Is the playlist currently empty.
*/
public boolean isEmpty(){
return songList.isEmpty();
}
/**
* Remove all songs from the playlist.
*/
public void removeAll(){
songList.clear();
fireTableDataChanged();
}
/**
* Get a Song from the playlist based on its index.
* @param index Index of the Song to get.
* @return Song object at index.
*/
public Optional<Song> getSong(int index){
return songList.size() > 0 && index >= 0 && index < songList.size() ? Optional.of(songList.get(index)) : Optional.empty();
}
/**
* Set the row at index to have a playing icon.
* @param index Row index to display as playing.
*/
public void setPlayingRow(int index){
playingRow = index;
fireTableDataChanged();
}
/**
* @return Songs currently in the playlist.
*/
public List<Song> getSongList(){
return songList;
}
}
}

View File

@ -1,76 +0,0 @@
package musicplayer.swingmodels;
import javax.swing.*;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class LibraryListModel<T extends File> extends AbstractListModel<T> {
private List<T> libraryFolders = new ArrayList<>();
@Override
public int getSize() {
return libraryFolders.size();
}
@Override
public T getElementAt(int index) {
return libraryFolders.get(index);
}
/**
* Add a directory to the list of directories used for library indexing.
* @param folder Directory to add.
*/
public void addFolder(T folder){
if(folder.exists() && folder.isDirectory()) {
libraryFolders.add(folder);
fireAllContentsChanged();
}
}
/**
* @return List of directories currently used for library indexing.
*/
public List<T> currentFolderList(){
return new ArrayList<>(libraryFolders); // Copy? Don't want modification via here.
}
/**
* Check if this File already exists in libraryFolders.
* @param file File to find.
* @return Does file exist in libraryFolders.
*/
public boolean contains(T file){
return libraryFolders.contains(file);
}
/**
* Set the list contained in libraryFolders.
* @param folderList New directory list.
*/
public void setFolderList(List<T> folderList){
if(folderList != null && folderList.size() > 0)
libraryFolders = new ArrayList<>(folderList);
else
libraryFolders = new ArrayList<>();
fireAllContentsChanged();
}
/**
* Remove a directory from libraryFolders.
* @param file Directory to be removed.
*/
public void removeFile(T file){
libraryFolders.remove(file);
fireAllContentsChanged();
}
/**
* Fire that libraryFolders contents has changed.
*/
private void fireAllContentsChanged(){
fireContentsChanged(this, 0, libraryFolders.size() == 0 ? 0 : libraryFolders.size() - 1);
}
}

View File

@ -1,182 +0,0 @@
package musicplayer.swingmodels;
import musicplayer.model.Song;
import javax.swing.table.AbstractTableModel;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class PlaylistTableModel extends AbstractTableModel {
private List<Song> songList;
private int playingRow = -1; // -1 means no song is currently playing.
public PlaylistTableModel(List<Song> songList){
this.songList = songList;
}
@Override
public int getRowCount() {
return songList.size();
}
@Override
public int getColumnCount() {
return 5;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
Object o = "?";
Song song = songList.get(rowIndex);
switch (columnIndex){
case 0: o = (playingRow == rowIndex) ? "" : " "; break;
case 1: o = song.getTrackNumber(); break;
case 2: o = song.getTitle(); break;
case 3: o = song.getArtist(); break;
case 4: o = song.getAlbum(); break;
}
return o;
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return Song.class;
}
@Override
public String getColumnName(int column) {
String name = "";
switch (column){
case 0 : name = " "; break;
case 1: name = "Track"; break;
case 2: name = "Title"; break;
case 3: name = "Artist"; break;
case 4: name = "Album"; break;
}
return name;
}
/**
* Add a song to the current playlist.
* @param song Song to add to the playlist.
*/
public void addSong(Song song){
songList.add(song);
fireTableDataChanged();
}
/**
* @return The first song in the playlist.
*/
public Optional<Song> getFirst(){
return songList.size() > 0 ? Optional.of(songList.get(0)) : Optional.<Song>empty();
}
/**
* Get the next song in the playlist. (Wraps around)
* @param song The current song.
* @return The song after the current song.
*/
public Optional<Song> nextSong(Song song){
int index = songList.indexOf(song) + 1;
if(index >= songList.size()) index = 0;
return songList.size() > 0 ? Optional.of(songList.get(index)) : Optional.empty();
}
/**
* Get the previous song in the playlist. (Wraps around)
* @param song The current song.
* @return The song before the current song.
*/
public Optional<Song> previous(Song song){
int index = songList.indexOf(song) - 1;
if(index < 0) index = songList.size() - 1;
return songList.size() > 0 ? Optional.of(songList.get(index)) : Optional.empty();
}
/**
* @param song Song to find in the current playlist.
* @return Index of song in the playlist.
*/
public int getSongIndex(Song song){
return songList.indexOf(song);
}
/**
* Remove the song at index from the playlist.
* @param index List index of the song to be removed.
*/
public void removeSong(int index){
if(songList.size() > index && index >= 0){
songList.remove(index);
fireTableRowsDeleted(index, index);
if(index < playingRow)
playingRow--;
else if(playingRow == index)
playingRow = -1;
}
}
/**
* Remove a number of songs from the playlist.
* @param index Indices of the songs to be removed.
*/
public void removeSong(int[] index){
List<Integer> indices = IntStream.of(index).boxed().collect(Collectors.toList());
Collections.reverse(indices); // Do removals in reverse order to prevent mis-deletion.
indices.forEach(this::removeSong);
}
/**
* Remove a song from the playlist.
* @param song Song to be removed.
*/
public void removeSong(Song song){
songList.remove(song);
fireTableDataChanged();
}
/**
* @return Is the playlist currently empty.
*/
public boolean isEmpty(){
return songList.isEmpty();
}
/**
* Remove all songs from the playlist.
*/
public void removeAll(){
songList.clear();
fireTableDataChanged();
}
/**
* Get a Song from the playlist based on its index.
* @param index Index of the Song to get.
* @return Song object at index.
*/
public Optional<Song> getSong(int index){
return songList.size() > 0 && index >= 0 && index < songList.size() ? Optional.of(songList.get(index)) : Optional.empty();
}
/**
* Set the row at index to have a playing icon.
* @param index Row index to display as playing.
*/
public void setPlayingRow(int index){
playingRow = index;
fireTableDataChanged();
}
/**
* @return Songs currently in the playlist.
*/
public List<Song> getSongList(){
return songList;
}
}

View File

@ -1,4 +1,4 @@
package musicplayer; package musicplayer.util;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;