package client.frame;
import general.statics.function.GUIFunctions;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JViewport;
import javax.swing.ScrollPaneConstants;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;
import javax.swing.table.DefaultTableCellRenderer;
import server.protocol.Message;
import client.Client;
import client.custom.CustomMainPanel;
import client.custom.JCustomField;
import client.custom.JCustomPane;
import client.frame.listener.InstructButtonListener;
import client.frame.listener.LeaveButtonListener;
import client.gui.Constants;
import client.model.QueuedPlayerTableModel;
/**
* The <code>StartupFrame</code> class, a descendant of <code>YahtzeeFrame</code>, is the frame class for displaying the
* startup/login screen of the game. Contains a moderate number of dynamic inputs and controls.
*
* @author Priidu Neemre
*/
public class StartupFrame extends YahtzeeFrame{
private static final long serialVersionUID = 00000001L;
//Constants for this frame
public static final int WINDOW_WIDTH = 570;
public static final int WINDOW_HEIGHT = 230;
public static final int TEXTFIELD_HEIGHT = 20;
public static final int TEXTFIELD_WIDTH = 195;
public static final int JOINED_PANE_HEIGHT = 120;
public static final int JOINED_PANE_WIDTH = 238;
public static final int GAMERDY_CHECK_INTERVAL = 500;
//MAIN CONTAINER elements of the GUI
private JPanel bgPanel;
private JPanel contentPanel;
//HEADING AREA GUI elements
private JLabel txt_loginHeading;
//JOIN INPUTAREA GUI elements for the start-up frame
private JLabel txt_joinGame;
private JLabel txt_joinerName;
private JTextField joinField;
private JPanel joinerNamePanel;
private JLabel txt_noOfPlayers;
private JComboBox<Integer> cmbNoOfPlayers;
private Integer[] possiblePlayers = {1,2,3,4};
private JPanel noOfPlayersPanel;
private JButton joinBtn;
private JButton instructionBtn;
private JPanel posBtnPanel;
private JButton leaveBtn;
private JPanel negBtnPanel;
private JPanel joinInputPanel;
//QUEUED PLAYERS' LIST GUI elements
private QueuedPlayerTableModel queuedModel;
private JTable queuedTable;
private JLabel txt_queueList;
private JScrollPane queuedPlayerPane;
private JPanel queuedPlayerPanel;
private Client client;
private Timer mainLauncher;
public StartupFrame(Client client){
this.client = client;
buildConfigureJFrame();
buildConfigureMainContainers();
buildConfigureHeadingArea();
buildConfigureJoinInputArea();
buildConfigureJoinerListArea();
buildConfigureActiveProperties();
this.setVisible(true);
}
/**
* Sets the desired properties for the <code>StartupFrame</code> itself.
*/
private void buildConfigureJFrame(){
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
this.setTitle(GameplayFrame.FRAME_DEFAULT_TITLE + " - Login");
this.setLocationRelativeTo(null); //Align the JFrame to the center of the screen
}
/**
* Instantiates variables and enforces properties in connection with the main containers of the frame.
*/
private void buildConfigureMainContainers(){
//Create the contentpane JPanel(bgPanel) of the frame, the root container for all following elements
bgPanel = new CustomMainPanel(Constants.MAIN_BACKGROUND_IMAGE_PATH, Constants.REGULAR_TILE_IMAGE_PATH);
bgPanel.setLayout(new BoxLayout(bgPanel, BoxLayout.Y_AXIS));
setContentPane(bgPanel);
contentPanel = new JPanel();
contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.X_AXIS));
contentPanel.setOpaque(false);
}
/**
* Instantiates variables and enforces properties for all the elements of the GUI's "Heading area" area.
*/
private void buildConfigureHeadingArea(){
txt_loginHeading = new JLabel("Yahtzee login");
txt_loginHeading.setBorder(new EmptyBorder(2,0,2,0));
txt_loginHeading.setFont(Constants.tahoma_huge_bold);
txt_loginHeading.setForeground(Color.WHITE);
JPanel headingPanel = new JPanel();
headingPanel.setLayout(new BoxLayout(headingPanel, BoxLayout.X_AXIS));
headingPanel.setOpaque(false);
headingPanel.add(txt_loginHeading);
bgPanel.add(headingPanel);
}
/**
* Instantiates variables and enforces properties for all the elements of the GUI's "Join input" area.
*/
private void buildConfigureJoinInputArea(){
txt_joinGame = new JLabel("<html>Please fill the follwing fields to be queued for a game of Yahtzee: </html>");
txt_joinGame.setAlignmentX(CENTER_ALIGNMENT);
txt_joinGame.setFont(Constants.tahoma_mediumsmall_bold);
txt_joinGame.setBorder(new EmptyBorder(2,0,8,0));
txt_joinGame.setForeground(Color.WHITE);
txt_joinerName = new JLabel("<html>Player name: </html>");
txt_joinerName.setPreferredSize(new Dimension(80,22));
txt_joinerName.setMaximumSize(new Dimension(80,22));
txt_joinerName.setAlignmentX(CENTER_ALIGNMENT);
txt_joinerName.setFont(Constants.tahoma_mediumsmall_bold);
txt_joinerName.setForeground(Color.WHITE);
joinField = new JCustomField(Constants.TEXTAREA_TILE_IMAGE_PATH);
joinField.setPreferredSize(new Dimension(TEXTFIELD_WIDTH, TEXTFIELD_HEIGHT));
joinField.setMaximumSize(new Dimension(TEXTFIELD_WIDTH, TEXTFIELD_HEIGHT));
joinField.setFont(Constants.tahoma_mediumsmall_normal);
joinField.setForeground(Color.WHITE);
joinField.setCaretColor(Color.WHITE);
joinField.setOpaque(false);
joinField.addActionListener(new JoinListener());
joinerNamePanel = new JPanel();
joinerNamePanel.setLayout(new BoxLayout(joinerNamePanel, BoxLayout.X_AXIS));
joinerNamePanel.setBorder(new EmptyBorder(1,1,1,1));
joinerNamePanel.setOpaque(false);
joinerNamePanel.add(txt_joinerName);
joinerNamePanel.add(joinField);
joinerNamePanel.add(Box.createHorizontalGlue());
txt_noOfPlayers = new JLabel("<html>No. of Players:</html>");
txt_noOfPlayers.setPreferredSize(new Dimension(80,22));
txt_noOfPlayers.setMaximumSize(new Dimension(80,22));
txt_noOfPlayers.setFont(Constants.tahoma_mediumsmall_bold);
txt_noOfPlayers.setForeground(Color.WHITE);
cmbNoOfPlayers = new JComboBox<Integer>(possiblePlayers);
cmbNoOfPlayers.setPreferredSize(new Dimension(40,22));
cmbNoOfPlayers.setMaximumSize(new Dimension(40,22));
cmbNoOfPlayers.addActionListener(new PlayerNumberListener());
cmbNoOfPlayers.setOpaque(false);
noOfPlayersPanel = new JPanel();
noOfPlayersPanel.setLayout(new BoxLayout(noOfPlayersPanel, BoxLayout.X_AXIS));
noOfPlayersPanel.setBorder(new EmptyBorder(1,1,4,1));
noOfPlayersPanel.setOpaque(false);
noOfPlayersPanel.add(txt_noOfPlayers);
noOfPlayersPanel.add(cmbNoOfPlayers);
noOfPlayersPanel.add(Box.createHorizontalGlue());
joinBtn = new JButton("Join");
joinBtn.setPreferredSize(new Dimension(70, 22));
joinBtn.addActionListener(new JoinListener());
instructionBtn = new JButton("Instructions");
instructionBtn.setPreferredSize(new Dimension(100, 22));
instructionBtn.addActionListener(new InstructButtonListener());
posBtnPanel = new JPanel();
posBtnPanel.setLayout(new BoxLayout(posBtnPanel, BoxLayout.X_AXIS));
posBtnPanel.setBorder(new EmptyBorder(1,1,2,1));
posBtnPanel.setOpaque(false);
posBtnPanel.add(joinBtn);
posBtnPanel.add(Box.createRigidArea(new Dimension(4, joinBtn.getHeight())));
posBtnPanel.add(instructionBtn);
posBtnPanel.add(Box.createHorizontalGlue());
joinInputPanel = new JPanel();
joinInputPanel.setLayout(new BoxLayout(joinInputPanel, BoxLayout.Y_AXIS));
joinInputPanel.setBorder(new EmptyBorder(0,3,0,1));
joinInputPanel.setOpaque(false);
joinInputPanel.add(txt_joinGame);
joinInputPanel.add(joinerNamePanel);
joinInputPanel.add(noOfPlayersPanel);
joinInputPanel.add(Box.createVerticalGlue());
joinInputPanel.add(posBtnPanel);
contentPanel.add(joinInputPanel);
}
/**
* Instantiates variables and enforces properties for all the elements of the GUI's "Joiner list" area.
*/
private void buildConfigureJoinerListArea(){
txt_queueList = new JLabel(String.format("<html>Clients queued for a %d-player game <font color=red>" +
"(Enables after \"Join\")</font>: </html>", cmbNoOfPlayers.getSelectedItem()));
txt_queueList.setPreferredSize(new Dimension(150, 30));
txt_queueList.setFont(Constants.tahoma_mediumsmall_bold);
txt_queueList.setAlignmentX(CENTER_ALIGNMENT);
txt_queueList.setForeground(Color.WHITE);
txt_queueList.setBorder(new EmptyBorder(5,0,8,0));
queuedModel = new QueuedPlayerTableModel(client.getJoinedList());
queuedTable = new JTable(queuedModel);
queuedTable.getTableHeader().setReorderingAllowed(false);
queuedTable.getTableHeader().setResizingAllowed(false);
GUIFunctions.alignJTableContents(JLabel.LEFT, queuedTable);
queuedTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
GUIFunctions.setJTableColumnWidth(queuedTable, 18, QueuedPlayerTableModel.QUEUED_TABLE_INDEX_COLUMN);
GUIFunctions.setJTableColumnWidth(queuedTable, 90, QueuedPlayerTableModel.QUEUED_TABLE_NAME_COLUMN);
GUIFunctions.setJTableColumnWidth(queuedTable, 125, QueuedPlayerTableModel.QUEUED_TABLE_DATE_COLUMN);
DefaultTableCellRenderer cellRend = (DefaultTableCellRenderer) queuedTable.getCellRenderer(0, 0);
cellRend.setForeground(Color.WHITE);
cellRend.setOpaque(false);
queuedTable.setOpaque(false);
queuedTable.getTableHeader().setOpaque(false);
queuedTable.getTableHeader().setBackground(new Color(0,true));
queuedTable.getTableHeader().setPreferredSize(new Dimension(0,18));
queuedPlayerPane = new JCustomPane(queuedTable, Constants.TEXTAREA_TILE_IMAGE_PATH);
queuedPlayerPane.setPreferredSize(new Dimension(JOINED_PANE_WIDTH, JOINED_PANE_HEIGHT));
queuedPlayerPane.setMaximumSize(new Dimension(JOINED_PANE_WIDTH, JOINED_PANE_HEIGHT));
queuedPlayerPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
queuedPlayerPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
queuedPlayerPane.getViewport().setOpaque(false);
queuedPlayerPane.setOpaque(false);
queuedPlayerPane.setColumnHeader(new JViewport());
queuedPlayerPane.getColumnHeader().setOpaque(false);
queuedPlayerPanel = new JPanel();
queuedPlayerPanel.setLayout(new BoxLayout(queuedPlayerPanel, BoxLayout.Y_AXIS));
queuedPlayerPanel.setBorder(new EmptyBorder(3,3,2,2));
queuedPlayerPanel.setOpaque(false);
leaveBtn = new JButton("Leave");
leaveBtn.setPreferredSize(new Dimension(70, 22));
leaveBtn.addActionListener(new LeaveButtonListener(client));
negBtnPanel = new JPanel();
negBtnPanel.setLayout(new BoxLayout(negBtnPanel, BoxLayout.X_AXIS));
negBtnPanel.setBorder(new EmptyBorder(3,1,0,1));
negBtnPanel.setOpaque(false);
negBtnPanel.add(Box.createHorizontalGlue());
negBtnPanel.add(leaveBtn);
queuedPlayerPanel.add(txt_queueList);
queuedPlayerPanel.add(queuedPlayerPane);
queuedPlayerPanel.add(negBtnPanel);
contentPanel.add(queuedPlayerPanel);
getContentPane().add(contentPanel);
}
/**
* Instantiates variables and enforces properties for all the GUI's dynamic components.
*/
private void buildConfigureActiveProperties(){
//Instantiate the mainLauncher timer in the constructor to avoid NPEs and other bugs
mainLauncher = new Timer(GAMERDY_CHECK_INTERVAL, new LauncherTickListener());
}
@Override
public void updateGUIIndicators() {
switch(client.getState()){
case PROCESS_LAUNCHED:
setTitle(FRAME_DEFAULT_TITLE);
joinBtn.setEnabled(true);
joinField.setEnabled(true);
cmbNoOfPlayers.setEnabled(true);
break;
case INITIATING_CONNECTION:
setTitle("Initiating a connection with the gameserver...");
lockAllInputs();
queuedModel.updateQueuedPlayerList();
break;
default:
break;
}
repaint();
}
/**
* Locks all the player-manipulatable inputs of the frame.
*/
private void lockAllInputs(){
joinBtn.setEnabled(false);
joinField.setEnabled(false);
cmbNoOfPlayers.setEnabled(false);
}
/**
* Notifies the player about the chosen name already being in use on the server and transitions the client back into the
* <code>PROCESS_LAUNCHED</code> state, also stoping the mainframe launcher timer.
*/
public void notifyNameInUse(){
JOptionPane.showMessageDialog(null, "The server has detected that the name you picked is already in use!\n" +
"Please pick another player name!");
client.bInitiatingConnectionTOProcessLaunched();
mainLauncher.stop();
updateGUIForeign();
}
@Override
public void writeToLogArea(String message) {}
@Override
public void writeToLogArea(Message msg) {}
//[START]GETTERS AND SETTERS
public StartupFrame getThis(){
return this;
}
//[END]GETTERS AND SETTERS
//[START]LISTENERS (CONTROLLER)
/**
* The <code>JoinListener</code> class is an implementation of the <code>ActionListener</code> which if instantiated
* and binded to a control, reacts to an incoming event by:<br />
* a) Making sure that the name entered to the "player name" field was of appropriate length. <br />
* b) If so, transitioning the client to the <code>INSTANTIATING_CONNECTION</code> state. <br />
*/
class JoinListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
String tempName = joinField.getText().trim();
if(tempName.length() < Client.CLIENT_NAME_MINLENGTH || (tempName.length() > Client.CLIENT_NAME_MAXLENGTH)){
JOptionPane.showMessageDialog(getThis(), "Invalid player name! Must have a length of "
+ Client.CLIENT_NAME_MINLENGTH + " to " + Client.CLIENT_NAME_MAXLENGTH + " characters!");
}else{
lockAllInputs();
client.fPrcoessLaunchedTOInitiatingConnection(tempName, (Integer)cmbNoOfPlayers.getSelectedItem());
mainLauncher.start();
}
}
}
/**
* The <code>LaunchTickListener</code> class is an implementation of the <code>ActionListener</code> which if instantiated
* and binded to a control, reacts to an incoming event by :<br />
* a) Calling a repaint on all GUI indicators,<br />
* b) Checking whether all the vital components for game-start have been received from the gameserver,<br />
* c) If all the vital components have been received, transition the client to the <code>GAME_IN_PROGRESS</code> state and
* close the startup frame.
*/
class LauncherTickListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
//Update the GUI every time the constraints for game start are checked
updateGUIIndicators();
//Check whether the client has successfully received all the initial data for a game-start
if((client.getClientName() == null) || (client.getDice() == null) || (client.getGameSheet() == null) ||
(client.getLeaderboard() == null) || (client.getNoOfPlayers() > Client.MAX_NO_OF_PLAYERS ||
client.getNoOfPlayers() < Client.MIN_NO_OF_PLAYERS)){
return;
}else{
//The client has received all the initial data from the gameserver
mainLauncher.stop();
client.fInitiatingConnectionTOGameInProgress();
getThis().dispose();
}
}
}
/**
* The <code>PlayerNumberListener</code> class is an implementation of the <code>ActionListener</code> which if instantiated
* and binded to a control, reacts to an incoming event by:<br />
* a) Updating the text above the joiner area accordingly to the number of players chosen.
*/
class PlayerNumberListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
txt_queueList.setText(String.format("<html>Clients queued for a %d-player game <font color=red>" +
"(Enables after \"Join\")</font>: </html>", cmbNoOfPlayers.getSelectedItem()));
}
}
}