diff --git a/pom.xml b/pom.xml index f2d77fb..a76065b 100644 --- a/pom.xml +++ b/pom.xml @@ -101,6 +101,7 @@ com.google.inject guice 4.0 + compile diff --git a/src/main/java/musicplayer/LibraryConfigGUI.java b/src/main/java/musicplayer/LibraryConfigGUI.java index ef1db02..ba8687b 100644 --- a/src/main/java/musicplayer/LibraryConfigGUI.java +++ b/src/main/java/musicplayer/LibraryConfigGUI.java @@ -1,10 +1,11 @@ package musicplayer; -import musicplayer.swingmodels.LibraryListModel; +import musicplayer.util.ConfigManager; import javax.swing.*; import java.awt.*; import java.io.File; +import java.util.ArrayList; import java.util.List; class LibraryConfigGUI { @@ -73,4 +74,74 @@ class LibraryConfigGUI { files.forEach(listModel::removeFile); saveLibraryList(); } + + private class LibraryListModel extends AbstractListModel { + + private List 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 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 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); + } + } } diff --git a/src/main/java/musicplayer/LibraryTreeCellRenderer.java b/src/main/java/musicplayer/LibraryTreeCellRenderer.java deleted file mode 100644 index 48ffa87..0000000 --- a/src/main/java/musicplayer/LibraryTreeCellRenderer.java +++ /dev/null @@ -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; - } -} diff --git a/src/main/java/musicplayer/PlayerGUI.java b/src/main/java/musicplayer/PlayerGUI.java index 4a0215a..29a51e9 100644 --- a/src/main/java/musicplayer/PlayerGUI.java +++ b/src/main/java/musicplayer/PlayerGUI.java @@ -3,17 +3,14 @@ package musicplayer; import com.google.inject.Guice; import com.google.inject.Inject; import com.google.inject.Injector; -import musicplayer.callbacks.LibraryCallbackInterface; import musicplayer.callbacks.PlayerCallbackInterface; import musicplayer.db.IDatabase; -import musicplayer.model.Album; -import musicplayer.model.Artist; -import musicplayer.model.HasSongs; +import musicplayer.library.ILibrary; import musicplayer.model.Song; import musicplayer.player.IPlayer; -import musicplayer.swingmodels.PlaylistTableModel; +import musicplayer.playlist.IPlaylist; +import musicplayer.util.ConfigManager; import musicplayer.util.Icons; -import musicplayer.util.LibraryUtils; import musicplayer.util.PlaylistUtils; import org.jnativehook.GlobalScreen; import org.jnativehook.NativeHookException; @@ -23,12 +20,7 @@ import org.jnativehook.keyboard.NativeKeyListener; import javax.swing.*; import javax.swing.event.HyperlinkEvent; 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.event.ItemEvent; -import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.io.BufferedReader; @@ -36,35 +28,25 @@ import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.net.URISyntaxException; -import java.nio.file.Path; import java.util.*; -import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; -public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterface { - private JPanel mainPanel; - private final JTree libraryView = new JTree(); - private final JTable playList = new JTable(); +public class PlayerGUI extends JPanel implements PlayerCallbackInterface { + private final IPlaylist playlist; private JSlider seekBar; - private final JComboBox libraryDisplayType = new JComboBox<>(); private final IPlayer player; - private final IDatabase database; - private final PlaylistTableModel playlistTableModel = new PlaylistTableModel(new ArrayList<>()); - private static final DefaultMutableTreeNode updatingNode = new DefaultMutableTreeNode(); - private boolean libraryUpdating = false; + private final ILibrary library; private final AtomicBoolean seeking = new AtomicBoolean(false); - private final Map libraryDisplayVariants = createDisplayVariantMap(); - @Inject - public PlayerGUI(IPlayer player, IDatabase database) { + public PlayerGUI(IPlayer player, IPlaylist playlist, ILibrary library) { this.player = player; - this.database = database; + this.playlist = playlist; + this.library = library; createUI(); - resetTree(); Thread seekBarUpdater = new Thread(() -> { boolean running = true; while (running) { @@ -99,15 +81,15 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf System.out.println(ex.getMessage()); System.out.println(Arrays.toString(ex.getStackTrace())); } - refreshLibrary(); + library.refreshLibrary(); } public static void main(String[] args) { - Injector injector = Guice.createInjector(); + Injector injector = Guice.createInjector(new SwingUIModule()); PlayerGUI playerGUI = injector.getInstance(PlayerGUI.class); JFrame frame = new JFrame(); frame.setMinimumSize(new Dimension(600, 400)); - frame.setContentPane(playerGUI.mainPanel); + frame.setContentPane(playerGUI); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.pack(); 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 > void populateLibraryWithGroupedSongs(List 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 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. * @@ -187,17 +114,14 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf */ @Override public void setSongHighlighting(Song playingSong) { - int index = playlistTableModel.getSongIndex(playingSong); - if(index >= 0) - SwingUtilities.invokeLater(() -> playList.setRowSelectionInterval(index, index)); - playlistTableModel.setPlayingRow(index); + playlist.setPlayingSong(playingSong); } private void playSong(Optional song){ try { player.playSong(song); } 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."); } } @@ -207,7 +131,7 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf */ public void playNextSong() { 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() { 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 public void removeInvalidSong(Song invalidSong) { - playlistTableModel.removeSong(invalidSong); + playlist.delete(invalidSong); } public void playerStopped(){ setSongHighlighting(player.getCurrentSong()); - playlistTableModel.setPlayingRow(-1); - } - - /** - * 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 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 createDisplayVariantMap() { - Map 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; + playlist.setPlayingSong(null); } /* - mainPanel + this +--------------------+ |menuBar | +---------+----------+ @@ -311,79 +175,22 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf +--------------------+ */ private void createUI() { - mainPanel = new JPanel(); - mainPanel.setLayout(new BorderLayout(0, 0)); + this.setLayout(new BorderLayout(0, 0)); final JSplitPane libraryAndPlaylistPane = new JSplitPane(); libraryAndPlaylistPane.setDividerLocation(240); - libraryAndPlaylistPane.setRightComponent(createPlaylistArea()); - mainPanel.add(libraryAndPlaylistPane, BorderLayout.CENTER); - final JPanel libraryAndComboboxPane = new JPanel(); - 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()); + libraryAndPlaylistPane.setRightComponent((JScrollPane)playlist); + this.add(libraryAndPlaylistPane, BorderLayout.CENTER); + libraryAndPlaylistPane.setLeftComponent((JPanel)library); final JPanel controlPane = new JPanel(); controlPane.setLayout(new BorderLayout(0, 0)); - mainPanel.add(controlPane, BorderLayout.SOUTH); + this.add(controlPane, BorderLayout.SOUTH); seekBar = new JSlider(); seekBar.setMinorTickSpacing(1); seekBar.setValue(0); seekBar.addMouseListener(new SeekbarMouseListener()); controlPane.add(seekBar, BorderLayout.NORTH); controlPane.add(createControlButtons(), BorderLayout.SOUTH); - mainPanel.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; + this.add(createMenuBar(), BorderLayout.NORTH); } /** @@ -394,16 +201,10 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf toolBar.setFloatable(false); JPanel controlBar = new JPanel(new GridLayout(1, 5)); JButton playButton = new JButton(); + playButton.setName("playButton"); playButton.setIcon(Icons.playIcon); playButton.setMnemonic('P'); - playButton.addActionListener(e -> { - if (playList.getRowCount() > 0) { - if (playList.getSelectedRowCount() > 0) - playSong(playlistTableModel.getSong(playList.getSelectedRow())); - else - playSong(playlistTableModel.getFirst()); - } - }); + playButton.addActionListener(e -> playSong(playlist.getActive())); controlBar.add(playButton); JButton pauseButton = new JButton(); pauseButton.setIcon(Icons.pauseIcon); @@ -465,12 +266,12 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf // Tools menu JMenu tools = new JMenu("Tools"); JMenuItem menuItem = new JMenuItem("Refresh Library"); - menuItem.addActionListener(e -> refreshLibrary()); + menuItem.addActionListener(e -> library.refreshLibrary()); tools.add(menuItem); menuItem = new JMenuItem("Update Library"); menuItem.setToolTipText("Reindex and refresh library."); - menuItem.addActionListener(e -> updateLibrary()); + menuItem.addActionListener(e -> library.updateLibrary()); tools.add(menuItem); menuItem = new JMenuItem("Library Settings"); @@ -483,7 +284,7 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf JMenu playlistTools = new JMenu("Playlist"); menuItem = new JMenuItem("Clear All"); menuItem.addActionListener((e) -> { - playlistTableModel.removeAll(); + playlist.deleteAll(); player.stop(); }); playlistTools.add(menuItem); @@ -491,17 +292,17 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf FileNameExtensionFilter m3uExtensionFilter = new FileNameExtensionFilter("m3u playlist", "m3u"); menuItem = new JMenuItem("Save Playlist"); menuItem.addActionListener(e -> { - if(!playlistTableModel.isEmpty()) { + if(!playlist.isEmpty()) { JFileChooser fileChooser = new JFileChooser(); fileChooser.setDialogType(JFileChooser.SAVE_DIALOG); fileChooser.setSelectedFile(new File("playlist.m3u")); fileChooser.setFileFilter(m3uExtensionFilter); - if (fileChooser.showSaveDialog(mainPanel) == JFileChooser.APPROVE_OPTION) { + if (fileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) { String filename = fileChooser.getSelectedFile().toString(); if (!filename.endsWith(".m3u")) filename += ".m3u"; try { - PlaylistUtils.writePlaylistToFile(playlistTableModel.getSongList(), new File(filename)); + PlaylistUtils.writePlaylistToFile(playlist.getSongList(), new File(filename)); } catch (IOException e1) { 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(); fileChooser.setDialogType(JFileChooser.OPEN_DIALOG); fileChooser.setFileFilter(m3uExtensionFilter); - if(fileChooser.showOpenDialog(mainPanel) == JFileChooser.APPROVE_OPTION){ - PlaylistUtils.readPlaylistFromFile(fileChooser.getSelectedFile()).stream().forEach(playlistTableModel::addSong); + if(fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION){ + PlaylistUtils.readPlaylistFromFile(fileChooser.getSelectedFile()).stream().forEach(playlist::addSong); } }); playlistTools.add(menuItem); @@ -540,7 +341,7 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf e1.printStackTrace(); } }); - JOptionPane.showMessageDialog(mainPanel, messagePane, "About", JOptionPane.INFORMATION_MESSAGE); + JOptionPane.showMessageDialog(this, messagePane, "About", JOptionPane.INFORMATION_MESSAGE); } catch (IOException e1) { e1.printStackTrace(); } @@ -551,25 +352,6 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf 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. * 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. } else{ - if (playList.getRowCount() > 0) { - if (playList.getSelectedRowCount() > 0) - playSong(playlistTableModel.getSong(playList.getSelectedRow())); - else - playSong(playlistTableModel.getFirst()); - } + playSong(playlist.getActive()); } break; case stop: diff --git a/src/main/java/musicplayer/SwingUIModule.java b/src/main/java/musicplayer/SwingUIModule.java new file mode 100644 index 0000000..9acc9bd --- /dev/null +++ b/src/main/java/musicplayer/SwingUIModule.java @@ -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); + } +} diff --git a/src/main/java/musicplayer/db/HibernateDatabase.java b/src/main/java/musicplayer/db/HibernateDatabase.java index f22eaed..aaab4bd 100644 --- a/src/main/java/musicplayer/db/HibernateDatabase.java +++ b/src/main/java/musicplayer/db/HibernateDatabase.java @@ -1,6 +1,6 @@ package musicplayer.db; -import musicplayer.ConfigManager; +import musicplayer.util.ConfigManager; import musicplayer.model.*; import musicplayer.util.AlbumArtExtractor; import org.hibernate.Criteria; @@ -18,7 +18,7 @@ import java.util.Properties; /** * Class for managing connection to Hibernate. */ -class HibernateDatabase implements IDatabase{ +public class HibernateDatabase implements IDatabase{ private static SessionFactory sessionFactory; diff --git a/src/main/java/musicplayer/db/IDatabase.java b/src/main/java/musicplayer/db/IDatabase.java index 8081ccc..aa8bd9a 100644 --- a/src/main/java/musicplayer/db/IDatabase.java +++ b/src/main/java/musicplayer/db/IDatabase.java @@ -1,12 +1,10 @@ package musicplayer.db; -import com.google.inject.ImplementedBy; import musicplayer.model.*; import java.util.List; import java.util.Optional; -@ImplementedBy(HibernateDatabase.class) public interface IDatabase { Optional getOneAlbum(String name); Optional getOneArtist(String name); diff --git a/src/main/java/musicplayer/library/ILibrary.java b/src/main/java/musicplayer/library/ILibrary.java new file mode 100644 index 0000000..1af778d --- /dev/null +++ b/src/main/java/musicplayer/library/ILibrary.java @@ -0,0 +1,10 @@ +package musicplayer.library; + +import musicplayer.model.HasSongs; + +public interface ILibrary { + void showSongs(); + > void showGroupedSongs(Class grouping); + void updateLibrary(); + void refreshLibrary(); +} diff --git a/src/main/java/musicplayer/library/JTreeLibrary.java b/src/main/java/musicplayer/library/JTreeLibrary.java new file mode 100644 index 0000000..5465ac6 --- /dev/null +++ b/src/main/java/musicplayer/library/JTreeLibrary.java @@ -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 libraryDisplayType = new JComboBox<>(); + private final JTree libraryTree = new JTree(); + private final Map libraryDisplayVariants = createDisplayVariantMap(); + + /** + * @return Map of display types for the library paired code to populate the library with data in the correct format. + */ + private Map createDisplayVariantMap() { + Map 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> 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 > void showGroupedSongs(Class grouping) { + Optional> 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 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; + } + } +} diff --git a/src/main/java/musicplayer/model/HasSongs.java b/src/main/java/musicplayer/model/HasSongs.java index 836bf16..21538ea 100644 --- a/src/main/java/musicplayer/model/HasSongs.java +++ b/src/main/java/musicplayer/model/HasSongs.java @@ -2,7 +2,7 @@ package musicplayer.model; import java.util.Set; -public interface HasSongs { +public interface HasSongs extends IDBType { Set getSongs(); } diff --git a/src/main/java/musicplayer/player/GStreamerPlayer.java b/src/main/java/musicplayer/player/GStreamerPlayer.java index eb4ecaf..484012e 100644 --- a/src/main/java/musicplayer/player/GStreamerPlayer.java +++ b/src/main/java/musicplayer/player/GStreamerPlayer.java @@ -13,7 +13,7 @@ import org.gstreamer.elements.PlayBin2; import java.io.File; import java.util.Optional; -class GStreamerPlayer implements IPlayer{ +public class GStreamerPlayer implements IPlayer{ private final PlayBin2 playBin; diff --git a/src/main/java/musicplayer/player/IPlayer.java b/src/main/java/musicplayer/player/IPlayer.java index 8a934b2..063a430 100644 --- a/src/main/java/musicplayer/player/IPlayer.java +++ b/src/main/java/musicplayer/player/IPlayer.java @@ -1,12 +1,10 @@ package musicplayer.player; -import com.google.inject.ImplementedBy; import musicplayer.StartPlayingException; import musicplayer.model.Song; import java.util.Optional; -@ImplementedBy(GStreamerPlayer.class) public interface IPlayer { void playSong(Optional inputSong) throws StartPlayingException; void stop(); diff --git a/src/main/java/musicplayer/playlist/IPlaylist.java b/src/main/java/musicplayer/playlist/IPlaylist.java new file mode 100644 index 0000000..fbd724b --- /dev/null +++ b/src/main/java/musicplayer/playlist/IPlaylist.java @@ -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 getFirst(); + Optional getNext(Song currentSong); + Optional getPrevious(Song currentSong); + Optional get(int index); + Optional getActive(); + List 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); + +} diff --git a/src/main/java/musicplayer/playlist/JTablePlaylist.java b/src/main/java/musicplayer/playlist/JTablePlaylist.java new file mode 100644 index 0000000..9c1411a --- /dev/null +++ b/src/main/java/musicplayer/playlist/JTablePlaylist.java @@ -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 getFirst() { + return playlistTableModel.getFirst(); + } + + @Override + public Optional getNext(Song currentSong) { + return playlistTableModel.nextSong(currentSong); + } + + @Override + public Optional getPrevious(Song currentSong) { + return playlistTableModel.previous(currentSong); + } + + @Override + public Optional get(int index) { + return playlistTableModel.getSong(index); + } + + @Override + public Optional getActive() { + if (playList.getRowCount() > 0) { + if (playList.getSelectedRowCount() > 0){ + return get(playList.getSelectedRow()); + } + } + return getFirst(); + } + + @Override + public List 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 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 getFirst(){ + return songList.size() > 0 ? Optional.of(songList.get(0)) : Optional.empty(); + } + + /** + * Get the next song in the playlist. (Wraps around) + * @param song The current song. + * @return The song after the current song. + */ + public Optional 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 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 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 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 getSongList(){ + return songList; + } + } +} diff --git a/src/main/java/musicplayer/swingmodels/LibraryListModel.java b/src/main/java/musicplayer/swingmodels/LibraryListModel.java deleted file mode 100644 index 162b442..0000000 --- a/src/main/java/musicplayer/swingmodels/LibraryListModel.java +++ /dev/null @@ -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 extends AbstractListModel { - - private List 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 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 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); - } -} diff --git a/src/main/java/musicplayer/swingmodels/PlaylistTableModel.java b/src/main/java/musicplayer/swingmodels/PlaylistTableModel.java deleted file mode 100644 index dd7867c..0000000 --- a/src/main/java/musicplayer/swingmodels/PlaylistTableModel.java +++ /dev/null @@ -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 songList; - private int playingRow = -1; // -1 means no song is currently playing. - - public PlaylistTableModel(List 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 getFirst(){ - return songList.size() > 0 ? Optional.of(songList.get(0)) : Optional.empty(); - } - - /** - * Get the next song in the playlist. (Wraps around) - * @param song The current song. - * @return The song after the current song. - */ - public Optional 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 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 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 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 getSongList(){ - return songList; - } -} diff --git a/src/main/java/musicplayer/ConfigManager.java b/src/main/java/musicplayer/util/ConfigManager.java similarity index 99% rename from src/main/java/musicplayer/ConfigManager.java rename to src/main/java/musicplayer/util/ConfigManager.java index 6d29cd2..2b8f6f5 100644 --- a/src/main/java/musicplayer/ConfigManager.java +++ b/src/main/java/musicplayer/util/ConfigManager.java @@ -1,4 +1,4 @@ -package musicplayer; +package musicplayer.util; import java.io.File; import java.io.FileInputStream;