package controller.ui; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.GridLayout; import java.awt.Image; import java.awt.Insets; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.AbstractButton; import javax.swing.ButtonGroup; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.JRadioButton; import javax.swing.JToggleButton; import common.Log; import common.TotalScaleLayout; import controller.Clock; import controller.EventHandler; import controller.action.ActionBoard; import controller.action.GCAction; import controller.net.RobotOnlineStatus; import controller.net.RobotWatcher; import data.AdvancedData; import data.GameControlData; import data.HL; import data.PlayerInfo; import data.Rules; import data.SPL; import data.Teams; /** * @author Michel Bartsch * * This is the main GUI. * In this class you will find the whole graphical output and the bindings * of buttons to their actions, nothing less and nothing more. */ public class GUI extends JFrame implements GCGUI { private static final boolean IS_OSX = System.getProperty("os.name").contains("OS X"); private static final boolean IS_APPLE_JAVA = IS_OSX && System.getProperty("java.version").compareTo("1.7") < 0; private static final Insets insets = IS_APPLE_JAVA ? new Insets (2, -30, 2, -30) : null; private static final String BUTTON_MASK = IS_APPLE_JAVA ? "<html><div style=\"padding: 0px 12px\"><center>%s</center></div></html>" : "<html><center>%s</center></html>"; /** Fix button centering for Apple Java. */ private class Button extends JButton { private static final long serialVersionUID = -1533689100759569853L; public Button() { setMargin(insets); } public Button(String text) { setMargin(insets); setText(text); } public void setText(String text) { super.setText(String.format(BUTTON_MASK, text)); } } /** Fix button centering for Apple Java. */ private class ToggleButton extends JToggleButton { private static final long serialVersionUID = -7733709666734108610L; public ToggleButton() { setMargin(insets); } public ToggleButton(String text) { setMargin(insets); setText(text); } public void setText(String text) { super.setText(String.format(BUTTON_MASK, text)); } } private static final long serialVersionUID = 1L; /** * Some constants defining this GUI`s appearance as their names say. * Feel free to change them and see what happens. */ private static final int WINDOW_WIDTH = 1024; private static final int WINDOW_HEIGHT = 768; private static final int STANDARD_FONT_SIZE = 17; private static final int TITLE_FONT_SIZE = 24; private static final String STANDARD_FONT = "Helvetica"; private static final int GOALS_FONT_SIZE = 60; private static final int TIME_FONT_SIZE = 50; private static final int TIME_SUB_FONT_SIZE = 40; private static final int TIMEOUT_FONT_SIZE = 14; private static final int STATE_FONT_SIZE = 12; private static final String WINDOW_TITLE = "GameController"; private static final String ICONS_PATH = "config/icons/"; private static final String[][] BACKGROUND_SIDE = {{"robot_left_blue.png", "robot_left_red.png", "robot_left_yellow.png", "robot_left_black.png"}, {"robot_right_blue.png", "robot_right_red.png", "robot_right_yellow.png", "robot_right_black.png"}}; private static final String BACKGROUND_MID = "field.png"; private static final String BACKGROUND_CLOCK_SMALL = "time_ground_small.png"; private static final String BACKGROUND_CLOCK = "time_ground.png"; private static final String KICKOFF = "Kickoff"; private static final String KICKOFF_PENALTY_SHOOTOUT = "P.-taker"; private static final String PUSHES = "Pushes"; private static final String SHOT = "Shot"; private static final String SHOTS = "Shots"; private static final String EJECTED = "Ejected"; private static final String ONLINE = "wlan_status_green.png"; private static final String OFFLINE = "wlan_status_red.png"; private static final String HIGH_LATENCY = "wlan_status_yellow.png"; private static final String UNKNOWN_ONLINE_STATUS = "wlan_status_grey.png"; private static final String TIMEOUT = "Timeout"; private static final String REFEREE_TIMEOUT = "Referee<br/>Timeout"; private static final String STUCK = "Global <br/> Game <br/> Stuck"; private static final String OUT = "Out"; private static final String STATE_INITIAL = "Initial"; private static final String STATE_READY = "Ready"; private static final String STATE_SET = "Set"; private static final String STATE_PLAY = "Play"; private static final String STATE_FINISH = "Finish"; private static final String CLOCK_RESET = "reset.png"; private static final String CLOCK_PAUSE = "pause.png"; private static final String CLOCK_PLAY = "play.png"; private static final String CLOCK_PLUS = "plus.png"; private static final String FIRST_HALF = "First Half"; private static final String SECOND_HALF = "Second Half"; private static final String FIRST_HALF_SHORT = "1st Half"; private static final String SECOND_HALF_SHORT = "2nd Half"; private static final String FIRST_HALF_OVERTIME = "1st Extra"; private static final String SECOND_HALF_OVERTIME = "2nd Extra"; private static final String PENALTY_SHOOT = "Penalty Shots"; private static final String PENALTY_SHOOT_SHORT = "Penalty"; private static final String PEN_PUSHING = "Pushing"; private static final String PEN_LEAVING = "Leaving the Field"; private static final String PEN_MOTION_IN_SET = "Motion in Set"; private static final String PEN_MOTION_IN_SET_SHORT = "Motion"; private static final String PEN_INACTIVE = "Fallen / Inactive / Local Game Stuck"; private static final String PEN_DEFENDER = "Illegal Defender"; private static final String PEN_BALL_CONTACT = "Ball Holding / Hands"; private static final String PEN_KICK_OFF_GOAL = "Kickoff Goal"; private static final String PEN_COACH_MOTION = "Coach Motion"; private static final String PEN_PICKUP = "Pick-Up"; private static final String PEN_MANIPULATION = "Ball Manipulation"; private static final String PEN_PHYSICAL = "Physical Contact"; private static final String PEN_DEFENSE = "Illegal Defense"; private static final String PEN_ATTACK = "Illegal Attack"; private static final String PEN_PICKUP_INCAPABLE = "Pickup/Incapable"; private static final String PEN_SERVICE = "Service"; private static final String PEN_SUBSTITUTE = "Substitute"; private static final String PEN_SUBSTITUTE_SHORT = "Sub"; private static final String DROP_BALL = "Dropped Ball"; private static final String TEAMMATE_PUSHING = "Teammate Pushing"; private static final String CANCEL = "Cancel"; private static final String COACH = "Coach"; private static final String BACKGROUND_BOTTOM = "timeline_ground.png"; private static final Color COLOR_HIGHLIGHT = Color.YELLOW; private static final Color COLOR_STANDARD = (new JButton()).getBackground(); private static final int UNPEN_HIGHLIGHT_SECONDS = 10; private static final int TIMEOUT_HIGHLIGHT_SECONDS = 10; private static final int FINISH_HIGHLIGHT_SECONDS = 10; private static final int KICKOFF_BLOCKED_HIGHLIGHT_SECONDS = 3; /** Some attributes used in the GUI components. */ private double lastSize = 0; private Font standardFont; private Font titleFont; private Font goalsFont; private Font timeFont; private Font timeSubFont; private Font timeoutFont; private Font stateFont; private ImageIcon clockImgReset; private ImageIcon clockImgPlay; private ImageIcon clockImgPause; private ImageIcon clockImgPlus; private ImageIcon lanOnline; private ImageIcon lanHighLatency; private ImageIcon lanOffline; private ImageIcon lanUnknown; private ImageIcon[][] backgroundSide; /** All the components of this GUI. */ private ImagePanel[] side; private JLabel[] name; private JButton[] goalDec; private JButton[] goalInc; private JLabel[] goals; private JRadioButton[] kickOff; private ButtonGroup kickOffGroup; private JLabel[] pushes; private JPanel[] robots; private JButton[][] robot; private JLabel[][] robotLabel; private ImageIcon[][] lanIcon; private JProgressBar[][] robotTime; private JToggleButton refereeTimeout; private JToggleButton[] timeOut; private JButton[] stuck; private JButton[] out; private JPanel mid; private JToggleButton initial; private JToggleButton ready; private JToggleButton set; private JToggleButton play; private JToggleButton finish; private ButtonGroup stateGroup; private ImageButton clockReset; private ImagePanel clockContainer; private JLabel clock; private JLabel clockSub; private ImageButton incGameClock; private ImageButton clockPause; private JToggleButton firstHalf; private JToggleButton secondHalf; private JToggleButton firstHalfOvertime; private JToggleButton secondHalfOvertime; private JToggleButton penaltyShoot; private ButtonGroup halfGroup; private JToggleButton[] pen; private JButton dropBall; private ImagePanel bottom; private JPanel log; private JToggleButton[] undo; private JButton cancelUndo; /** * Creates a new GUI. * * @param fullscreen If true, the GUI tries to start using the full * size of the screen. Actually this means changing * the display`s resolution to the GUI`s size. * @param data The starting data. */ public GUI(boolean fullscreen, GameControlData data) { super(WINDOW_TITLE); setSize(WINDOW_WIDTH, WINDOW_HEIGHT); setResizable(true); GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); int width = gd.getDisplayMode().getWidth(); int height = gd.getDisplayMode().getHeight(); setLocation((width-WINDOW_WIDTH)/2, (height-WINDOW_HEIGHT)/2); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { Clock.getInstance().interrupt(); dispose(); } }); clockImgReset = new ImageIcon(ICONS_PATH+CLOCK_RESET); clockImgPlay = new ImageIcon(ICONS_PATH+CLOCK_PLAY); clockImgPause = new ImageIcon(ICONS_PATH+CLOCK_PAUSE); clockImgPlus = new ImageIcon(ICONS_PATH+CLOCK_PLUS); lanOnline = new ImageIcon(ICONS_PATH+ONLINE); lanHighLatency = new ImageIcon(ICONS_PATH+HIGH_LATENCY); lanOffline = new ImageIcon(ICONS_PATH+OFFLINE); lanUnknown = new ImageIcon(ICONS_PATH+UNKNOWN_ONLINE_STATUS); backgroundSide = new ImageIcon[2][4]; for (int i=0; i<BACKGROUND_SIDE.length; i++) { for (int j=0; j<BACKGROUND_SIDE[i].length; j++) { backgroundSide[i][j] = new ImageIcon(ICONS_PATH+Rules.league.leagueDirectory+"/"+BACKGROUND_SIDE[i][j]); } } //Components side = new ImagePanel[2]; for (int i=0; i<2; i++) { side[i] = new ImagePanel(backgroundSide[i][i].getImage()); side[i].setOpaque(true); } mid = new ImagePanel(new ImageIcon(ICONS_PATH+BACKGROUND_MID).getImage()); bottom = new ImagePanel(new ImageIcon(ICONS_PATH+BACKGROUND_BOTTOM).getImage()); //--side-- // score name = new JLabel[2]; goalDec = new JButton[2]; goalInc = new JButton[2]; goals = new JLabel[2]; kickOff = new JRadioButton[3]; kickOffGroup = new ButtonGroup(); pushes = new JLabel[2]; for (int i=0; i<2; i++) { name[i] = new JLabel(Teams.getNames(false)[data.team[i].teamNumber]); name[i].setHorizontalAlignment(JLabel.CENTER); name[i].setForeground(Rules.league.teamColor[data.team[i].teamColor]); goalInc[i] = new Button("+"); goalDec[i] = new Button("-"); kickOff[i] = new JRadioButton(KICKOFF); kickOff[i].setOpaque(false); kickOff[i].setHorizontalAlignment(JLabel.CENTER); kickOffGroup.add(kickOff[i]); goals[i] = new JLabel("0"); goals[i].setHorizontalAlignment(JLabel.CENTER); pushes[i] = new JLabel("0"); pushes[i].setHorizontalAlignment(JLabel.CENTER); } kickOff[2] = new JRadioButton(); kickOffGroup.add(kickOff[2]); // robots robots = new JPanel[2]; if (Rules.league.isCoachAvailable) { robot = new JButton[2][Rules.league.teamSize+1]; robotLabel = new JLabel[2][Rules.league.teamSize+1]; lanIcon = new ImageIcon[2][Rules.league.teamSize+1]; robotTime = new JProgressBar[2][Rules.league.teamSize+1]; } else { robot = new JButton[2][Rules.league.teamSize]; robotLabel = new JLabel[2][Rules.league.teamSize]; lanIcon = new ImageIcon[2][Rules.league.teamSize]; robotTime = new JProgressBar[2][Rules.league.teamSize]; } for (int i=0; i<2; i++) { robots[i] = new JPanel(); robots[i].setLayout(new GridLayout(robot[i].length, 1, 0, 10)); robots[i].setOpaque(false); for (int j=0; j<robot[i].length; j++) { robot[i][j] = new Button(); robotLabel[i][j] = new JLabel(); robotLabel[i][j].setHorizontalAlignment(JLabel.CENTER); lanIcon[i][j] = lanUnknown; robotLabel[i][j].setIcon(lanIcon[i][j]); robotTime[i][j] = new JProgressBar(); robotTime[i][j].setMaximum(1000); robotTime[i][j].setVisible(false); TotalScaleLayout robotLayout = new TotalScaleLayout(robot[i][j]); robot[i][j].setLayout(robotLayout); robotLayout.add(.1, .1, .8, .5, robotLabel[i][j]); robotLayout.add(.1, .7, .8, .2, robotTime[i][j]); robots[i].add(robot[i][j]); } } // team timeOut = new JToggleButton[2]; out = new JButton[2]; for (int i=0; i<2; i++) { timeOut[i] = new ToggleButton(TIMEOUT); out[i] = new JButton(OUT); } if (Rules.league instanceof SPL) { stuck = new Button[2]; for (int i=0; i<2; i++) { stuck[i] = new Button(); } } //--mid-- // time clockReset = new ImageButton(clockImgReset.getImage()); clockReset.setOpaque(false); clockReset.setBorder(null); if (Rules.league.lostTime) { clockContainer = new ImagePanel(new ImageIcon(ICONS_PATH+BACKGROUND_CLOCK_SMALL).getImage()); } else { clockContainer = new ImagePanel(new ImageIcon(ICONS_PATH+BACKGROUND_CLOCK).getImage()); } clockContainer.setOpaque(false); clock = new JLabel("10:00"); clock.setForeground(Color.WHITE); clock.setHorizontalAlignment(JLabel.CENTER); clockPause = new ImageButton(clockImgReset.getImage()); clockPause.setOpaque(false); clockPause.setBorder(null); clockSub = new JLabel("0:00"); clockSub.setHorizontalAlignment(JLabel.CENTER); incGameClock = new ImageButton(clockImgPlus.getImage()); incGameClock.setOpaque(false); incGameClock.setBorder(null); if (!Rules.league.overtime) { firstHalf = new ToggleButton(FIRST_HALF); firstHalf.setSelected(true); secondHalf = new ToggleButton(SECOND_HALF); penaltyShoot = new ToggleButton(PENALTY_SHOOT); refereeTimeout = new ToggleButton(REFEREE_TIMEOUT); halfGroup = new ButtonGroup(); halfGroup.add(firstHalf); halfGroup.add(secondHalf); halfGroup.add(penaltyShoot); if (Rules.league.isRefereeTimeoutAvailable) { halfGroup.add(refereeTimeout); } } else { firstHalf = new ToggleButton(FIRST_HALF_SHORT); firstHalf.setSelected(true); secondHalf = new ToggleButton(SECOND_HALF_SHORT); firstHalfOvertime = new ToggleButton(FIRST_HALF_OVERTIME); secondHalfOvertime = new ToggleButton(SECOND_HALF_OVERTIME); penaltyShoot = new ToggleButton(PENALTY_SHOOT_SHORT); refereeTimeout = new ToggleButton(REFEREE_TIMEOUT); halfGroup = new ButtonGroup(); halfGroup.add(firstHalf); halfGroup.add(secondHalf); halfGroup.add(firstHalfOvertime); halfGroup.add(secondHalfOvertime); halfGroup.add(penaltyShoot); if (Rules.league.isRefereeTimeoutAvailable) { halfGroup.add(refereeTimeout); } } // state initial = new ToggleButton(STATE_INITIAL); initial.setSelected(true); ready = new ToggleButton(STATE_READY); set = new ToggleButton(STATE_SET); play = new ToggleButton(STATE_PLAY); finish = new ToggleButton(STATE_FINISH); stateGroup = new ButtonGroup(); stateGroup.add(initial); stateGroup.add(ready); stateGroup.add(set); stateGroup.add(play); stateGroup.add(finish); // penalties if (Rules.league instanceof SPL) { pen = new JToggleButton[10]; pen[0] = new ToggleButton(PEN_PUSHING); pen[1] = new ToggleButton(PEN_LEAVING); pen[2] = new ToggleButton(PEN_INACTIVE); pen[3] = new ToggleButton(PEN_DEFENDER); pen[4] = new ToggleButton(PEN_MOTION_IN_SET); pen[5] = new ToggleButton(PEN_KICK_OFF_GOAL); pen[6] = new ToggleButton(PEN_BALL_CONTACT); pen[7] = new ToggleButton(PEN_PICKUP); pen[8] = new ToggleButton(Rules.league.dropInPlayerMode ? TEAMMATE_PUSHING : PEN_COACH_MOTION); pen[9] = new ToggleButton(PEN_SUBSTITUTE); } else if (Rules.league instanceof HL) { pen = new JToggleButton[7]; pen[0] = new ToggleButton(PEN_MANIPULATION); pen[1] = new ToggleButton(PEN_PHYSICAL); pen[2] = new ToggleButton(PEN_ATTACK); pen[3] = new ToggleButton(PEN_DEFENSE); pen[4] = new ToggleButton(PEN_PICKUP_INCAPABLE); pen[5] = new ToggleButton(PEN_SERVICE); pen[6] = new ToggleButton(PEN_SUBSTITUTE); dropBall = new Button(DROP_BALL); } //--bottom-- // log log = new JPanel(); log.setOpaque(false); log.setLayout(new GridLayout(1, ActionBoard.MAX_NUM_UNDOS_AT_ONCE-1, 10, 0)); undo = new JToggleButton[ActionBoard.MAX_NUM_UNDOS_AT_ONCE-1]; for (int i=undo.length-1; i>=0; i--) { undo[i] = new ToggleButton(); undo[i].setVisible(false); log.add(undo[i]); } cancelUndo = new Button(CANCEL); cancelUndo.setVisible(false); //--layout-- TotalScaleLayout layout = new TotalScaleLayout(this); setLayout(layout); layout.add(0, 0, .3, .04, name[0]); layout.add(.7, 0, .3, .04, name[1]); layout.add(.01, .05, .08, .07, goalInc[0]); layout.add(.91, .05, .08, .07, goalInc[1]); layout.add(.01, .13, .08, .06, goalDec[0]); layout.add(.91, .13, .08, .06, goalDec[1]); layout.add(.17, .05, .12, .04, kickOff[0]); layout.add(.71, .05, .12, .04, kickOff[1]); layout.add(.21, .09, .08, .07, goals[0]); layout.add(.71, .09, .08, .07, goals[1]); layout.add(.21, .16, .08, .04, pushes[0]); layout.add(.71, .16, .08, .04, pushes[1]); layout.add(.01, .21, .28, .55, robots[0]); layout.add(.71, .21, .28, .55, robots[1]); if (Rules.league instanceof SPL && !Rules.league.dropInPlayerMode) { layout.add(.01, .77, .09, .09, timeOut[0]); layout.add(.9, .77, .09, .09, timeOut[1]); layout.add(.11, .77, .08, .09, stuck[0]); layout.add(.81, .77, .08, .09, stuck[1]); layout.add(.20, .77, .09, .09, out[0]); layout.add(.71, .77, .09, .09, out[1]); } else { if (Rules.league instanceof SPL) { layout.add(.01, .77, .135, .09, stuck[0]); layout.add(.855, .77, .135, .09, stuck[1]); } else { layout.add(.01, .77, .135, .09, timeOut[0]); layout.add(.855, .77, .135, .09, timeOut[1]); } layout.add(.155, .77, .135, .09, out[0]); layout.add(.71, .77, .135, .09, out[1]); } layout.add(.31, .0, .08, .11, clockReset); layout.add(.4, .012, .195, .10, clock); layout.add(.61, .0, .08, .11, clockPause); layout.add(.4, .11, .2, .07, clockSub); if (Rules.league.lostTime) { layout.add(.590, .0, .03, .11, incGameClock); layout.add(.4, .0, .195, .11, clockContainer); } else{ layout.add(.4, .0, .2, .11, clockContainer); } if (!Rules.league.overtime) { if (Rules.league.isRefereeTimeoutAvailable && !Rules.league.dropInPlayerMode) { layout.add(.31, .19, .09, .06, firstHalf); layout.add(.407, .19, .09, .06, secondHalf); layout.add(.503, .19, .09, .06, penaltyShoot); layout.add(.60, .19, .09, .06, refereeTimeout); } else { // no referee timeout in dropInPlayerMode is not supported! layout.add(.31, .19, .12, .06, firstHalf); layout.add(.44, .19, .12, .06, secondHalf); layout.add(.57, .19, .12, .06, Rules.league.dropInPlayerMode ? refereeTimeout : penaltyShoot); } } else { if (Rules.league.isRefereeTimeoutAvailable) { layout.add(.31, .19, .06, .06, firstHalf); layout.add(.375, .19, .06, .06, secondHalf); layout.add(.439, .19, .06, .06, firstHalfOvertime); layout.add(.501, .19, .06, .06, secondHalfOvertime); layout.add(.565, .19, .06, .06, penaltyShoot); layout.add(.63, .19, .06, .06, refereeTimeout); } else { layout.add(.31, .19, .07, .06, firstHalf); layout.add(.3875, .19, .07, .06, secondHalf); layout.add(.465, .19, .07, .06, firstHalfOvertime); layout.add(.5425, .19, .07, .06, secondHalfOvertime); layout.add(.62, .19, .07, .06, penaltyShoot); } } layout.add(.31, .26, .07, .08, initial); layout.add(.3875, .26, .07, .08, ready); layout.add(.465, .26, .07, .08, set); layout.add(.5425, .26, .07, .08, play); layout.add(.62, .26, .07, .08, finish); if (Rules.league instanceof SPL) { layout.add(.31, .37, .185, .08, pen[0]); layout.add(.505, .37, .185, .08, pen[1]); layout.add(.31, .47, .185, .08, pen[2]); layout.add(.505, .47, .185, .08, pen[3]); layout.add(.31, .57, .185, .08, pen[4]); layout.add(.505, .57, .185, .08, pen[5]); layout.add(.31, .67, .185, .08, pen[6]); layout.add(.505, .67, .185, .08, pen[7]); layout.add(.31, .77, .185, .08, pen[8]); if (Rules.league.teamSize > Rules.league.robotsPlaying) { layout.add(.505, .77, .185, .08, pen[9]); } } else if (Rules.league instanceof HL) { layout.add(.31, .38, .185, .08, pen[0]); layout.add(.505, .38, .185, .08, pen[1]); layout.add(.31, .48, .185, .08, pen[2]); layout.add(.505, .48, .185, .08, pen[3]); layout.add(.31, .58, .185, .08, pen[4]); layout.add(.505, .58, .185, .08, pen[5]); layout.add(.31, .68, .185, .08, pen[6]); layout.add(.31, .78, .38, .08, dropBall); } layout.add(.08, .88, .84, .11, log); layout.add(.925, .88, .07, .11, cancelUndo); layout.add(0, 0, .3, .87, side[0]); layout.add(.3, 0, .4, .87, mid); layout.add(.7, 0, .3, .87, side[1]); layout.add(0, .87, 1, .132, bottom); //--listener-- for (int i=0; i<2; i++) { goalDec[i].addActionListener(ActionBoard.goalDec[i]); goalInc[i].addActionListener(ActionBoard.goalInc[i]); kickOff[i].addActionListener(ActionBoard.kickOff[i]); for (int j=0; j<robot[i].length; j++) { robot[i][j].addActionListener(ActionBoard.robot[i][j]); } timeOut[i].addActionListener(ActionBoard.timeOut[i]); out[i].addActionListener(ActionBoard.out[i]); if (Rules.league instanceof SPL) { stuck[i].addActionListener(ActionBoard.stuck[i]); } } refereeTimeout.addActionListener(ActionBoard.refereeTimeout); initial.addActionListener(ActionBoard.initial); ready.addActionListener(ActionBoard.ready); set.addActionListener(ActionBoard.set); play.addActionListener(ActionBoard.play); finish.addActionListener(ActionBoard.finish); clockReset.addActionListener(ActionBoard.clockReset); clockPause.addActionListener(ActionBoard.clockPause); if (Rules.league.lostTime) { incGameClock.addActionListener(ActionBoard.incGameClock); } firstHalf.addActionListener(ActionBoard.firstHalf); secondHalf.addActionListener(ActionBoard.secondHalf); if (Rules.league.overtime) { firstHalfOvertime.addActionListener(ActionBoard.firstHalfOvertime); secondHalfOvertime.addActionListener(ActionBoard.secondHalfOvertime); } penaltyShoot.addActionListener(ActionBoard.penaltyShoot); if (Rules.league instanceof SPL) { pen[0].addActionListener(ActionBoard.pushing); pen[1].addActionListener(ActionBoard.leaving); pen[2].addActionListener(ActionBoard.inactive); pen[3].addActionListener(ActionBoard.defender); pen[4].addActionListener(ActionBoard.motionInSet); pen[5].addActionListener(ActionBoard.kickOffGoal); pen[6].addActionListener(ActionBoard.ballContact); pen[7].addActionListener(ActionBoard.pickUp); pen[8].addActionListener(Rules.league.dropInPlayerMode ? ActionBoard.teammatePushing : ActionBoard.coachMotion); pen[9].addActionListener(ActionBoard.substitute); } else if (Rules.league instanceof HL) { pen[0].addActionListener(ActionBoard.ballManipulation); pen[1].addActionListener(ActionBoard.pushing); pen[2].addActionListener(ActionBoard.attack); pen[3].addActionListener(ActionBoard.defense); pen[4].addActionListener(ActionBoard.pickUpHL); pen[5].addActionListener(ActionBoard.serviceHL); pen[6].addActionListener(ActionBoard.substitute); dropBall.addActionListener(ActionBoard.dropBall); } for (int i=0; i<undo.length; i++) { undo[i].addActionListener(ActionBoard.undo[i+1]); } cancelUndo.addActionListener(ActionBoard.cancelUndo); //fullscreen if (fullscreen) { setUndecorated(true); GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().setFullScreenWindow(this); if (IS_APPLE_JAVA) { setVisible(false); // without this, keyboard input is missing on OS X } } setVisible(true); } /** * @author Michel Bartsch * * This is a normal JPanel, but it has a background image. */ class ImagePanel extends JPanel { private static final long serialVersionUID = 1L; /** The image that is shown in the background. */ private Image image; /** * Creates a new ImagePanel. * * @param image The Image to be shown in the background. */ public ImagePanel(Image image) { this.image = image; } /** * Changes the background image. * * @param image Changes the image to this one. */ public void setImage(Image image) { this.image = image; } /** * Paints this Component, should be called automatically. * * @param g This components graphical content. */ @Override public void paintComponent(Graphics g) { if (super.isOpaque()) { g.setColor(Color.WHITE); g.fillRect(0, 0, getWidth(), getHeight()); } g.drawImage(image, 0, 0, getWidth(), getHeight(), null); } } /** * @author Michel Bartsch * * This is a normal JButton, but it has a background image. */ class ImageButton extends JButton { private static final long serialVersionUID = 1L; /** The image that is shown in the background. */ private Image image; /** * Creates a new ImageButton. * * @param image The Image to be shown in the background. */ public ImageButton(Image image) { this.image = image; } /** * Changes the background image. * * @param image Changes the image to this one. */ public void setImage(Image image) { this.image = image; } /** * Paints this Component, should be called automatically. * * @param g This components graphical content. */ @Override public void paintComponent(Graphics g) { if (super.isOpaque()) { g.clearRect(0, 0, getWidth(), getHeight()); } g.drawImage(image, 0, 0, getWidth(), getHeight(), null); } } /** * This is called by the EventHandler after an action has been performed. * Here the GUI should update it`s view based on the data parameter. * There are three additional sources of information that can be used here: * 1. The RobotWatcher, you can ask him for the robots online-status. * 2. The last events from the EventHandler, but you should try to avoid * this for less dependencies between actions and GUI (view and control). * 3. The actions isLegal method to enable or disable buttons. * This method should never have other effects than updating the view! * * @param data The current data (model) the GUI should view. */ @Override public void update(AdvancedData data) { updateClock(data); updateHalf(data); updateColor(data); updateState(data); updateGoal(data); updateKickoff(data); updatePushes(data); updateTimeOut(data); updateRefereeTimeout(data); updateOut(data); if (Rules.league instanceof SPL) { updateGlobalStuck(data); updatePenaltiesSPL(data); } else if (Rules.league instanceof HL) { updatePenaltiesHL(data); updateDropBall(data); } updateRobots(data); updateUndo(data); repaint(); } /** * Always update fonts before drawing. * * @param g The graphics context to draw to. */ @Override public void paint(Graphics g) { updateFonts(); super.paint(g); } /** * Updates the clock. * * @param data The current data (model) the GUI should view. */ private void updateClock(AdvancedData data) { clock.setText(formatTime(data.getRemainingGameTime(true))); Integer secondaryTime = data.getSecondaryTime(KICKOFF_BLOCKED_HIGHLIGHT_SECONDS - 1); if (secondaryTime != null) { if (data.gameState == GameControlData.STATE_PLAYING) { clockSub.setText(formatTime(Math.max(0, secondaryTime))); clockSub.setForeground(secondaryTime <= 0 && clockSub.getForeground() != COLOR_HIGHLIGHT ? COLOR_HIGHLIGHT : Color.BLACK); } else { clockSub.setText(formatTime(secondaryTime)); clockSub.setForeground(Color.BLACK); } } else { clockSub.setText(""); clockSub.setForeground(Color.BLACK); } ImageIcon tmp; if (ActionBoard.clock.isClockRunning(data)) { tmp = clockImgPause; } else { tmp = clockImgPlay; } clockPause.setImage(tmp.getImage()); clockReset.setVisible(ActionBoard.clockReset.isLegal(data)); clockPause.setVisible(ActionBoard.clockPause.isLegal(data)); if (Rules.league.lostTime) { incGameClock.setEnabled(ActionBoard.incGameClock.isLegal(data)); } } /** * Updates the half. * * @param data The current data (model) the GUI should view. */ private void updateHalf(AdvancedData data) { for (int i=0; i<2; i++) { name[i].setText(Teams.getNames(false)[data.team[i].teamNumber]); } firstHalf.setEnabled(ActionBoard.firstHalf.isLegal(data)); secondHalf.setEnabled(ActionBoard.secondHalf.isLegal(data)); if (Rules.league.overtime) { firstHalfOvertime.setEnabled(ActionBoard.firstHalfOvertime.isLegal(data)); secondHalfOvertime.setEnabled(ActionBoard.secondHalfOvertime.isLegal(data)); } penaltyShoot.setEnabled(ActionBoard.penaltyShoot.isLegal(data)); firstHalf.setSelected((data.secGameState == GameControlData.STATE2_NORMAL) && (data.firstHalf == GameControlData.C_TRUE)); secondHalf.setSelected((data.secGameState == GameControlData.STATE2_NORMAL) && (data.firstHalf != GameControlData.C_TRUE)); if (Rules.league.overtime) { firstHalfOvertime.setSelected((data.secGameState == GameControlData.STATE2_OVERTIME) && (data.firstHalf == GameControlData.C_TRUE)); secondHalfOvertime.setSelected((data.secGameState == GameControlData.STATE2_OVERTIME) && (data.firstHalf != GameControlData.C_TRUE)); } penaltyShoot.setSelected(data.secGameState == GameControlData.STATE2_PENALTYSHOOT || data.previousSecGameState == GameControlData.STATE2_PENALTYSHOOT); } /** * Updates left and right background picture. * * @param data The current data (model) the GUI should view. */ private void updateColor(AdvancedData data) { for (int i=0; i<2; i++) { name[i].setForeground(Rules.league.teamColor[data.team[i].teamColor]); side[i].setImage(backgroundSide[i][data.team[i].teamColor].getImage()); } } /** * Updates the state. * * @param data The current data (model) the GUI should view. */ private void updateState(AdvancedData data) { initial.setEnabled(ActionBoard.initial.isLegal(data)); ready.setEnabled(ActionBoard.ready.isLegal(data)); set.setEnabled(ActionBoard.set.isLegal(data)); play.setEnabled(ActionBoard.play.isLegal(data)); finish.setEnabled(ActionBoard.finish.isLegal(data)); switch (data.gameState) { case GameControlData.STATE_INITIAL: initial.setSelected(true); break; case GameControlData.STATE_READY: ready.setSelected(true); break; case GameControlData.STATE_SET: set.setSelected(true); break; case GameControlData.STATE_PLAYING: play.setSelected(true); break; case GameControlData.STATE_FINISHED: finish.setSelected(true); break; } highlight(finish, (data.gameState != GameControlData.STATE_FINISHED) && (data.getRemainingGameTime(true) <= FINISH_HIGHLIGHT_SECONDS) && (finish.getBackground() != COLOR_HIGHLIGHT)); } /** * Updates the goal. * * @param data The current data (model) the GUI should view. */ private void updateGoal(AdvancedData data) { for (int i=0; i<2; i++) { goals[i].setText(""+data.team[i].score); goalInc[i].setEnabled(ActionBoard.goalInc[i].isLegal(data)); goalDec[i].setVisible(ActionBoard.goalDec[i].isLegal(data)); } } /** * Updates the kickoff. * * @param data The current data (model) the GUI should view. */ private void updateKickoff(AdvancedData data) { if (data.kickOffTeam == GameControlData.DROPBALL) { kickOff[2].setSelected(true); } else { kickOff[data.team[0].teamNumber == data.kickOffTeam ? 0 : 1].setSelected(true); } for (int i=0; i<2; i++) { kickOff[i].setEnabled(ActionBoard.kickOff[i].isLegal(data)); if (data.secGameState != GameControlData.STATE2_PENALTYSHOOT && data.previousSecGameState != GameControlData.STATE2_PENALTYSHOOT) { kickOff[i].setText(KICKOFF); } else { kickOff[i].setText(KICKOFF_PENALTY_SHOOTOUT); } } } /** * Updates the pushes. * * @param data The current data (model) the GUI should view. */ private void updatePushes(AdvancedData data) { for (int i=0; i<2; i++) { if (data.secGameState != GameControlData.STATE2_PENALTYSHOOT && data.previousSecGameState != GameControlData.STATE2_PENALTYSHOOT) { if (Rules.league.pushesToEjection == null || Rules.league.pushesToEjection.length == 0) { pushes[i].setText(""); } else { pushes[i].setText(PUSHES+": "+data.pushes[i]); } } else { pushes[i].setText((i == 0 && (data.gameState == GameControlData.STATE_SET || data.gameState == GameControlData.STATE_PLAYING) ? SHOT : SHOTS)+": "+data.team[i].penaltyShot); } } } /** * Updates the robots. * * @param data The current data (model) the GUI should view. */ private void updateRobots(AdvancedData data) { RobotOnlineStatus[][] onlineStatus = RobotWatcher.updateRobotOnlineStatus(); for (int i=0; i<robot.length; i++) { for (int j=0; j<robot[i].length; j++) { if (ActionBoard.robot[i][j].isCoach(data)) { if (data.team[i].coach.penalty == PlayerInfo.PENALTY_SPL_COACH_MOTION) { robot[i][j].setEnabled(false); robotLabel[i][j].setText(EJECTED); } else { robotLabel[i][j].setText(Rules.league.teamColorName[data.team[i].teamColor]+" "+COACH); } } else { if (data.team[i].player[j].penalty != PlayerInfo.PENALTY_NONE) { if (!data.ejected[i][j]) { int seconds = data.getRemainingPenaltyTime(i, j); boolean pickup = ((Rules.league instanceof SPL && data.team[i].player[j].penalty == PlayerInfo.PENALTY_SPL_REQUEST_FOR_PICKUP) || (Rules.league instanceof HL && ( data.team[i].player[j].penalty == PlayerInfo.PENALTY_HL_PICKUP_OR_INCAPABLE || data.team[i].player[j].penalty == PlayerInfo.PENALTY_HL_SERVICE )) ); boolean illegalMotion = Rules.league instanceof SPL && data.team[i].player[j].penalty == PlayerInfo.PENALTY_SPL_ILLEGAL_MOTION_IN_SET; if (seconds == 0) { if (pickup) { robotLabel[i][j].setText(Rules.league.teamColorName[data.team[i].teamColor]+" "+(j+1)+" ("+PEN_PICKUP+")"); highlight(robot[i][j], true); } else if (illegalMotion) { robotLabel[i][j].setText(Rules.league.teamColorName[data.team[i].teamColor]+" "+(j+1)+" ("+PEN_MOTION_IN_SET_SHORT+")"); highlight(robot[i][j], true); } else if (data.team[i].player[j].penalty == PlayerInfo.PENALTY_SUBSTITUTE) { robotLabel[i][j].setText(Rules.league.teamColorName[data.team[i].teamColor]+" "+(j+1)+" ("+PEN_SUBSTITUTE_SHORT+")"); highlight(robot[i][j], false); } else if (!(Rules.league instanceof SPL) || !(data.team[i].player[j].penalty == PlayerInfo.PENALTY_SPL_COACH_MOTION)) { robotLabel[i][j].setText(Rules.league.teamColorName[data.team[i].teamColor]+" "+(j+1)+": "+formatTime(seconds)); highlight(robot[i][j], seconds <= UNPEN_HIGHLIGHT_SECONDS && robot[i][j].getBackground() != COLOR_HIGHLIGHT); } } else { robotLabel[i][j].setText(Rules.league.teamColorName[data.team[i].teamColor]+" "+(j+1)+": "+formatTime(seconds)+(pickup ? " (P)" : "")); highlight(robot[i][j], seconds <= UNPEN_HIGHLIGHT_SECONDS && robot[i][j].getBackground() != COLOR_HIGHLIGHT); } int penTime = (seconds + data.getSecondsSince(data.whenPenalized[i][j])); if (seconds != 0) { robotTime[i][j].setValue(1000 * seconds / penTime); } robotTime[i][j].setVisible(seconds != 0); } else { robotLabel[i][j].setText(EJECTED); robotTime[i][j].setVisible(false); highlight(robot[i][j], false); } } else { robotLabel[i][j].setText(Rules.league.teamColorName[data.team[i].teamColor]+" "+(j+1)); robotTime[i][j].setVisible(false); highlight(robot[i][j], false); } } robot[i][j].setEnabled(ActionBoard.robot[i][j].isLegal(data)); ImageIcon currentLanIcon; if (onlineStatus[i][j] == RobotOnlineStatus.ONLINE) { currentLanIcon = lanOnline; } else if (onlineStatus[i][j] == RobotOnlineStatus.HIGH_LATENCY) { currentLanIcon = lanHighLatency; } else if (onlineStatus[i][j] == RobotOnlineStatus.OFFLINE) { currentLanIcon = lanOffline; } else { currentLanIcon = lanUnknown; } robotLabel[i][j].setIcon(currentLanIcon); } } } /** * Updates the time-out. * * @param data The current data (model) the GUI should view. */ private void updateTimeOut(AdvancedData data) { for (int i=0; i<2; i++) { if (!data.timeOutActive[i]) { timeOut[i].setSelected(false); highlight(timeOut[i], false); } else { boolean shouldHighlight = (data.getRemainingSeconds(data.whenCurrentGameStateBegan, Rules.league.timeOutTime) < TIMEOUT_HIGHLIGHT_SECONDS) && (timeOut[i].getBackground() != COLOR_HIGHLIGHT); timeOut[i].setSelected(!IS_OSX || !shouldHighlight); highlight(timeOut[i], shouldHighlight); } timeOut[i].setEnabled(ActionBoard.timeOut[i].isLegal(data)); } } private void updateRefereeTimeout(AdvancedData data) { refereeTimeout.setSelected(data.refereeTimeout); refereeTimeout.setEnabled(ActionBoard.refereeTimeout.isLegal(data)); } /** * Updates the global game stuck. * * @param data The current data (model) the GUI should view. */ private void updateGlobalStuck(AdvancedData data) { for (int i=0; i<2; i++) { if (data.gameState == GameControlData.STATE_PLAYING && data.getRemainingSeconds(data.whenCurrentGameStateBegan, Rules.league.kickoffTime + Rules.league.minDurationBeforeStuck) > 0) { stuck[i].setEnabled(false); stuck[i].setText("<font color=#808080>"+STUCK); } else { stuck[i].setEnabled(ActionBoard.stuck[i].isLegal(data)); stuck[i].setText((ActionBoard.stuck[i].isLegal(data) ? "<font color=#000000>" : "<font color=#808080>")+STUCK); } } } /** * Updates the dropped-ball button. * * @param data The current data (model) the GUI should view. */ private void updateDropBall(AdvancedData data) { dropBall.setEnabled(ActionBoard.dropBall.isLegal(data)); } /** * Updates the out. * * @param data The current data (model) the GUI should view. */ private void updateOut(AdvancedData data) { for (int i=0; i<2; i++) { out[i].setEnabled(ActionBoard.out[i].isLegal(data)); } } /** * Updates the SPL penalties. * * @param data The current data (model) the GUI should view. */ private void updatePenaltiesSPL(AdvancedData data) { pen[0].setEnabled(ActionBoard.pushing.isLegal(data)); pen[1].setEnabled(ActionBoard.leaving.isLegal(data)); pen[2].setEnabled(ActionBoard.inactive.isLegal(data)); pen[2].setText("<html><center>" +(ActionBoard.inactive.isLegal(data) ? "<font color=#000000>" : "<font color=#808080>") +PEN_INACTIVE); pen[3].setEnabled(ActionBoard.defender.isLegal(data)); pen[4].setEnabled(ActionBoard.motionInSet.isLegal(data)); pen[5].setEnabled(ActionBoard.kickOffGoal.isLegal(data)); pen[6].setEnabled(ActionBoard.ballContact.isLegal(data)); pen[7].setEnabled(ActionBoard.pickUp.isLegal(data)); pen[8].setEnabled(Rules.league.dropInPlayerMode ? ActionBoard.teammatePushing.isLegal(data) : ActionBoard.coachMotion.isLegal(data)); pen[9].setEnabled(ActionBoard.substitute.isLegal(data)); GCAction hightlightEvent = EventHandler.getInstance().lastUIEvent; pen[0].setSelected(hightlightEvent == ActionBoard.pushing); pen[1].setSelected(hightlightEvent == ActionBoard.leaving); pen[2].setSelected(hightlightEvent == ActionBoard.inactive); pen[3].setSelected(hightlightEvent == ActionBoard.defender); pen[5].setSelected(hightlightEvent == ActionBoard.kickOffGoal); pen[6].setSelected(hightlightEvent == ActionBoard.ballContact); pen[7].setSelected(hightlightEvent == ActionBoard.pickUp); pen[8].setSelected(Rules.league.dropInPlayerMode ? hightlightEvent == ActionBoard.teammatePushing : hightlightEvent == ActionBoard.coachMotion); pen[9].setSelected(hightlightEvent == ActionBoard.substitute); // Handle quick select for ILLEGAL_MOTION_IN_SET if (pen[4].isEnabled()) { boolean otherButtonSelected = false; for (JToggleButton button : pen) { otherButtonSelected |= button != pen[4] && button.isSelected(); } for (JToggleButton button : undo) { otherButtonSelected |= button.isSelected(); } pen[4].setSelected(!otherButtonSelected); if (!otherButtonSelected) { EventHandler.getInstance().lastUIEvent = ActionBoard.motionInSet; } } else { pen[4].setSelected(EventHandler.getInstance().lastUIEvent == ActionBoard.motionInSet); } } /** * Updates the HL penalties. * * @param data The current data (model) the GUI should view. */ private void updatePenaltiesHL(AdvancedData data) { pen[0].setEnabled(ActionBoard.ballManipulation.isLegal(data)); pen[1].setEnabled(ActionBoard.pushing.isLegal(data)); pen[2].setEnabled(ActionBoard.attack.isLegal(data)); pen[3].setEnabled(ActionBoard.defense.isLegal(data)); pen[4].setEnabled(ActionBoard.pickUpHL.isLegal(data)); pen[5].setEnabled(ActionBoard.serviceHL.isLegal(data)); pen[6].setEnabled(ActionBoard.substitute.isLegal(data)); GCAction hightlightEvent = EventHandler.getInstance().lastUIEvent; pen[0].setSelected(hightlightEvent == ActionBoard.ballManipulation); pen[1].setSelected(hightlightEvent == ActionBoard.pushing); pen[2].setSelected(hightlightEvent == ActionBoard.attack); pen[3].setSelected(hightlightEvent == ActionBoard.defense); pen[4].setSelected(hightlightEvent == ActionBoard.pickUpHL); pen[5].setSelected(hightlightEvent == ActionBoard.serviceHL); pen[6].setSelected(hightlightEvent == ActionBoard.substitute); } /** * Updates the timeline/undo. * * @param data The current data (model) the GUI should view. */ private void updateUndo(AdvancedData data) { GCAction highlightEvent = EventHandler.getInstance().lastUIEvent; String[] undos = Log.getLast(ActionBoard.MAX_NUM_UNDOS_AT_ONCE); boolean undoFromHere = false; for (int i=undo.length - 1; i >= 0; i--) { undo[i].setVisible(!undos[i].equals("")); undo[i].setEnabled(!undos[i].contains(" vs ")); if ((highlightEvent == ActionBoard.undo[i+1]) && (!ActionBoard.undo[i+1].executed)) { undoFromHere = true; } if (undoFromHere) { undo[i].setText("<html><center>Undo '"+undos[i] + "\'?"); undo[i].setSelected(true); } else { undo[i].setText("<html><center>"+undos[i]); undo[i].setSelected(false); } } cancelUndo.setVisible(undoFromHere); } private void updateFonts() { double size = Math.min((getWidth()/(double)WINDOW_WIDTH), (getHeight()/(double)WINDOW_HEIGHT)); if (size == lastSize) { return; } lastSize = size; titleFont = new Font(STANDARD_FONT, Font.PLAIN, (int)(TITLE_FONT_SIZE*(size))); standardFont = new Font(STANDARD_FONT, Font.PLAIN, (int)(STANDARD_FONT_SIZE*(size))); goalsFont = new Font(STANDARD_FONT, Font.PLAIN, (int)(GOALS_FONT_SIZE*(size))); timeFont = new Font(STANDARD_FONT, Font.PLAIN, (int)(TIME_FONT_SIZE*(size))); timeSubFont = new Font(STANDARD_FONT, Font.PLAIN, (int)(TIME_SUB_FONT_SIZE*(size))); timeoutFont = new Font(STANDARD_FONT, Font.PLAIN, (int)(TIMEOUT_FONT_SIZE*(size))); stateFont = new Font(STANDARD_FONT, Font.PLAIN, (int)(STATE_FONT_SIZE*(size))); for (int i=0; i<=1; i++) { name[i].setFont(titleFont); goalInc[i].setFont(standardFont); goalDec[i].setFont(standardFont); kickOff[i].setFont(standardFont); goals[i].setFont(goalsFont); pushes[i].setFont(standardFont); for (int j=0; j<robot[i].length; j++) { robotLabel[i][j].setFont(titleFont); } timeOut[i].setFont(timeoutFont); out[i].setFont(timeoutFont); if (Rules.league instanceof SPL) { stuck[i].setFont(timeoutFont); } } clock.setFont(timeFont); clockSub.setFont(timeSubFont); firstHalf.setFont(timeoutFont); secondHalf.setFont(timeoutFont); if (Rules.league.overtime) { firstHalfOvertime.setFont(timeoutFont); secondHalfOvertime.setFont(timeoutFont); } penaltyShoot.setFont(timeoutFont); if (Rules.league.isRefereeTimeoutAvailable) { refereeTimeout.setFont(timeoutFont); } initial.setFont(stateFont); ready.setFont(stateFont); set.setFont(stateFont); play.setFont(stateFont); finish.setFont(stateFont); for (int i=0; i<pen.length; i++) { pen[i].setFont(standardFont); } if (dropBall != null) { dropBall.setFont(standardFont); } for (int i=0; i<undo.length; i++) { undo[i].setFont(timeoutFont); } cancelUndo.setFont(standardFont); } /** * Set the given button highlighted or normal. * * @param button The button to highlight. * @param highlight If the button should be highlighted. */ private void highlight(AbstractButton button, boolean highlight) { button.setBackground(highlight ? COLOR_HIGHLIGHT : COLOR_STANDARD); if (IS_OSX) { button.setOpaque(highlight); button.setBorderPainted(!highlight); } } private String formatTime(int seconds) { int displaySeconds = Math.abs(seconds) % 60; int displayMinutes = Math.abs(seconds) / 60; return (seconds < 0 ? "-" : "") + String.format("%02d:%02d", displayMinutes, displaySeconds); } }