Added a queue system.
This commit is contained in:
@@ -0,0 +1,351 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package com.wordpress.tips4java;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.BevelBorder;
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* A JPanel containing default options for scrolling.
|
||||
*
|
||||
* @author Rob Camick, https://tips4java.wordpress.com/2009/12/20/scrollable-panel/
|
||||
*/
|
||||
public class ScrollablePanel extends JPanel implements Scrollable, SwingConstants
|
||||
{
|
||||
public enum ScrollableSizeHint
|
||||
{
|
||||
NONE,
|
||||
FIT,
|
||||
STRETCH;
|
||||
}
|
||||
|
||||
public enum IncrementType
|
||||
{
|
||||
PERCENT,
|
||||
PIXELS;
|
||||
}
|
||||
|
||||
private ScrollableSizeHint scrollableHeight = ScrollableSizeHint.NONE;
|
||||
private ScrollableSizeHint scrollableWidth = ScrollableSizeHint.NONE;
|
||||
|
||||
private IncrementInfo horizontalBlock;
|
||||
private IncrementInfo horizontalUnit;
|
||||
private IncrementInfo verticalBlock;
|
||||
private IncrementInfo verticalUnit;
|
||||
|
||||
/**
|
||||
* Default constructor that uses a FlowLayout
|
||||
*/
|
||||
public ScrollablePanel()
|
||||
{
|
||||
this(new FlowLayout());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constuctor for specifying the LayoutManager of the panel.
|
||||
*
|
||||
* @param layout the LayountManger for the panel
|
||||
*/
|
||||
public ScrollablePanel(LayoutManager layout)
|
||||
{
|
||||
super(layout);
|
||||
|
||||
IncrementInfo block = new IncrementInfo(IncrementType.PERCENT, 100);
|
||||
IncrementInfo unit = new IncrementInfo(IncrementType.PERCENT, 10);
|
||||
|
||||
setScrollableBlockIncrement(HORIZONTAL, block);
|
||||
setScrollableBlockIncrement(VERTICAL, block);
|
||||
setScrollableUnitIncrement(HORIZONTAL, unit);
|
||||
setScrollableUnitIncrement(VERTICAL, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the height ScrollableSizeHint enum
|
||||
*
|
||||
* @return the ScrollableSizeHint enum for the height
|
||||
*/
|
||||
public ScrollableSizeHint getScrollableHeight()
|
||||
{
|
||||
return scrollableHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ScrollableSizeHint enum for the height. The enum is used to
|
||||
* determine the boolean value that is returned by the
|
||||
* getScrollableTracksViewportHeight() method. The valid values are:
|
||||
* <p>
|
||||
* ScrollableSizeHint.NONE - return "false", which causes the height
|
||||
* of the panel to be used when laying out the children
|
||||
* ScrollableSizeHint.FIT - return "true", which causes the height of
|
||||
* the viewport to be used when laying out the children
|
||||
* ScrollableSizeHint.STRETCH - return "true" when the viewport height
|
||||
* is greater than the height of the panel, "false" otherwise.
|
||||
*
|
||||
* @param scrollableHeight as represented by the ScrollableSizeHint enum.
|
||||
*/
|
||||
public void setScrollableHeight(ScrollableSizeHint scrollableHeight)
|
||||
{
|
||||
this.scrollableHeight = scrollableHeight;
|
||||
revalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the width ScrollableSizeHint enum
|
||||
*
|
||||
* @return the ScrollableSizeHint enum for the width
|
||||
*/
|
||||
public ScrollableSizeHint getScrollableWidth()
|
||||
{
|
||||
return scrollableWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ScrollableSizeHint enum for the width. The enum is used to
|
||||
* determine the boolean value that is returned by the
|
||||
* getScrollableTracksViewportWidth() method. The valid values are:
|
||||
* <p>
|
||||
* ScrollableSizeHint.NONE - return "false", which causes the width
|
||||
* of the panel to be used when laying out the children
|
||||
* ScrollableSizeHint.FIT - return "true", which causes the width of
|
||||
* the viewport to be used when laying out the children
|
||||
* ScrollableSizeHint.STRETCH - return "true" when the viewport width
|
||||
* is greater than the width of the panel, "false" otherwise.
|
||||
*
|
||||
* @param scrollableWidth as represented by the ScrollableSizeHint enum.
|
||||
*/
|
||||
public void setScrollableWidth(ScrollableSizeHint scrollableWidth)
|
||||
{
|
||||
this.scrollableWidth = scrollableWidth;
|
||||
revalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the block IncrementInfo for the specified orientation
|
||||
*
|
||||
* @return the block IncrementInfo for the specified orientation
|
||||
*/
|
||||
public IncrementInfo getScrollableBlockIncrement(int orientation)
|
||||
{
|
||||
return orientation == SwingConstants.HORIZONTAL ? horizontalBlock : verticalBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the information needed to do block scrolling.
|
||||
*
|
||||
* @param orientation specify the scrolling orientation. Must be either:
|
||||
* SwingContants.HORIZONTAL or SwingContants.VERTICAL.
|
||||
* @param amount a value used with the IncrementType to determine the
|
||||
* scrollable amount
|
||||
* @paran type specify how the amount parameter in the calculation of
|
||||
* the scrollable amount. Valid values are:
|
||||
* IncrementType.PERCENT - treat the amount as a % of the viewport size
|
||||
* IncrementType.PIXEL - treat the amount as the scrollable amount
|
||||
*/
|
||||
public void setScrollableBlockIncrement(int orientation, IncrementType type, int amount)
|
||||
{
|
||||
IncrementInfo info = new IncrementInfo(type, amount);
|
||||
setScrollableBlockIncrement(orientation, info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the information needed to do block scrolling.
|
||||
*
|
||||
* @param orientation specify the scrolling orientation. Must be either:
|
||||
* SwingContants.HORIZONTAL or SwingContants.VERTICAL.
|
||||
* @param info An IncrementInfo object containing information of how to
|
||||
* calculate the scrollable amount.
|
||||
*/
|
||||
public void setScrollableBlockIncrement(int orientation, IncrementInfo info)
|
||||
{
|
||||
switch (orientation)
|
||||
{
|
||||
case SwingConstants.HORIZONTAL:
|
||||
horizontalBlock = info;
|
||||
break;
|
||||
case SwingConstants.VERTICAL:
|
||||
verticalBlock = info;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid orientation: " + orientation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unit IncrementInfo for the specified orientation
|
||||
*
|
||||
* @return the unit IncrementInfo for the specified orientation
|
||||
*/
|
||||
public IncrementInfo getScrollableUnitIncrement(int orientation)
|
||||
{
|
||||
return orientation == SwingConstants.HORIZONTAL ? horizontalUnit : verticalUnit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the information needed to do unit scrolling.
|
||||
*
|
||||
* @param orientation specify the scrolling orientation. Must be either:
|
||||
* SwingContants.HORIZONTAL or SwingContants.VERTICAL.
|
||||
* @param amount a value used with the IncrementType to determine the
|
||||
* scrollable amount
|
||||
* @paran type specify how the amount parameter in the calculation of
|
||||
* the scrollable amount. Valid values are:
|
||||
* IncrementType.PERCENT - treat the amount as a % of the viewport size
|
||||
* IncrementType.PIXEL - treat the amount as the scrollable amount
|
||||
*/
|
||||
public void setScrollableUnitIncrement(int orientation, IncrementType type, int amount)
|
||||
{
|
||||
IncrementInfo info = new IncrementInfo(type, amount);
|
||||
setScrollableUnitIncrement(orientation, info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the information needed to do unit scrolling.
|
||||
*
|
||||
* @param orientation specify the scrolling orientation. Must be either:
|
||||
* SwingContants.HORIZONTAL or SwingContants.VERTICAL.
|
||||
* @param info An IncrementInfo object containing information of how to
|
||||
* calculate the scrollable amount.
|
||||
*/
|
||||
public void setScrollableUnitIncrement(int orientation, IncrementInfo info)
|
||||
{
|
||||
switch (orientation)
|
||||
{
|
||||
case SwingConstants.HORIZONTAL:
|
||||
horizontalUnit = info;
|
||||
break;
|
||||
case SwingConstants.VERTICAL:
|
||||
verticalUnit = info;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid orientation: " + orientation);
|
||||
}
|
||||
}
|
||||
|
||||
// Implement Scrollable interface
|
||||
|
||||
public Dimension getPreferredScrollableViewportSize()
|
||||
{
|
||||
return getPreferredSize();
|
||||
}
|
||||
|
||||
public int getScrollableUnitIncrement(
|
||||
Rectangle visible, int orientation, int direction)
|
||||
{
|
||||
switch (orientation)
|
||||
{
|
||||
case SwingConstants.HORIZONTAL:
|
||||
return getScrollableIncrement(horizontalUnit, visible.width);
|
||||
case SwingConstants.VERTICAL:
|
||||
return getScrollableIncrement(verticalUnit, visible.height);
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid orientation: " + orientation);
|
||||
}
|
||||
}
|
||||
|
||||
public int getScrollableBlockIncrement(
|
||||
Rectangle visible, int orientation, int direction)
|
||||
{
|
||||
switch (orientation)
|
||||
{
|
||||
case SwingConstants.HORIZONTAL:
|
||||
return getScrollableIncrement(horizontalBlock, visible.width);
|
||||
case SwingConstants.VERTICAL:
|
||||
return getScrollableIncrement(verticalBlock, visible.height);
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid orientation: " + orientation);
|
||||
}
|
||||
}
|
||||
|
||||
protected int getScrollableIncrement(IncrementInfo info, int distance)
|
||||
{
|
||||
if (info.getIncrement() == IncrementType.PIXELS)
|
||||
{
|
||||
return info.getAmount();
|
||||
}
|
||||
else
|
||||
{
|
||||
return distance * info.getAmount() / 100;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getScrollableTracksViewportWidth()
|
||||
{
|
||||
if (scrollableWidth == ScrollableSizeHint.NONE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (scrollableWidth == ScrollableSizeHint.FIT)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// STRETCH sizing, use the greater of the panel or viewport width
|
||||
|
||||
if (getParent() instanceof JViewport)
|
||||
{
|
||||
return (((JViewport) getParent()).getWidth() > getPreferredSize().width);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean getScrollableTracksViewportHeight()
|
||||
{
|
||||
if (scrollableHeight == ScrollableSizeHint.NONE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (scrollableHeight == ScrollableSizeHint.FIT)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// STRETCH sizing, use the greater of the panel or viewport height
|
||||
|
||||
|
||||
if (getParent() instanceof JViewport)
|
||||
{
|
||||
return (((JViewport) getParent()).getHeight() > getPreferredSize().height);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to hold the information required to calculate the scroll amount.
|
||||
*/
|
||||
static class IncrementInfo
|
||||
{
|
||||
private IncrementType type;
|
||||
private int amount;
|
||||
|
||||
public IncrementInfo(IncrementType type, int amount)
|
||||
{
|
||||
this.type = type;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public IncrementType getIncrement()
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
public int getAmount()
|
||||
{
|
||||
return amount;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return
|
||||
"com.ScrollablePanel[" +
|
||||
type + ", " +
|
||||
amount + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -442,9 +442,11 @@ public class LocalSongProvider implements SongProvider<LocalSong>
|
||||
if (getFormats().contains(type))
|
||||
{
|
||||
try
|
||||
{
|
||||
synchronized (db)
|
||||
{
|
||||
state = getDb().createStatement();
|
||||
result = state.executeQuery("SELECT mod FROM local_songs WHERE file='" + this.file.getAbsolutePath() + "';");
|
||||
result = state.executeQuery("SELECT mod FROM local_songs WHERE file='" + this.file.getAbsolutePath().replaceAll("'", "''") + "';");
|
||||
if (result.next())
|
||||
{
|
||||
if (result.getLong(1) >= this.file.lastModified())
|
||||
@@ -458,7 +460,8 @@ public class LocalSongProvider implements SongProvider<LocalSong>
|
||||
}
|
||||
else
|
||||
{
|
||||
state.executeUpdate("UPDATE local_songs SET mod = " + this.file.lastModified() + " WHERE file='" + this.file.getAbsolutePath() + "';");
|
||||
state.executeUpdate("UPDATE local_songs SET mod = " + this.file.lastModified() + " WHERE file='" + this.file.getAbsolutePath().replaceAll("'", "''") + "';");
|
||||
}
|
||||
}
|
||||
}
|
||||
currentFolder = file.getPath();
|
||||
@@ -609,6 +612,11 @@ public class LocalSongProvider implements SongProvider<LocalSong>
|
||||
/*
|
||||
* Update album information.
|
||||
*/
|
||||
synchronized (db)
|
||||
{
|
||||
/*
|
||||
* This part in particular is prone to thread-safety issues.
|
||||
*/
|
||||
result = state.executeQuery("SELECT album FROM local_albums WHERE album='" + albumTitle + "';");
|
||||
if (!result.next())
|
||||
{
|
||||
@@ -639,7 +647,7 @@ public class LocalSongProvider implements SongProvider<LocalSong>
|
||||
/*
|
||||
* Create the song
|
||||
*/
|
||||
result = state.executeQuery("SELECT title FROM local_songs WHERE file='" + file.getAbsolutePath() + "';");
|
||||
result = state.executeQuery("SELECT title FROM local_songs WHERE file='" + file.getAbsolutePath().replaceAll("'", "''") + "';");
|
||||
if (result.next())
|
||||
{
|
||||
logger.debug("Updating song cache for {} ({})", title, file);
|
||||
@@ -695,7 +703,7 @@ public class LocalSongProvider implements SongProvider<LocalSong>
|
||||
sql.append("album=NULL, ");
|
||||
}
|
||||
sql.append("mod=").append(file.lastModified());
|
||||
sql.append(" WHERE file='").append(file.getAbsolutePath()).append("';");
|
||||
sql.append(" WHERE file='").append(file.getAbsolutePath().replaceAll("'", "''")).append("';");
|
||||
state.executeUpdate(sql.toString());
|
||||
}
|
||||
else
|
||||
@@ -706,7 +714,7 @@ public class LocalSongProvider implements SongProvider<LocalSong>
|
||||
StringBuilder values = new StringBuilder("(");
|
||||
|
||||
columns.append("file,");
|
||||
values.append('\'').append(file.getAbsolutePath()).append("',");
|
||||
values.append('\'').append(file.getAbsolutePath().replaceAll("'", "''")).append("',");
|
||||
columns.append("codec,");
|
||||
values.append('\'').append(codec).append("',");
|
||||
columns.append("type,");
|
||||
@@ -751,6 +759,7 @@ public class LocalSongProvider implements SongProvider<LocalSong>
|
||||
sql.append(values);
|
||||
state.executeUpdate(sql.toString());
|
||||
}
|
||||
}
|
||||
|
||||
updatedSongs++;
|
||||
triggerUpdateListeners();
|
||||
@@ -828,6 +837,8 @@ public class LocalSongProvider implements SongProvider<LocalSong>
|
||||
/*
|
||||
* Check if the table exists
|
||||
*/
|
||||
synchronized (db)
|
||||
{
|
||||
state = getDb().createStatement();
|
||||
result = state.executeQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='LOCAL_ALBUMS';");
|
||||
if (!result.next())
|
||||
@@ -906,6 +917,7 @@ public class LocalSongProvider implements SongProvider<LocalSong>
|
||||
}
|
||||
state.close();
|
||||
}
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
logger.error("Could not query SQL database.", e);
|
||||
|
||||
469
interface/src/main/java/edu/regis/universeplayer/data/Queue.java
Normal file
469
interface/src/main/java/edu/regis/universeplayer/data/Queue.java
Normal file
@@ -0,0 +1,469 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.data;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A song queue contains a list of songs that are to play, along with a way to
|
||||
* order them
|
||||
*
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public class Queue extends ArrayList<Song>
|
||||
{
|
||||
private static Queue INSTANCE;
|
||||
|
||||
public static Queue getInstance()
|
||||
{
|
||||
if (INSTANCE == null)
|
||||
{
|
||||
INSTANCE = new Queue();
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private final ArrayList<Song> queueOrder = new ArrayList<>();
|
||||
private int currentIndex;
|
||||
private boolean shuffle;
|
||||
private boolean repeat;
|
||||
private final Random random = new Random();
|
||||
|
||||
private final LinkedList<SongChangeListener> songListeners = new LinkedList<>();
|
||||
private final LinkedList<QueueChangeListener> queueListeners = new LinkedList<>();
|
||||
|
||||
public Queue()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the song scheduled to play.
|
||||
*
|
||||
* @return The currently playing song.
|
||||
*/
|
||||
public Song getCurrentSong()
|
||||
{
|
||||
if (this.currentIndex < 0 || this.currentIndex >= this.queueOrder.size())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return this.queueOrder.get(currentIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the song index scheduled to play.
|
||||
*
|
||||
* @return The currently playing song index.
|
||||
*/
|
||||
public int getCurrentIndex()
|
||||
{
|
||||
return this.indexOf(this.getCurrentSong());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this queue will loop after the end of the song.
|
||||
*
|
||||
* @return True if the queue loops, false otherwise.
|
||||
*/
|
||||
public boolean isRepeating()
|
||||
{
|
||||
return this.repeat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this queue should loop after the last song plays.
|
||||
*
|
||||
* @param repeat - Whether the queue loops.
|
||||
*/
|
||||
public void setRepeat(boolean repeat)
|
||||
{
|
||||
this.repeat = repeat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this queue is in shuffle mode.
|
||||
*
|
||||
* @return True if the queue shuffles, false otherwise.
|
||||
*/
|
||||
public boolean isShuffling()
|
||||
{
|
||||
return this.shuffle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this queue should be shuffled or not.
|
||||
*
|
||||
* @param shuffle - Whether or not the queue shuffles.
|
||||
*/
|
||||
public void setShuffle(boolean shuffle)
|
||||
{
|
||||
this.shuffle = shuffle;
|
||||
this.queueOrder.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips to the next song.
|
||||
*
|
||||
* @return The new song, or null if we finished the list.
|
||||
*/
|
||||
public Song skipNext()
|
||||
{
|
||||
if (++this.currentIndex >= this.queueOrder.size())
|
||||
{
|
||||
if (this.repeat)
|
||||
{
|
||||
this.currentIndex = 0;
|
||||
/*
|
||||
* Reshuffle the queue as needed.
|
||||
*/
|
||||
this.queueOrder.clear();
|
||||
this.getOrder();
|
||||
this.triggerSongChangeListeners();
|
||||
return this.getCurrentSong();
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Make sure we aren't infinitely increasing the current index.
|
||||
*/
|
||||
this.currentIndex = this.queueOrder.size() - 1;
|
||||
this.triggerSongChangeListeners();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
this.triggerSongChangeListeners();
|
||||
return this.getCurrentSong();
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips to the previous song.
|
||||
*
|
||||
* @return The previous song, or the first one if we go underboard.
|
||||
*/
|
||||
public Song skipPrev()
|
||||
{
|
||||
if (--this.currentIndex < 0)
|
||||
{
|
||||
if (this.repeat)
|
||||
{
|
||||
this.currentIndex = this.queueOrder.size() - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.currentIndex = 0;
|
||||
}
|
||||
}
|
||||
this.triggerSongChangeListeners();
|
||||
return this.getCurrentSong();
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips to the song at the provided song index.
|
||||
*
|
||||
* @param index - The new index. This is not relational to the queue.
|
||||
* @return The new song, or null if we finished the list.
|
||||
*/
|
||||
public Song skipToSong(int index)
|
||||
{
|
||||
Song song = this.get(index);
|
||||
this.currentIndex = this.queueOrder.indexOf(song);
|
||||
this.triggerSongChangeListeners();
|
||||
return this.getCurrentSong();
|
||||
}
|
||||
|
||||
private ArrayList<Song> getOrder()
|
||||
{
|
||||
LinkedList<Song> indexesRemaining;
|
||||
if (this.queueOrder.isEmpty())
|
||||
{
|
||||
this.queueOrder.addAll(this);
|
||||
if (this.shuffle)
|
||||
{
|
||||
indexesRemaining = new LinkedList<>(this.queueOrder);
|
||||
this.queueOrder.clear();
|
||||
while (indexesRemaining.size() > 0)
|
||||
{
|
||||
this.queueOrder.add(indexesRemaining.remove(random.nextInt(indexesRemaining.size())));
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.queueOrder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(Song song)
|
||||
{
|
||||
int newIndex;
|
||||
if (super.add(song))
|
||||
{
|
||||
if (this.shuffle)
|
||||
{
|
||||
newIndex = random.nextInt(this.queueOrder.size());
|
||||
if (newIndex <= this.currentIndex)
|
||||
{
|
||||
this.currentIndex++;
|
||||
}
|
||||
this.queueOrder.add(newIndex, song);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.queueOrder.add(song);
|
||||
}
|
||||
this.triggerQueueChangeListeners();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, Song song)
|
||||
{
|
||||
int newIndex;
|
||||
super.add(index, song);
|
||||
if (this.shuffle)
|
||||
{
|
||||
newIndex = this.random.nextInt(this.queueOrder.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
newIndex = index;
|
||||
}
|
||||
if (newIndex <= this.currentIndex)
|
||||
{
|
||||
this.currentIndex++;
|
||||
}
|
||||
this.queueOrder.add(newIndex, song);
|
||||
this.triggerQueueChangeListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Song remove(int index)
|
||||
{
|
||||
Song removed = super.remove(index);
|
||||
if (removed != null)
|
||||
{
|
||||
index = this.queueOrder.indexOf(removed);
|
||||
if (index <= this.currentIndex)
|
||||
{
|
||||
this.currentIndex--;
|
||||
}
|
||||
this.queueOrder.remove(removed);
|
||||
this.triggerQueueChangeListeners();
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o)
|
||||
{
|
||||
int index = this.indexOf(o);
|
||||
if (super.remove(o))
|
||||
{
|
||||
if (index <= this.currentIndex)
|
||||
{
|
||||
this.currentIndex--;
|
||||
}
|
||||
this.queueOrder.remove(o);
|
||||
this.triggerQueueChangeListeners();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear()
|
||||
{
|
||||
super.clear();
|
||||
this.queueOrder.clear();
|
||||
this.currentIndex = 0;
|
||||
this.triggerQueueChangeListeners();
|
||||
this.triggerSongChangeListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends Song> c)
|
||||
{
|
||||
int newIndex;
|
||||
int length = this.size();
|
||||
if (super.addAll(c))
|
||||
{
|
||||
for (Song song : c)
|
||||
{
|
||||
if (this.shuffle)
|
||||
{
|
||||
newIndex = random.nextInt(this.queueOrder.size());
|
||||
if (newIndex <= this.currentIndex)
|
||||
{
|
||||
this.currentIndex++;
|
||||
}
|
||||
this.queueOrder.add(newIndex, song);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.queueOrder.add(song);
|
||||
}
|
||||
}
|
||||
this.triggerQueueChangeListeners();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(int index, Collection<? extends Song> c)
|
||||
{
|
||||
int newIndex;
|
||||
int i = 0;
|
||||
if (super.addAll(index, c))
|
||||
{
|
||||
for (Song song : c)
|
||||
{
|
||||
if (this.shuffle)
|
||||
{
|
||||
newIndex = random.nextInt(this.queueOrder.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
newIndex = index + i++;
|
||||
}
|
||||
if (newIndex <= this.currentIndex)
|
||||
{
|
||||
this.currentIndex++;
|
||||
}
|
||||
this.queueOrder.add(newIndex, song);
|
||||
}
|
||||
this.triggerQueueChangeListeners();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeRange(int fromIndex, int toIndex)
|
||||
{
|
||||
for (int i = fromIndex; i < toIndex; i++)
|
||||
{
|
||||
this.queueOrder.remove(this.get(i));
|
||||
if (i <= this.currentIndex)
|
||||
{
|
||||
this.currentIndex--;
|
||||
}
|
||||
}
|
||||
super.removeRange(fromIndex, toIndex);
|
||||
this.triggerQueueChangeListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c)
|
||||
{
|
||||
boolean success;
|
||||
for (int i = 0, removed = 0; i <= this.currentIndex && i < this.size(); i++)
|
||||
{
|
||||
if (c.contains(this.get(i)))
|
||||
{
|
||||
this.currentIndex--;
|
||||
}
|
||||
}
|
||||
this.queueOrder.removeAll(c);
|
||||
success = super.removeAll(c);
|
||||
this.triggerQueueChangeListeners();
|
||||
return success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c)
|
||||
{
|
||||
boolean success;
|
||||
for (int i = 0, removed = 0; i <= this.currentIndex && i < this.size(); i++)
|
||||
{
|
||||
if (!c.contains(this.get(i)))
|
||||
{
|
||||
this.currentIndex--;
|
||||
}
|
||||
}
|
||||
this.queueOrder.retainAll(c);
|
||||
success = super.retainAll(c);
|
||||
this.triggerQueueChangeListeners();
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener for when the current song changes.
|
||||
*
|
||||
* @param listener - The listener to add.
|
||||
*/
|
||||
public void addSongChangeListener(SongChangeListener listener)
|
||||
{
|
||||
this.songListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a listener for when the current song changes.
|
||||
*
|
||||
* @param listener - The listener to remove.
|
||||
*/
|
||||
public void removeSongChangeListener(SongChangeListener listener)
|
||||
{
|
||||
this.songListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells all listeners that the song changed.
|
||||
*/
|
||||
protected void triggerSongChangeListeners()
|
||||
{
|
||||
this.songListeners.forEach(listener -> listener.onSongChange(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener for when the queue contents changes.
|
||||
*
|
||||
* @param listener - The listener to add.
|
||||
*/
|
||||
public void addQueueChangeListener(QueueChangeListener listener)
|
||||
{
|
||||
this.queueListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a listener for when the queue contents changes.
|
||||
*
|
||||
* @param listener - The listener to remove.
|
||||
*/
|
||||
public void removeQueueChangeListener(QueueChangeListener listener)
|
||||
{
|
||||
this.queueListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells all listeners that the queue changed.
|
||||
*/
|
||||
protected void triggerQueueChangeListeners()
|
||||
{
|
||||
this.queueListeners.forEach(listener -> listener.onQueueChange(this));
|
||||
}
|
||||
|
||||
public interface SongChangeListener extends EventListener
|
||||
{
|
||||
/**
|
||||
* Called when the song of a queue changes.
|
||||
*
|
||||
* @param queue - The queue whose song changed.
|
||||
*/
|
||||
void onSongChange(Queue queue);
|
||||
}
|
||||
|
||||
public interface QueueChangeListener extends EventListener
|
||||
{
|
||||
/**
|
||||
* Called when the queue contents change.
|
||||
*
|
||||
* @param queue - The queue that changed.
|
||||
*/
|
||||
void onQueueChange(Queue queue);
|
||||
}
|
||||
}
|
||||
@@ -4,29 +4,37 @@
|
||||
|
||||
package edu.regis.universeplayer.player;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SpringLayout;
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.LineBorder;
|
||||
|
||||
import edu.regis.universeplayer.data.Album;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* This panel will display information on an album.
|
||||
*/
|
||||
public class AlbumInfo extends JPanel
|
||||
public class AlbumInfo extends JButton
|
||||
{
|
||||
private Album album;
|
||||
private JLabel artLabel;
|
||||
private JLabel albumName;
|
||||
private JLabel artists;
|
||||
private JLabel genres;
|
||||
private JLabel year;
|
||||
public Album album;
|
||||
public final JLabel artLabel;
|
||||
public final JLabel albumName;
|
||||
public final JLabel artists;
|
||||
public final JLabel genres;
|
||||
public final JLabel year;
|
||||
|
||||
public AlbumInfo()
|
||||
{
|
||||
this.removeAll();
|
||||
this.setContentAreaFilled(false);
|
||||
this.setBorder(null);
|
||||
SpringLayout infoLayout = new SpringLayout();
|
||||
this.setLayout(infoLayout);
|
||||
this.setFocusable(true);
|
||||
|
||||
this.setModel(new DefaultButtonModel());
|
||||
|
||||
this.artLabel = new JLabel();
|
||||
this.add(this.artLabel);
|
||||
@@ -52,11 +60,38 @@ public class AlbumInfo extends JPanel
|
||||
infoLayout.putConstraint(SpringLayout.WEST, genres, 5, SpringLayout.EAST, artLabel);
|
||||
infoLayout.putConstraint(SpringLayout.NORTH, year, 5, SpringLayout.SOUTH, genres);
|
||||
infoLayout.putConstraint(SpringLayout.WEST, year, 5, SpringLayout.EAST, artLabel);
|
||||
// infoLayout.putConstraint(SpringLayout.EAST, this, 0, SpringLayout.EAST, albumName);
|
||||
infoLayout.putConstraint(SpringLayout.EAST, this, 0, SpringLayout.EAST, artists);
|
||||
// infoLayout.putConstraint(SpringLayout.EAST, this, 0, SpringLayout.EAST, genres);
|
||||
// infoLayout.putConstraint(SpringLayout.EAST, this, 0, SpringLayout.EAST, year);
|
||||
|
||||
int maxLength = -1;
|
||||
JLabel longest = null;
|
||||
for (JLabel label : Arrays.asList(albumName, artists, genres, year))
|
||||
{
|
||||
if (label.getWidth() > maxLength)
|
||||
{
|
||||
longest = label;
|
||||
maxLength = longest.getWidth();
|
||||
}
|
||||
}
|
||||
infoLayout.putConstraint(SpringLayout.EAST, this, 0, SpringLayout.EAST, longest);
|
||||
infoLayout.putConstraint(SpringLayout.SOUTH, this, 5, SpringLayout.EAST, artLabel);
|
||||
|
||||
while (this.getMouseListeners().length > 0)
|
||||
{
|
||||
this.removeMouseListener(this.getMouseListeners()[0]);
|
||||
}
|
||||
this.addFocusListener(new FocusAdapter()
|
||||
{
|
||||
@Override
|
||||
public void focusGained(FocusEvent e)
|
||||
{
|
||||
((AlbumInfo) e.getComponent()).setBorder(new LineBorder(Color.GRAY, 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focusLost(FocusEvent e)
|
||||
{
|
||||
((AlbumInfo) e.getComponent()).setBorder(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public AlbumInfo(Album album)
|
||||
|
||||
@@ -4,14 +4,16 @@
|
||||
|
||||
package edu.regis.universeplayer.player;
|
||||
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.*;
|
||||
import java.awt.event.FocusAdapter;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.*;
|
||||
|
||||
import com.wordpress.tips4java.ScrollablePanel;
|
||||
import edu.regis.universeplayer.ClickListener;
|
||||
import edu.regis.universeplayer.data.Album;
|
||||
import edu.regis.universeplayer.data.CollectionType;
|
||||
@@ -24,7 +26,7 @@ import edu.regis.universeplayer.data.SongProvider;
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public class CollectionList extends JPanel
|
||||
public class CollectionList extends ScrollablePanel
|
||||
{
|
||||
/**
|
||||
* The type of collections being displayed.
|
||||
@@ -33,7 +35,7 @@ public class CollectionList extends JPanel
|
||||
/**
|
||||
* A link between the JLabel and the object they point towards.
|
||||
*/
|
||||
private Map<JLabel, Object> labelMap = new HashMap<>();
|
||||
private Map<JButton, Object> labelMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* A list of all things interested in knowing when we click a collection.
|
||||
@@ -49,6 +51,16 @@ public class CollectionList extends JPanel
|
||||
|
||||
FlowLayout layout = new FlowLayout();
|
||||
this.setLayout(layout);
|
||||
this.setFocusCycleRoot(true);
|
||||
// this.setFocusable(true);
|
||||
this.addFocusListener(new FocusAdapter()
|
||||
{
|
||||
@Override
|
||||
public void focusGained(FocusEvent e)
|
||||
{
|
||||
labelMap.keySet().stream().findFirst().ifPresent(JButton::requestFocusInWindow);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,12 +117,13 @@ public class CollectionList extends JPanel
|
||||
{
|
||||
final int ART_SIZE = 128;
|
||||
|
||||
JLabel artistLabel;
|
||||
JButton artistLabel;
|
||||
ImageIcon icon;
|
||||
|
||||
for (String artist : artists)
|
||||
{
|
||||
artistLabel = new JLabel();
|
||||
artistLabel = new JButton();
|
||||
setButtonLook(artistLabel);
|
||||
// TODO - Maybe add some sort of artist image lookup?
|
||||
// if (album.art != null)
|
||||
// {
|
||||
@@ -126,7 +139,7 @@ public class CollectionList extends JPanel
|
||||
artistLabel.setText(artist);
|
||||
artistLabel.setHorizontalTextPosition(JLabel.CENTER);
|
||||
artistLabel.setVerticalTextPosition(JLabel.BOTTOM);
|
||||
artistLabel.addMouseListener((ClickListener) mouseEvent -> {
|
||||
artistLabel.addActionListener(mouseEvent -> {
|
||||
if (album)
|
||||
{
|
||||
this.triggerSongDisplayListeners(SongProvider.INSTANCE
|
||||
@@ -155,12 +168,13 @@ public class CollectionList extends JPanel
|
||||
{
|
||||
final int ART_SIZE = 128;
|
||||
|
||||
JLabel albumLabel;
|
||||
JButton albumLabel;
|
||||
ImageIcon icon;
|
||||
|
||||
for (Album album : albums)
|
||||
{
|
||||
albumLabel = new JLabel();
|
||||
albumLabel = new JButton();
|
||||
setButtonLook(albumLabel);
|
||||
icon = Objects.requireNonNullElseGet(album.art, () -> new ImageIcon(this.getClass()
|
||||
.getResource("/gui/icons/defaultart.png"), "Default"));
|
||||
icon.setImage(icon.getImage().getScaledInstance(ART_SIZE, ART_SIZE, 0));
|
||||
@@ -168,7 +182,7 @@ public class CollectionList extends JPanel
|
||||
albumLabel.setText(album.name);
|
||||
albumLabel.setHorizontalTextPosition(JLabel.CENTER);
|
||||
albumLabel.setVerticalTextPosition(JLabel.BOTTOM);
|
||||
albumLabel.addMouseListener((ClickListener) mouseEvent -> this.triggerSongDisplayListeners(new ArrayList<>(SongProvider.INSTANCE
|
||||
albumLabel.addActionListener(mouseEvent -> this.triggerSongDisplayListeners(new ArrayList<>(SongProvider.INSTANCE
|
||||
.getSongsFromAlbum(album))));
|
||||
this.add(albumLabel);
|
||||
this.labelMap.put(albumLabel, album);
|
||||
@@ -184,12 +198,13 @@ public class CollectionList extends JPanel
|
||||
{
|
||||
// final int ART_SIZE = 128;
|
||||
|
||||
JLabel genreLabel;
|
||||
JButton genreLabel;
|
||||
// ImageIcon icon;
|
||||
|
||||
for (String genre : genres)
|
||||
{
|
||||
genreLabel = new JLabel();
|
||||
genreLabel = new JButton();
|
||||
setButtonLook(genreLabel);
|
||||
// TODO - Maybe add some sort of artist image lookup?
|
||||
// if (album.art != null)
|
||||
// {
|
||||
@@ -205,7 +220,7 @@ public class CollectionList extends JPanel
|
||||
genreLabel.setText(genre);
|
||||
genreLabel.setHorizontalTextPosition(JLabel.CENTER);
|
||||
genreLabel.setVerticalTextPosition(JLabel.BOTTOM);
|
||||
genreLabel.addMouseListener((ClickListener) mouseEvent -> this.triggerSongDisplayListeners(SongProvider.INSTANCE
|
||||
genreLabel.addActionListener(mouseEvent -> this.triggerSongDisplayListeners(SongProvider.INSTANCE
|
||||
.getAlbumsFromGenre(genre).stream()
|
||||
.flatMap(album2 -> SongProvider.INSTANCE.getSongsFromAlbum(album2)
|
||||
.stream())
|
||||
@@ -224,12 +239,13 @@ public class CollectionList extends JPanel
|
||||
{
|
||||
// final int ART_SIZE = 128;
|
||||
|
||||
JLabel yearLabel;
|
||||
JButton yearLabel;
|
||||
// ImageIcon icon;
|
||||
|
||||
for (Integer year : years)
|
||||
{
|
||||
yearLabel = new JLabel();
|
||||
yearLabel = new JButton();
|
||||
setButtonLook(yearLabel);
|
||||
// TODO - Maybe add some sort of artist image lookup?
|
||||
// if (album.art != null)
|
||||
// {
|
||||
@@ -245,7 +261,7 @@ public class CollectionList extends JPanel
|
||||
yearLabel.setText(year.toString());
|
||||
yearLabel.setHorizontalTextPosition(JLabel.CENTER);
|
||||
yearLabel.setVerticalTextPosition(JLabel.BOTTOM);
|
||||
yearLabel.addMouseListener((ClickListener) mouseEvent -> this.triggerSongDisplayListeners(SongProvider.INSTANCE
|
||||
yearLabel.addActionListener(mouseEvent -> this.triggerSongDisplayListeners(SongProvider.INSTANCE
|
||||
.getAlbumsFromYear(year).stream()
|
||||
.flatMap(album2 -> SongProvider.INSTANCE.getSongsFromAlbum(album2)
|
||||
.stream())
|
||||
@@ -255,6 +271,15 @@ public class CollectionList extends JPanel
|
||||
}
|
||||
}
|
||||
|
||||
private void setButtonLook(JButton button)
|
||||
{
|
||||
button.setFocusPainted(true);
|
||||
button.setMargin(new Insets(0, 0, 0, 0));
|
||||
button.setContentAreaFilled(false);
|
||||
button.setBorderPainted(false);
|
||||
button.setOpaque(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener for when the displayed songs should change.
|
||||
*
|
||||
|
||||
@@ -4,14 +4,15 @@
|
||||
|
||||
package edu.regis.universeplayer.player;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.LineBorder;
|
||||
import javax.swing.plaf.LabelUI;
|
||||
|
||||
import edu.regis.universeplayer.ClickListener;
|
||||
import edu.regis.universeplayer.data.CollectionType;
|
||||
@@ -36,37 +37,85 @@ public class Collections extends JPanel
|
||||
*/
|
||||
public Collections()
|
||||
{
|
||||
JLabel label;
|
||||
JButton defaultLabel;
|
||||
JButton label;
|
||||
BoxLayout layout = new BoxLayout(this, BoxLayout.Y_AXIS);
|
||||
this.setLayout(layout);
|
||||
this.setFocusable(true);
|
||||
this.setFocusCycleRoot(true);
|
||||
|
||||
this.add(label = new JLabel("All Songs"));
|
||||
label.setForeground(Color.BLUE);
|
||||
label.addMouseListener((ClickListener) mouseEvent -> this
|
||||
this.add(defaultLabel = label = this.createButton("All Songs"));
|
||||
label.setMnemonic('A');
|
||||
label.addActionListener(mouseEvent -> this
|
||||
.triggerSongDisplayListeners(new ArrayList<>(SongProvider.INSTANCE.getSongs())));
|
||||
this.add(label = new JLabel("Artists"));
|
||||
label.setForeground(Color.BLUE);
|
||||
label.addMouseListener((ClickListener) mouseEvent -> this
|
||||
this.add(label = this.createButton("Artists"));
|
||||
label.setMnemonic('T');
|
||||
label.addActionListener(mouseEvent -> this
|
||||
.triggerCollectionDisplayListeners(CollectionType.albumArtist, SongProvider.INSTANCE
|
||||
.getAlbumArtists()));
|
||||
this.add(label = new JLabel("Albums"));
|
||||
label.setForeground(Color.BLUE);
|
||||
label.addMouseListener((ClickListener) mouseEvent -> this
|
||||
this.add(label = this.createButton("Albums"));
|
||||
label.setMnemonic('B');
|
||||
label.addActionListener(mouseEvent -> this
|
||||
.triggerCollectionDisplayListeners(CollectionType.album, SongProvider.INSTANCE
|
||||
.getAlbums()));
|
||||
this.add(label = new JLabel("Genres"));
|
||||
label.setForeground(Color.BLUE);
|
||||
label.addMouseListener((ClickListener) mouseEvent -> this
|
||||
this.add(label = this.createButton("Genres"));
|
||||
label.setMnemonic('G');
|
||||
label.addActionListener(mouseEvent -> this
|
||||
.triggerCollectionDisplayListeners(CollectionType.genre, SongProvider.INSTANCE
|
||||
.getGenres()));
|
||||
this.add(label = new JLabel("Years"));
|
||||
label.setForeground(Color.BLUE);
|
||||
label.addMouseListener((ClickListener) mouseEvent -> this
|
||||
this.add(label = this.createButton("Years"));
|
||||
label.setMnemonic('Y');
|
||||
label.addActionListener(mouseEvent -> this
|
||||
.triggerCollectionDisplayListeners(CollectionType.year, SongProvider.INSTANCE
|
||||
.getYears()));
|
||||
this.add(new JLabel("\u23AF\u23AF\u23AF\u23AF\u23AF\u23AF"));
|
||||
this.add(label = new JLabel("Playlists"));
|
||||
this.add(new JLabel("\u23AF".repeat(6)));
|
||||
this.add(label = this.createButton("Playlists"));
|
||||
label.setMnemonic('P');
|
||||
|
||||
addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e)
|
||||
{
|
||||
defaultLabel.requestFocusInWindow();
|
||||
}
|
||||
});
|
||||
addFocusListener(new FocusAdapter()
|
||||
{
|
||||
@Override
|
||||
public void focusGained(FocusEvent e)
|
||||
{
|
||||
int index = -1;
|
||||
Component[] children = ((Container) e.getComponent()).getComponents();
|
||||
for (int i = 0, l = children.length; index == -1 && i < l; i++)
|
||||
{
|
||||
if (children[i] == e.getOppositeComponent())
|
||||
{
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Only auto-switch focus if the previous component was not from
|
||||
* within.
|
||||
*/
|
||||
if (index == -1)
|
||||
{
|
||||
defaultLabel.requestFocusInWindow();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private JButton createButton(String text)
|
||||
{
|
||||
JButton label = new JButton(text);
|
||||
label.setFocusPainted(true);
|
||||
label.setMargin(new Insets(0, 0, 0, 0));
|
||||
label.setContentAreaFilled(false);
|
||||
label.setBorderPainted(false);
|
||||
label.setOpaque(false);
|
||||
label.setForeground(Color.BLUE);
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.player;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
public class FocusManager extends DefaultKeyboardFocusManager
|
||||
{
|
||||
public FocusManager()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -6,25 +6,21 @@ package edu.regis.universeplayer.player;
|
||||
|
||||
import edu.regis.universeplayer.Player;
|
||||
import edu.regis.universeplayer.browser.Browser;
|
||||
import edu.regis.universeplayer.data.CollectionType;
|
||||
import edu.regis.universeplayer.data.Song;
|
||||
import edu.regis.universeplayer.data.SongProvider;
|
||||
import edu.regis.universeplayer.data.UpdateListener;
|
||||
import edu.regis.universeplayer.data.Queue;
|
||||
import edu.regis.universeplayer.data.*;
|
||||
import net.harawata.appdirs.AppDirsFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.ComponentListener;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.event.WindowListener;
|
||||
import java.awt.event.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/**
|
||||
@@ -33,7 +29,7 @@ import java.util.concurrent.Future;
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public class Interface extends JFrame implements SongDisplayListener, ComponentListener, WindowListener, PlaybackCommandListener, UpdateListener
|
||||
public class Interface extends JFrame implements SongDisplayListener, ComponentListener, WindowListener, PlaybackCommandListener, UpdateListener, FocusListener
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(Interface.class);
|
||||
|
||||
@@ -44,6 +40,10 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
* A reference to the panel containing links to different collection views.
|
||||
*/
|
||||
private final Collections collectionTypes;
|
||||
/**
|
||||
* A reference to the panel showing the queue.
|
||||
*/
|
||||
private final QueueList queueList;
|
||||
/**
|
||||
* A reference to the central view showing a list of songs.
|
||||
*/
|
||||
@@ -81,9 +81,11 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
*/
|
||||
logger.info("Starting application");
|
||||
inter = new Interface();
|
||||
inter.pack();
|
||||
inter.setSize(700, 500);
|
||||
SongProvider.INSTANCE.addUpdateListener(inter);
|
||||
inter.setVisible(true);
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
inter.players.add(browser = Browser.createBrowser());
|
||||
@@ -118,8 +120,6 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
logger.error("Could not open browser background", e);
|
||||
JOptionPane.showMessageDialog(inter, e, "Could not open browser background", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
SongProvider.INSTANCE.addUpdateListener(inter);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
@@ -179,14 +179,27 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
|
||||
this.setTitle("Universal Music Player");
|
||||
this.getContentPane().setLayout(new BorderLayout());
|
||||
this.setFocusable(true);
|
||||
this.setFocusCycleRoot(true);
|
||||
|
||||
this.getContentPane()
|
||||
.add(this.collectionTypes = new Collections(), BorderLayout.LINE_START);
|
||||
this.collectionTypes.addFocusListener(this);
|
||||
this.collectionTypes.addSongDisplayListener(this);
|
||||
|
||||
this.controls = new PlayerControls();
|
||||
this.controls.addFocusListener(this);
|
||||
controls.addCommandListener(this);
|
||||
this.getContentPane().add(controls, BorderLayout.PAGE_END);
|
||||
|
||||
this.queueList = new QueueList();
|
||||
this.queueList.addFocusListener(this);
|
||||
Queue.getInstance().addQueueChangeListener(this.queueList);
|
||||
Queue.getInstance().addSongChangeListener(this.queueList);
|
||||
this.getContentPane().add(queueList, BorderLayout.LINE_END);
|
||||
|
||||
this.songList = new SongList();
|
||||
this.songList.addFocusListener(this);
|
||||
this.collectionList = new CollectionList();
|
||||
this.collectionList.addSongDisplayListener(this);
|
||||
|
||||
@@ -196,7 +209,13 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
|
||||
this.addComponentListener(this);
|
||||
this.addWindowListener(this);
|
||||
this.addFocusListener(this);
|
||||
|
||||
((SortingFocusTraversalPolicy) this.getFocusTraversalPolicy()).setImplicitDownCycleTraversal(true);
|
||||
this.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, Set.of(AWTKeyStroke.getAWTKeyStroke(KeyEvent.VK_DOWN, 0), AWTKeyStroke.getAWTKeyStroke(KeyEvent.VK_RIGHT, 0)));
|
||||
this.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, Set.of(AWTKeyStroke.getAWTKeyStroke(KeyEvent.VK_UP, 0), AWTKeyStroke.getAWTKeyStroke(KeyEvent.VK_LEFT, 0)));
|
||||
this.setFocusTraversalKeys(KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYS, Set.of(AWTKeyStroke.getAWTKeyStroke(KeyEvent.VK_TAB, 0)));
|
||||
this.setFocusTraversalKeys(KeyboardFocusManager.DOWN_CYCLE_TRAVERSAL_KEYS, Set.of(AWTKeyStroke.getAWTKeyStroke(KeyEvent.VK_TAB, KeyEvent.SHIFT_DOWN_MASK)));
|
||||
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
|
||||
this.updateSongs(SongProvider.INSTANCE.getSongs());
|
||||
@@ -207,11 +226,12 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
{
|
||||
this.songList.listAlbums(songs);
|
||||
this.centerView.setViewportView(this.songList);
|
||||
this.songList.setPreferredSize(new Dimension(this.centerView.getViewport()
|
||||
.getExtentSize().width, Integer.MAX_VALUE));
|
||||
// this.songList.setPreferredSize(new Dimension(this.centerView.getViewport()
|
||||
// .getExtentSize().width, Integer.MAX_VALUE));
|
||||
this.songList.setPreferredSize(new Dimension(this.centerView.getViewport()
|
||||
.getExtentSize().width, this.songList
|
||||
.getMinimumSize().height));
|
||||
this.centerView.validate();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -219,11 +239,12 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
{
|
||||
this.collectionList.listCollection(type, collections);
|
||||
this.centerView.setViewportView(this.collectionList);
|
||||
this.collectionList.setPreferredSize(new Dimension(this.centerView.getViewport()
|
||||
.getExtentSize().width, Integer.MAX_VALUE));
|
||||
// this.collectionList.setPreferredSize(new Dimension(this.centerView.getViewport()
|
||||
// .getExtentSize().width, Integer.MAX_VALUE));
|
||||
this.collectionList.setPreferredSize(new Dimension(this.centerView.getViewport()
|
||||
.getExtentSize().width, this.collectionList
|
||||
.getMinimumSize().height));
|
||||
this.centerView.validate();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -309,15 +330,11 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
@Override
|
||||
public void onCommand(PlaybackCommand command, Object data)
|
||||
{
|
||||
Player player;
|
||||
Player player = null;
|
||||
if (this.currentPlayer >= 0 && this.currentPlayer < this.players.size())
|
||||
{
|
||||
player = this.players.get(this.currentPlayer);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NullPointerException("No player available");
|
||||
}
|
||||
switch (command)
|
||||
{
|
||||
case PLAY -> {
|
||||
@@ -328,10 +345,8 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
}
|
||||
case PAUSE -> {
|
||||
}
|
||||
case NEXT -> {
|
||||
}
|
||||
case PREVIOUS -> {
|
||||
}
|
||||
case NEXT -> Queue.getInstance().skipNext();
|
||||
case PREVIOUS -> Queue.getInstance().skipPrev();
|
||||
case SEEK -> {
|
||||
}
|
||||
}
|
||||
@@ -350,4 +365,85 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
this.updateSongs(SongProvider.INSTANCE.getSongs());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focusGained(FocusEvent e)
|
||||
{
|
||||
Component parent;
|
||||
if (e.getOppositeComponent() == null)
|
||||
{
|
||||
collectionTypes.requestFocusInWindow();
|
||||
return;
|
||||
}
|
||||
parent = e.getOppositeComponent().getParent();
|
||||
if (e.getComponent() == this)
|
||||
{
|
||||
if (parent == collectionTypes)
|
||||
{
|
||||
centerView.getViewport().getView().requestFocusInWindow();
|
||||
}
|
||||
else if (parent == songList || parent == collectionList)
|
||||
{
|
||||
queueList.requestFocusInWindow();
|
||||
}
|
||||
else if (parent == queueList.songList || parent == queueList.header)
|
||||
{
|
||||
controls.requestFocusInWindow();
|
||||
}
|
||||
else if (parent == controls)
|
||||
{
|
||||
collectionTypes.requestFocusInWindow();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.warn("Unrecognized parent {}", parent);
|
||||
collectionTypes.requestFocusInWindow();
|
||||
}
|
||||
}
|
||||
/*
|
||||
* We are transfering backwards from an inner element
|
||||
*/
|
||||
else
|
||||
{
|
||||
int index = -1;
|
||||
Component[] children = ((Container) e.getComponent()).getComponents();
|
||||
for (int i = 0, l = children.length; index == -1 && i < l; i++)
|
||||
{
|
||||
if (children[i] == e.getOppositeComponent())
|
||||
{
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
if (index != -1)
|
||||
{
|
||||
parent = e.getComponent();
|
||||
if (parent == collectionTypes)
|
||||
{
|
||||
centerView.getViewport().getView().requestFocusInWindow();
|
||||
}
|
||||
else if (parent == songList || parent == collectionList)
|
||||
{
|
||||
queueList.requestFocusInWindow();
|
||||
}
|
||||
else if (parent == controls)
|
||||
{
|
||||
controls.requestFocusInWindow();
|
||||
}
|
||||
else if (parent == queueList.songList || parent == queueList.header)
|
||||
{
|
||||
collectionTypes.requestFocusInWindow();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.warn("Unrecognized parent {}", parent);
|
||||
collectionTypes.requestFocusInWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focusLost(FocusEvent e)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,9 @@
|
||||
|
||||
package edu.regis.universeplayer.player;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.*;
|
||||
import java.awt.event.FocusAdapter;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import javax.swing.*;
|
||||
@@ -40,6 +41,8 @@ public class PlayerControls extends JPanel
|
||||
|
||||
SpringLayout layout = new SpringLayout();
|
||||
this.setLayout(layout);
|
||||
this.setFocusable(true);
|
||||
this.setFocusCycleRoot(false);
|
||||
|
||||
buttonLayout = new FlowLayout();
|
||||
buttonCont = new JPanel(buttonLayout);
|
||||
@@ -83,6 +86,31 @@ public class PlayerControls extends JPanel
|
||||
this.setUpdateProgress(0, 0, null);
|
||||
this.add(this.updateProgress);
|
||||
|
||||
this.addFocusListener(new FocusAdapter()
|
||||
{
|
||||
@Override
|
||||
public void focusGained(FocusEvent e)
|
||||
{
|
||||
int index = -1;
|
||||
Component[] children = ((Container) e.getComponent()).getComponents();
|
||||
for (int i = 0, l = children.length; index == -1 && i < l; i++)
|
||||
{
|
||||
if (children[i] == e.getOppositeComponent())
|
||||
{
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Only auto-switch focus if the previous component was not from
|
||||
* within.
|
||||
*/
|
||||
if (index == -1)
|
||||
{
|
||||
prevButton.requestFocusInWindow();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
layout.putConstraint(SpringLayout.NORTH, buttonCont, 0, SpringLayout.NORTH, this);
|
||||
layout.putConstraint(SpringLayout.WEST, buttonCont, 0, SpringLayout.WEST, this);
|
||||
layout.putConstraint(SpringLayout.EAST, buttonCont, 0, SpringLayout.EAST, this);
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.player;
|
||||
|
||||
import com.wordpress.tips4java.ScrollablePanel;
|
||||
import edu.regis.universeplayer.ClickListener;
|
||||
import edu.regis.universeplayer.data.Queue;
|
||||
import edu.regis.universeplayer.data.Song;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.BevelBorder;
|
||||
import javax.swing.border.LineBorder;
|
||||
import java.awt.*;
|
||||
import java.awt.event.FocusAdapter;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.util.Formatter;
|
||||
|
||||
public class QueueList extends JPanel implements Queue.SongChangeListener, Queue.QueueChangeListener
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(QueueList.class);
|
||||
|
||||
private final JScrollPane scroll;
|
||||
final JPanel header;
|
||||
final ScrollablePanel songList;
|
||||
private int currentHighlight = 0;
|
||||
|
||||
private JButton clearButton;
|
||||
|
||||
public QueueList()
|
||||
{
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
|
||||
this.setLayout(new BorderLayout());
|
||||
this.setFocusable(true);
|
||||
// this.setFocusCycleRoot(true);
|
||||
|
||||
header = new JPanel(new GridBagLayout());
|
||||
// header.setFocusCycleRoot(true);
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.gridx = 0;
|
||||
c.gridy = 0;
|
||||
c.gridwidth = 1;
|
||||
header.add(new JLabel(" ".repeat(20) + "Queue" + " ".repeat(20)), c);
|
||||
this.clearButton = new JButton("Clear");
|
||||
this.clearButton.addActionListener(e -> {
|
||||
Queue.getInstance().clear();
|
||||
});
|
||||
c.gridy = 1;
|
||||
c.gridwidth = 1;
|
||||
header.add(clearButton, c);
|
||||
header.addFocusListener(new FocusAdapter()
|
||||
{
|
||||
@Override
|
||||
public void focusGained(FocusEvent e)
|
||||
{
|
||||
int index = -1;
|
||||
Component[] children = ((Container) e.getComponent()).getComponents();
|
||||
for (int i = 0, l = children.length; index == -1 && i < l; i++)
|
||||
{
|
||||
if (children[i] == e.getOppositeComponent())
|
||||
{
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Only auto-switch focus if the previous component was not from
|
||||
* within.
|
||||
*/
|
||||
if (index == -1)
|
||||
{
|
||||
clearButton.requestFocusInWindow();
|
||||
}
|
||||
}
|
||||
});
|
||||
this.add(header, BorderLayout.NORTH);
|
||||
|
||||
this.songList = new ScrollablePanel(new GridLayout(0, 2));
|
||||
/*
|
||||
* Don't focus on here until we have elements.
|
||||
*/
|
||||
this.songList.setFocusable(false);
|
||||
this.songList.setFocusCycleRoot(true);
|
||||
this.songList.addFocusListener(new FocusAdapter()
|
||||
{
|
||||
@Override
|
||||
public void focusGained(FocusEvent e)
|
||||
{
|
||||
int index = -1;
|
||||
Component[] children = ((Container) e.getComponent()).getComponents();
|
||||
for (int i = 0, l = children.length; index == -1 && i < l; i++)
|
||||
{
|
||||
if (children[i] == e.getOppositeComponent())
|
||||
{
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Only auto-switch focus if the previous component was not from
|
||||
* within.
|
||||
*/
|
||||
if (index == -1)
|
||||
{
|
||||
songList.getComponents()[0].requestFocusInWindow();
|
||||
}
|
||||
else
|
||||
{
|
||||
header.requestFocusInWindow();
|
||||
}
|
||||
}
|
||||
});
|
||||
this.songList.setScrollableWidth(ScrollablePanel.ScrollableSizeHint.FIT);
|
||||
|
||||
this.scroll = new JScrollPane(this.songList);
|
||||
// this.scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
|
||||
// this.scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||
this.add(this.scroll, BorderLayout.CENTER);
|
||||
|
||||
this.addFocusListener(new FocusAdapter()
|
||||
{
|
||||
@Override
|
||||
public void focusGained(FocusEvent e)
|
||||
{
|
||||
int index = -1;
|
||||
Component[] children = ((Container) e.getComponent()).getComponents();
|
||||
for (int i = 0, l = children.length; index == -1 && i < l; i++)
|
||||
{
|
||||
if (children[i] == e.getOppositeComponent())
|
||||
{
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Only auto-switch focus if the previous component was not from
|
||||
* within.
|
||||
*/
|
||||
if (index == -1)
|
||||
{
|
||||
header.requestFocusInWindow();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSongChange(Queue queue)
|
||||
{
|
||||
if (this.currentHighlight >= 0)
|
||||
{
|
||||
this.songList.getComponent(this.currentHighlight * 2).setForeground(Color.BLACK);
|
||||
}
|
||||
this.currentHighlight = queue.getCurrentIndex();
|
||||
if (this.currentHighlight >= 0)
|
||||
{
|
||||
this.songList.getComponent(this.currentHighlight * 2).setForeground(Color.BLUE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueueChange(Queue queue)
|
||||
{
|
||||
Formatter dateForm;
|
||||
JButton songLabel;
|
||||
JLabel durationLabel;
|
||||
GridBagConstraints c;
|
||||
this.songList.removeAll();
|
||||
int i = 0;
|
||||
for (Song song : queue)
|
||||
{
|
||||
songLabel = new JButton(song.title);
|
||||
songLabel.setFocusPainted(true);
|
||||
songLabel.setMargin(new Insets(0, 0, 0, 0));
|
||||
songLabel.setContentAreaFilled(false);
|
||||
songLabel.setBorderPainted(false);
|
||||
songLabel.setOpaque(false);
|
||||
songLabel.setHorizontalAlignment(JLabel.LEFT);
|
||||
int songIndex = i;
|
||||
songLabel.addMouseListener((ClickListener) e -> {
|
||||
if (e.getClickCount() == 2)
|
||||
{
|
||||
queue.skipToSong(songIndex);
|
||||
}
|
||||
});
|
||||
dateForm = new Formatter();
|
||||
durationLabel = new JLabel(dateForm.format("%1$tM:%1$tS", song.duration).toString());
|
||||
durationLabel.setHorizontalAlignment(JLabel.RIGHT);
|
||||
durationLabel.addMouseListener((ClickListener) e -> {
|
||||
if (e.getClickCount() == 2)
|
||||
{
|
||||
queue.skipToSong(songIndex);
|
||||
}
|
||||
});
|
||||
dateForm.close();
|
||||
|
||||
c = new GridBagConstraints();
|
||||
c.gridx = 0;
|
||||
c.gridy = i;
|
||||
c.anchor = GridBagConstraints.WEST;
|
||||
c.insets = new Insets(0, 0, 0, 5);
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
this.songList.add(songLabel);
|
||||
|
||||
c = new GridBagConstraints();
|
||||
c.gridx = 1;
|
||||
c.gridy = i;
|
||||
c.anchor = GridBagConstraints.EAST;
|
||||
c.insets = new Insets(0, 0, 0, 0);
|
||||
this.songList.add(durationLabel);
|
||||
|
||||
songLabel.setForeground(Color.BLACK);
|
||||
i++;
|
||||
}
|
||||
this.songList.setFocusable(queue.size() > 0);
|
||||
this.currentHighlight = queue.getCurrentIndex();
|
||||
if (this.currentHighlight >= 0)
|
||||
{
|
||||
this.songList.getComponent(queue.getCurrentIndex() * 2).setForeground(Color.BLUE);
|
||||
}
|
||||
this.validate();
|
||||
}
|
||||
}
|
||||
@@ -4,29 +4,25 @@
|
||||
|
||||
package edu.regis.universeplayer.player;
|
||||
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.Insets;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import com.wordpress.tips4java.ScrollablePanel;
|
||||
import edu.regis.universeplayer.ClickListener;
|
||||
import edu.regis.universeplayer.data.Queue;
|
||||
import edu.regis.universeplayer.data.*;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import edu.regis.universeplayer.data.Album;
|
||||
import edu.regis.universeplayer.data.Song;
|
||||
import edu.regis.universeplayer.data.SongProvider;
|
||||
|
||||
/**
|
||||
* This panel will list all the songs that are to be currently displayed.
|
||||
*/
|
||||
public class SongList extends JPanel
|
||||
public class SongList extends ScrollablePanel
|
||||
{
|
||||
private Map<Album, List<Song>> currentAlbums;
|
||||
private Map<JLabel, Song> labelMap = new HashMap<>();
|
||||
private Map<JComponent, Song> labelMap = new HashMap<>();
|
||||
private Map<AlbumInfo, Album> artMap = new HashMap<>();
|
||||
|
||||
public SongList()
|
||||
@@ -34,10 +30,37 @@ public class SongList extends JPanel
|
||||
super();
|
||||
|
||||
GridBagLayout layout = new GridBagLayout();
|
||||
this.setFocusTraversalPolicyProvider(true);
|
||||
this.setLayout(layout);
|
||||
|
||||
SongProvider<?> provider = SongProvider.INSTANCE;
|
||||
this.listAlbums(provider.getSongs());
|
||||
|
||||
this.setScrollableWidth(ScrollableSizeHint.FIT);
|
||||
this.setFocusTraversalPolicy(new SongListPolicy());
|
||||
this.addFocusListener(new FocusAdapter()
|
||||
{
|
||||
@Override
|
||||
public void focusGained(FocusEvent e)
|
||||
{
|
||||
int index = -1;
|
||||
Component[] children = ((Container) e.getComponent()).getComponents();
|
||||
for (int i = 0, l = children.length; index == -1 && i < l; i++)
|
||||
{
|
||||
if (children[i] == e.getOppositeComponent())
|
||||
{
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
if (index == -1)
|
||||
{
|
||||
artMap.keySet().stream().findFirst().ifPresent(albumInfo -> {
|
||||
albumInfo.requestFocusInWindow();
|
||||
scrollRectToVisible(albumInfo.getBounds());
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,14 +73,12 @@ public class SongList extends JPanel
|
||||
Map<Album, List<Song>> albums = songs.stream().sorted().collect(Collectors
|
||||
.groupingBy(song -> song.album, Collectors
|
||||
.mapping(song -> (Song) song, Collectors.toList())));
|
||||
GridBagConstraints c = new GridBagConstraints(), c2 = new GridBagConstraints();
|
||||
c.fill = GridBagConstraints.NONE;
|
||||
c.insets = new Insets(0, 0, 20, 0);
|
||||
int i = 0, j;
|
||||
AlbumInfo albumInfo;
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
int i = 0;
|
||||
List<Song> songCollection;
|
||||
JPanel songList;
|
||||
JLabel songNum, songTitle;
|
||||
JLabel songNum;
|
||||
JButton songTitle;
|
||||
|
||||
this.labelMap.clear();
|
||||
this.artMap.clear();
|
||||
@@ -68,42 +89,275 @@ public class SongList extends JPanel
|
||||
{
|
||||
songCollection = albums.get(album);
|
||||
|
||||
albumInfo = new AlbumInfo(album);
|
||||
AlbumInfo albumInfo = new AlbumInfo(album);
|
||||
c.gridx = 0;
|
||||
c.gridy = i;
|
||||
c.anchor = GridBagConstraints.WEST;
|
||||
c.gridwidth = 1;
|
||||
c.gridheight = songCollection.size();
|
||||
c.weightx = 0;
|
||||
c.anchor = GridBagConstraints.NORTHWEST;
|
||||
c.insets = new Insets(0, 0, 20, 10);
|
||||
List<Song> finalSongCollection = songCollection;
|
||||
albumInfo.addMouseListener((ClickListener) e -> {
|
||||
if (e.getClickCount() == 2)
|
||||
{
|
||||
Queue.getInstance().addAll(finalSongCollection);
|
||||
}
|
||||
});
|
||||
albumInfo.albumName.addMouseListener((ClickListener) e -> {
|
||||
if (e.getClickCount() == 2)
|
||||
{
|
||||
Container inter = this;
|
||||
do
|
||||
{
|
||||
inter = inter.getParent();
|
||||
}
|
||||
while (!(inter instanceof Interface) && inter.getParent() != null);
|
||||
if (inter instanceof Interface)
|
||||
{
|
||||
((Interface) inter).updateSongs(SongProvider.INSTANCE.getSongsFromAlbum(albumInfo.album));
|
||||
}
|
||||
}
|
||||
});
|
||||
albumInfo.artists.addMouseListener((ClickListener) e -> {
|
||||
if (e.getClickCount() == 2)
|
||||
{
|
||||
Container inter = this;
|
||||
do
|
||||
{
|
||||
inter = inter.getParent();
|
||||
}
|
||||
while (!(inter instanceof Interface) && inter.getParent() != null);
|
||||
if (inter instanceof Interface)
|
||||
{
|
||||
((Interface) inter).updateCollections(CollectionType.album, Arrays.stream(albumInfo.album.artists).flatMap(s -> SongProvider.INSTANCE.getAlbumsFromArtist(s).stream()).collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
});
|
||||
albumInfo.genres.addMouseListener((ClickListener) e -> {
|
||||
if (e.getClickCount() == 2)
|
||||
{
|
||||
Container inter = this;
|
||||
do
|
||||
{
|
||||
inter = inter.getParent();
|
||||
}
|
||||
while (!(inter instanceof Interface) && inter.getParent() != null);
|
||||
if (inter instanceof Interface)
|
||||
{
|
||||
((Interface) inter).updateCollections(CollectionType.album, Arrays.stream(albumInfo.album.genres).flatMap(s -> SongProvider.INSTANCE.getAlbumsFromGenre(s).stream()).collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
});
|
||||
albumInfo.year.addMouseListener((ClickListener) e -> {
|
||||
if (e.getClickCount() == 2)
|
||||
{
|
||||
Container inter = this;
|
||||
do
|
||||
{
|
||||
inter = inter.getParent();
|
||||
}
|
||||
while (!(inter instanceof Interface) && inter.getParent() != null);
|
||||
if (inter instanceof Interface)
|
||||
{
|
||||
((Interface) inter).updateCollections(CollectionType.album, SongProvider.INSTANCE.getAlbumsFromYear(albumInfo.album.year));
|
||||
}
|
||||
}
|
||||
});
|
||||
this.add(albumInfo, c);
|
||||
this.artMap.put(albumInfo, album);
|
||||
|
||||
songList = new JPanel(new GridBagLayout());
|
||||
j = 0;
|
||||
JButton firstSong = null;
|
||||
|
||||
for (Song song : songCollection)
|
||||
{
|
||||
songNum = new JLabel(String.valueOf(song.trackNum));
|
||||
c2.gridx = 0;
|
||||
c2.gridy = j;
|
||||
c2.anchor = GridBagConstraints.EAST;
|
||||
c2.insets = new Insets(0, 0, 0, 0);
|
||||
songList.add(songNum, c2);
|
||||
songNum.setFocusable(false);
|
||||
c.gridx = 1;
|
||||
c.gridy = i;
|
||||
c.gridheight = 1;
|
||||
c.weightx = 0;
|
||||
c.anchor = GridBagConstraints.NORTHEAST;
|
||||
c.insets = new Insets(0, 0, 0, 0);
|
||||
this.add(songNum, c);
|
||||
this.labelMap.put(songNum, song);
|
||||
|
||||
songTitle = new JLabel(song.title);
|
||||
c2.gridx = 1;
|
||||
c2.gridy = j;
|
||||
c2.anchor = GridBagConstraints.WEST;
|
||||
c2.insets = new Insets(0, 10, 0, 0);
|
||||
songList.add(songTitle, c2);
|
||||
songTitle = new JButton(song.title);
|
||||
songTitle.setHorizontalAlignment(JButton.LEFT);
|
||||
songTitle.setFocusPainted(true);
|
||||
songTitle.setMargin(new Insets(0, 0, 0, 0));
|
||||
songTitle.setContentAreaFilled(false);
|
||||
songTitle.setBorderPainted(false);
|
||||
songTitle.setOpaque(false);
|
||||
songTitle.addActionListener(new AbstractAction()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
Queue.getInstance().add(song);
|
||||
Queue.getInstance().skipToSong(Queue.getInstance().size() - 1);
|
||||
}
|
||||
});
|
||||
c.gridx = 2;
|
||||
c.gridy = i;
|
||||
c.weightx = 1.0;
|
||||
c.anchor = GridBagConstraints.NORTHWEST;
|
||||
c.insets = new Insets(0, 10, 0, 0);
|
||||
this.add(songTitle, c);
|
||||
this.labelMap.put(songTitle, song);
|
||||
// TODO - Add song length or something
|
||||
|
||||
j++;
|
||||
if (firstSong == null)
|
||||
{
|
||||
firstSong = songTitle;
|
||||
JButton finalFirstSong = firstSong;
|
||||
albumInfo.setAction(new AbstractAction()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
finalFirstSong.requestFocusInWindow();
|
||||
}
|
||||
});
|
||||
List<Song> finalSongCollection1 = songCollection;
|
||||
albumInfo.addKeyListener(new KeyAdapter()
|
||||
{
|
||||
@Override
|
||||
public void keyTyped(KeyEvent e)
|
||||
{
|
||||
if (e.getKeyCode() == KeyEvent.VK_ENTER)
|
||||
{
|
||||
Queue.getInstance().addAll(finalSongCollection1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
i++;
|
||||
}
|
||||
c.gridx = 1;
|
||||
c.anchor = GridBagConstraints.WEST;
|
||||
this.add(songList, c);
|
||||
// this.add(new JLabel(new ImageIcon(this.getClass().getResource("/gui/icons/defaultart.png"), "Default")), c);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = i++;
|
||||
c.gridwidth = 3;
|
||||
c.anchor = GridBagConstraints.NORTH;
|
||||
this.add(new JSeparator(SwingConstants.HORIZONTAL), c);
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
private class SongListPolicy extends FocusTraversalPolicy
|
||||
{
|
||||
@Override
|
||||
public Component getComponentAfter(Container aContainer, Component aComponent)
|
||||
{
|
||||
Component[] children = aContainer.getComponents();
|
||||
int index = -1;
|
||||
for (int i = 0, l = aContainer.getComponentCount(); index == -1 && i < l; i++)
|
||||
{
|
||||
if (children[i] == aComponent)
|
||||
{
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
if (index != -1)
|
||||
{
|
||||
if (aComponent instanceof AlbumInfo)
|
||||
{
|
||||
for (int i = index + 1, l = aContainer.getComponentCount(); i < l; i++)
|
||||
{
|
||||
if (children[i] instanceof AlbumInfo)
|
||||
{
|
||||
scrollRectToVisible(children[i].getBounds());
|
||||
return children[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (aComponent instanceof JButton)
|
||||
{
|
||||
for (int i = index + 1, l = aContainer.getComponentCount(); i < l; i++)
|
||||
{
|
||||
if (children[i].isFocusable())
|
||||
{
|
||||
scrollRectToVisible(children[i].getBounds());
|
||||
return children[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getComponentBefore(Container aContainer, Component aComponent)
|
||||
{
|
||||
Component[] children = aContainer.getComponents();
|
||||
int index = -1;
|
||||
for (int i = 0, l = aContainer.getComponentCount(); index == -1 && i < l; i++)
|
||||
{
|
||||
if (children[i] == aComponent)
|
||||
{
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
if (index != -1)
|
||||
{
|
||||
if (aComponent instanceof AlbumInfo)
|
||||
{
|
||||
for (int i = index - 1; i >= 0; i--)
|
||||
{
|
||||
if (children[i] instanceof AlbumInfo)
|
||||
{
|
||||
scrollRectToVisible(children[i].getBounds());
|
||||
return children[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (aComponent instanceof JButton)
|
||||
{
|
||||
for (int i = index - 1; i >= 0; i--)
|
||||
{
|
||||
if (children[i].isFocusable())
|
||||
{
|
||||
scrollRectToVisible(children[i].getBounds());
|
||||
return children[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getFirstComponent(Container aContainer)
|
||||
{
|
||||
Component comp = Arrays.stream(aContainer.getComponents()).filter(a -> a instanceof AlbumInfo).findFirst().orElse(null);
|
||||
if (comp != null)
|
||||
{
|
||||
scrollRectToVisible(comp.getBounds());
|
||||
}
|
||||
return comp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getLastComponent(Container aContainer)
|
||||
{
|
||||
Component[] matching = Arrays.stream(aContainer.getComponents()).filter(a -> a instanceof JButton).toArray(Component[]::new);
|
||||
if (matching.length > 0)
|
||||
{
|
||||
scrollRectToVisible(matching[matching.length - 1].getBounds());
|
||||
return matching[matching.length - 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getDefaultComponent(Container aContainer)
|
||||
{
|
||||
return this.getFirstComponent(aContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,5 +20,8 @@
|
||||
<Logger name="edu.regis.universeplayer.data.LocalSongProvider" level="debug">
|
||||
<AppenderRef ref="Console"/>
|
||||
</Logger>
|
||||
<Logger name="edu.regis.universeplayer.player.Interface" level="debug">
|
||||
<AppenderRef ref="Console"/>
|
||||
</Logger>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
Reference in New Issue
Block a user