package view;
import java.awt.Dimension;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Alignment;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.LayoutStyle.ComponentPlacement;
import javax.swing.JMenuBar;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.Collections;
import javax.swing.JScrollPane;
import javax.swing.JPanel;
import javax.swing.JLabel;
import model.OnlineComparator;
import model.Stream;
import java.awt.Font;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.Insets;
import java.awt.SystemTray;
import java.awt.Toolkit;
import javax.swing.JCheckBox;
import java.awt.event.ItemListener;
import java.awt.event.ItemEvent;
import java.io.File;
import javax.swing.JCheckBoxMenuItem;
public class Application implements ActionListener {
JFrame frmStreamtrackerV;
JPanel panel;
JScrollPane scrollPane;
static ArrayList<Stream> streams;
boolean initial;
boolean notificationsEnabled;
boolean groupOnline;
boolean minimizeToTray;
STTrayIcon trayIcon;
JButton btnAddStream;
JButton btnDeleteStream;
JLabel lblExamplechannel;
JLabel lblLoadingChannels;
Thread update;
Timer updateTimer;
/**
* Launch the application.
*/
public static void main(String[] args) {
// Set the correct Look and Feel, based on the system used.
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException e1) {
e1.printStackTrace();
} catch (InstantiationException e1) {
e1.printStackTrace();
} catch (IllegalAccessException e1) {
e1.printStackTrace();
} catch (UnsupportedLookAndFeelException e1) {
e1.printStackTrace();
}
// Initialize the streams ArrayList
controller.Init.init(new ArrayList<String>(0));
// Create the actual window and frame
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Application window = new Application();
window.frmStreamtrackerV.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the application.
*/
public Application() {
// Get the list of channels
String[] channels = controller.Init.init(new ArrayList<String>(0));
// Build a list of Stream objects out of the list of channel names
streams = new ArrayList<Stream>(0);
// Update all of the information for each stream
for (int i = 0; i < channels.length; i++)
streams.add(new Stream(channels[i]));
// Create the main frame and panel
panel = new JPanel();
frmStreamtrackerV = new JFrame();
// Set initial boolean values
notificationsEnabled = true;
initial = true;
groupOnline = false;
minimizeToTray = true;
// Create an initial thread for the table updating method
update = new Thread();
// Create the timer for stream refreshing, running once per 60 seconds
updateTimer = new Timer(60000, this);
updateTimer.setInitialDelay(1);
// Initialize the information in the window
initialize();
// Create and initialize the tray icon
trayIcon = new STTrayIcon();
trayIcon.init(this);
// Start the API call timer
updateTimer.start();
}
/**
* Initialize the contents of the frame.
*/
private void initialize() {
// Set properties of the main frame
frmStreamtrackerV.setTitle("StreamTracker v1.3");
frmStreamtrackerV.setIconImage(Toolkit.getDefaultToolkit().getImage("icon.gif"));
frmStreamtrackerV.setBounds(100, 100, 400, 410);
frmStreamtrackerV.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frmStreamtrackerV.addWindowListener(new WindowExitAdapter());
frmStreamtrackerV.addWindowListener(new WindowRestoreAdapter());
Dimension dim = new Dimension();
dim.setSize(400, 410);
frmStreamtrackerV.setMinimumSize(dim);
// Set proprerties of the Add stream button
btnAddStream = new JButton("Add stream");
btnAddStream.setFont(new Font("Tahoma", Font.PLAIN, 11));
btnAddStream.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent arg0) {
String streamToAdd = JOptionPane.showInputDialog(frmStreamtrackerV, "Enter the name of the channel you'd like to track:");
// Do not attempt to add invalid streams
if (streamToAdd == null || streamToAdd == "") return;
if (streams.contains(new Stream(streamToAdd))) return;
// Get the correct location to add the stream
int i;
for(i = 0; i < streams.size() && streamToAdd.toLowerCase().compareTo(streams.get(i).getChannel().toLowerCase()) > 0; i++) { }
if (i == streams.size()) streams.add(new Stream(streamToAdd));
else streams.add(i, new Stream(streamToAdd));
// Refresh the newly-added stream, update the table of information, then repaint the window
streams.get(i).refresh();
updateTable();
frmStreamtrackerV.validate();
frmStreamtrackerV.repaint();
}
});
// Set the properties for the delete stream button
btnDeleteStream = new JButton("Delete stream");
btnDeleteStream.setFont(new Font("Tahoma", Font.PLAIN, 11));
btnDeleteStream.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent arg0) {
String streamToRemove = JOptionPane.showInputDialog(frmStreamtrackerV, "Enter the name of the channel you'd like to stop tracking:");
Stream r = new Stream(streamToRemove);
// Do not remove invalid streams
if (streamToRemove == null || streamToRemove == "" || !streams.contains(r))
{
return;
}
streams.remove(r);
// Update and repaint the table
updateTable();
frmStreamtrackerV.validate();
frmStreamtrackerV.repaint();
}
});
// Create the scroll pane for the table
scrollPane = new JScrollPane();
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
JPanel panel_1 = new JPanel();
// Set the properties for the "Group online streams" checkbox
JCheckBox chckbxGroupOnlineStreams = new JCheckBox("Group online streams");
chckbxGroupOnlineStreams.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent ie) {
// Sort by the proper comparator based on whether the box was checked or unchecked
if (ie.getStateChange() == ItemEvent.SELECTED) {
groupOnline = true;
Collections.sort(streams, new OnlineComparator());
}
if (ie.getStateChange() == ItemEvent.DESELECTED) {
groupOnline = false;
Collections.sort(streams);
}
// Update and repaint the table
updateTable();
frmStreamtrackerV.validate();
frmStreamtrackerV.repaint();
}
});
chckbxGroupOnlineStreams.setFont(new Font("Tahoma", Font.PLAIN, 10));
// Set up the loading labels
lblLoadingChannels = new JLabel("Loading channels...");
lblLoadingChannels.setFont(new Font("Tahoma", Font.PLAIN, 10));
lblExamplechannel = new JLabel("");
lblExamplechannel.setFont(new Font("Tahoma", Font.PLAIN, 10));
GroupLayout groupLayout = new GroupLayout(frmStreamtrackerV.getContentPane());
groupLayout.setHorizontalGroup(
groupLayout.createParallelGroup(Alignment.LEADING)
.addGroup(groupLayout.createSequentialGroup()
.addContainerGap()
.addGroup(groupLayout.createParallelGroup(Alignment.LEADING)
.addGroup(groupLayout.createSequentialGroup()
.addComponent(panel_1, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGap(150))
.addGroup(Alignment.TRAILING, groupLayout.createSequentialGroup()
.addComponent(scrollPane, GroupLayout.DEFAULT_SIZE, 247, Short.MAX_VALUE)
.addPreferredGap(ComponentPlacement.RELATED)
.addGroup(groupLayout.createParallelGroup(Alignment.LEADING)
.addGroup(groupLayout.createParallelGroup(Alignment.LEADING, false)
.addComponent(btnAddStream, GroupLayout.PREFERRED_SIZE, 109, GroupLayout.PREFERRED_SIZE)
.addComponent(btnDeleteStream, Alignment.TRAILING, GroupLayout.PREFERRED_SIZE, 109, GroupLayout.PREFERRED_SIZE))
.addComponent(chckbxGroupOnlineStreams)
.addComponent(lblLoadingChannels)
.addComponent(lblExamplechannel))))
.addGap(0))
);
groupLayout.setVerticalGroup(
groupLayout.createParallelGroup(Alignment.LEADING)
.addGroup(groupLayout.createSequentialGroup()
.addContainerGap()
.addComponent(panel_1, GroupLayout.PREFERRED_SIZE, 17, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.RELATED)
.addGroup(groupLayout.createParallelGroup(Alignment.LEADING)
.addGroup(groupLayout.createSequentialGroup()
.addComponent(btnAddStream)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(btnDeleteStream)
.addPreferredGap(ComponentPlacement.UNRELATED)
.addComponent(chckbxGroupOnlineStreams)
.addGap(28)
.addComponent(lblLoadingChannels)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(lblExamplechannel))
.addComponent(scrollPane, GroupLayout.DEFAULT_SIZE, 297, Short.MAX_VALUE))
.addGap(10))
);
// Set the table layout for the column headers
GridBagLayout gbl_panel_1 = new GridBagLayout();
gbl_panel_1.columnWidths = new int[]{101, 63, 60};
gbl_panel_1.rowHeights = new int[]{25, 0};
gbl_panel_1.columnWeights = new double[]{0.0};
gbl_panel_1.rowWeights = new double[]{Double.MIN_VALUE};
panel_1.setLayout(gbl_panel_1);
// Create the column headers
JLabel lblChannel = new JLabel("Channel");
lblChannel.setFont(new Font("Tahoma", Font.BOLD, 11));
GridBagConstraints gbc_lblChannel = new GridBagConstraints();
gbc_lblChannel.insets = new Insets(4, 5, 5, 5);
gbc_lblChannel.gridx = 0;
gbc_lblChannel.gridy = 0;
gbc_lblChannel.anchor = GridBagConstraints.NORTH;
panel_1.add(lblChannel, gbc_lblChannel);
JLabel lblOnline_1 = new JLabel("Online");
lblOnline_1.setFont(new Font("Tahoma", Font.BOLD, 11));
GridBagConstraints gbc_lblOnline_1 = new GridBagConstraints();
gbc_lblOnline_1.insets = new Insets(4, 0, 5, 5);
gbc_lblOnline_1.gridx = 1;
gbc_lblOnline_1.gridy = 0;
gbc_lblChannel.anchor = GridBagConstraints.NORTHWEST;
panel_1.add(lblOnline_1, gbc_lblOnline_1);
JLabel lblViewers = new JLabel("Viewers");
lblViewers.setFont(new Font("Tahoma", Font.BOLD, 11));
GridBagConstraints gbc_lblViewers = new GridBagConstraints();
gbc_lblViewers.insets = new Insets(4, 0, 5, 0);
gbc_lblViewers.gridx = 2;
gbc_lblViewers.gridy = 0;
gbc_lblChannel.anchor = GridBagConstraints.NORTHWEST;
panel_1.add(lblViewers, gbc_lblViewers);
scrollPane.setViewportView(panel);
frmStreamtrackerV.getContentPane().setLayout(groupLayout);
// Set the properties of the menu bar
JMenuBar menuBar = new JMenuBar();
frmStreamtrackerV.setJMenuBar(menuBar);
JMenu mnFile = new JMenu("File");
menuBar.add(mnFile);
JMenuItem mntmExit = new JMenuItem("Exit");
mntmExit.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent arg0) {
new WindowExitAdapter().windowClosing(null);
System.exit(0);
}
});
mnFile.add(mntmExit);
JMenu mnHelp = new JMenu("Help");
menuBar.add(mnHelp);
JMenuItem mntmAbout = new JMenuItem("About");
mntmAbout.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(frmStreamtrackerV, "StreamTracker v1.3, May 27 2012\n" +
"Built using Eclipse and WindowBuilderPro", "About", JOptionPane.INFORMATION_MESSAGE);
}
});
// Set properties for disabling notifications
JCheckBoxMenuItem chckbxmntmDisableNotifications = new JCheckBoxMenuItem("Disable notifications");
chckbxmntmDisableNotifications.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent ie) {
// Enable or disable the notifications boolean
if (ie.getStateChange() == ItemEvent.SELECTED) notificationsEnabled = false;
if (ie.getStateChange() == ItemEvent.DESELECTED) notificationsEnabled = true;
}
});
mnHelp.add(chckbxmntmDisableNotifications);
JCheckBoxMenuItem chckbxmntmMinimizeToTray = new JCheckBoxMenuItem("Minimize to tray", true);
chckbxmntmMinimizeToTray.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent ie) {
// Enable or disable the minimize boolean
if (ie.getStateChange() == ItemEvent.DESELECTED) minimizeToTray = false;
if (ie.getStateChange() == ItemEvent.SELECTED) minimizeToTray = true;
}
});
mnHelp.add(chckbxmntmMinimizeToTray);
mnHelp.add(mntmAbout);
}
public static Stream[] getStreams() {
return streams.toArray(new Stream[0]);
}
public void updateTable()
{
// Set the layout
GridBagLayout gbl_panel = new GridBagLayout();
gbl_panel.columnWidths = new int[]{101, 60, 60, 0};
// Let the row heights be based on the number of streams
int[] rowHeights = new int[streams.size()+1];
for(int i = 0; i < streams.size(); i++)
rowHeights[i] = 25;
rowHeights[streams.size()] = 0;
gbl_panel.rowHeights = rowHeights;
gbl_panel.columnWeights = new double[]{0.0, 0.0, 0.0, Double.MIN_VALUE};
gbl_panel.rowWeights = new double[]{0.0};
panel.setLayout(gbl_panel);
panel.removeAll();
synchronized (streams)
{
if (groupOnline) Collections.sort(streams, new OnlineComparator());
else Collections.sort(streams);
for(int i = 0; i < streams.size(); i++)
{
JLabel lbl = new JLabel(streams.get(i).getChannel());
lbl.setFont(new Font("Tahoma", Font.PLAIN, 11));
GridBagConstraints gbc_lbl = new GridBagConstraints();
gbc_lbl.insets = new Insets(4, 5, 5, 5);
gbc_lbl.gridx = 0;
gbc_lbl.gridy = i;
gbc_lbl.anchor = GridBagConstraints.NORTHWEST;
panel.add(lbl, gbc_lbl);
JLabel lblOnline = new JLabel(streams.get(i).isOnline() ? "Online" : "Offline");
if (streams.get(i).isOnline())
{
lblOnline.setFont(new Font("Tahoma", Font.BOLD, 11));
JLabelLink.makeLinkable(lblOnline, new JLabelLink.LinkMouseListener(), streams.get(i).getChannel());
}
else
lblOnline.setFont(new Font("Tahoma", Font.PLAIN, 11));
GridBagConstraints gbc_lblOnline = new GridBagConstraints();
gbc_lblOnline.insets = new Insets(4, 0, 5, 5);
gbc_lblOnline.gridx = 1;
gbc_lblOnline.gridy = i;
gbc_lblOnline.anchor = GridBagConstraints.NORTH;
panel.add(lblOnline, gbc_lblOnline);
JLabel label = new JLabel(streams.get(i).isOnline() ? ""+streams.get(i).getViewers() : "-");
label.setFont(new Font("Tahoma", Font.PLAIN, 11));
GridBagConstraints gbc_label = new GridBagConstraints();
gbc_label.insets = new Insets(4, 0, 5, 0);
gbc_label.gridx = 2;
gbc_label.gridy = i;
gbc_label.anchor = GridBagConstraints.NORTH;
// Set weights to avoid entire list being center-aligned on resize
// I have absolutely no idea how this could be done better, or even how this actually works
gbc_label.weightx = 1.0;
if (i == streams.size() - 1)
gbc_label.weighty = 1.0;
panel.add(label, gbc_label);
}
}
}
@Override
public void actionPerformed(ActionEvent e) {
try {
// Force the current update thread to finish
update.join();
// Create new thread
update = new Thread(new Runnable() {
@Override
public void run() {
synchronized (streams)
{
for (Stream s : streams)
{
boolean oldOnline = s.isOnline();
lblExamplechannel.setText(s.getChannel());
s.refresh();
if (!initial && notificationsEnabled && s.isOnline() && !oldOnline) trayIcon.displayPopupMessage(s.getChannel());
}
updateTable();
if (initial)
{
lblLoadingChannels.setVisible(false);
lblExamplechannel.setVisible(false);
initial = false;
}
frmStreamtrackerV.validate();
frmStreamtrackerV.repaint();
}
}
});
update.start();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
public class WindowRestoreAdapter extends WindowAdapter {
public void windowDeiconified(WindowEvent e) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
int state = frmStreamtrackerV.getExtendedState();
state = state & ~JFrame.ICONIFIED;
frmStreamtrackerV.setExtendedState(state);
frmStreamtrackerV.setVisible(true);
frmStreamtrackerV.toFront();
frmStreamtrackerV.repaint();
}
});
}
}
public class WindowExitAdapter extends WindowAdapter {
// When the window is closed, uninitialize the application then quit.
public void windowClosing(WindowEvent e)
{
controller.Uninit.uninit(Application.getStreams(), new File("config.ini"));
System.exit(0);
}
// Hide the window when iconified
public void windowIconified(WindowEvent e)
{
if (SystemTray.isSupported() && minimizeToTray)
e.getComponent().setVisible(false);
}
}
}