package edu.harvard.econcs.turkserver.server.gui; import edu.harvard.econcs.turkserver.client.JTextFieldLimit; import edu.harvard.econcs.turkserver.client.LobbyPanel; import edu.harvard.econcs.turkserver.client.SortedListModel; import edu.harvard.econcs.turkserver.server.ExperimentControllerImpl; import edu.harvard.econcs.turkserver.server.HITWorkerImpl; import edu.harvard.econcs.turkserver.server.Lobby; import edu.harvard.econcs.turkserver.server.SessionServer; import java.awt.Component; import java.awt.Dimension; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Comparator; import java.util.Set; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.DefaultListModel; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.ListCellRenderer; import javax.swing.SwingUtilities; import javax.swing.Timer; import net.andrewmao.misc.Utils; public class ServerPanel extends JPanel implements ActionListener { private static final long serialVersionUID = -5350221106754787412L; private static final String updateStatusCmd = "UpdateStatus"; private static final String runningExpsText = "Running Experiments: "; private static final String doneExpsText = "Completed Experiments: "; private final SessionServer server; private final Lobby lobby; private JTextField statusMsg; private SortedListModel<HITWorkerImpl> userListModel; private DefaultListModel<ExperimentControllerImpl> runningExpModel; private JList<ExperimentControllerImpl> runningExpList; private DefaultListModel<ExperimentControllerImpl> doneExpModel; private JList<ExperimentControllerImpl> doneExpList; private JLabel currentUsers; private JLabel runningExpsLabel; private JLabel doneExpsLabel; private Timer timeTicker; public ServerPanel(SessionServer host, Lobby lobby) { // Put lobby on left and experiments on right super(new GridLayout(1, 2)); this.server = host; this.lobby = lobby; // Lobby JPanel lobbyPanel = new JPanel(); lobbyPanel.setLayout(new BoxLayout(lobbyPanel, BoxLayout.PAGE_AXIS)); lobbyPanel.setBorder(BorderFactory.createTitledBorder("Lobby")); JPanel statusPanel = new JPanel(); // Limit the height of this panel statusPanel.setMaximumSize(new Dimension(800, 100)); statusPanel.setLayout(new BoxLayout(statusPanel, BoxLayout.LINE_AXIS)); JLabel statusLabel = new JLabel("Message:"); statusMsg = new JTextField(40); statusMsg.setDocument(new JTextFieldLimit(80)); JButton updateStatusButton = new JButton("Update Status"); updateStatusButton.setActionCommand(updateStatusCmd); updateStatusButton.addActionListener(this); statusPanel.add(statusLabel); statusPanel.add(statusMsg); statusPanel.add(updateStatusButton); lobbyPanel.add(statusPanel); currentUsers = new JLabel(); lobbyPanel.add(currentUsers); userListModel = new SortedListModel<HITWorkerImpl>(new UsernameComparator()); JList<HITWorkerImpl> userList = new JList<>(userListModel); userList.setCellRenderer(new ServerLobbyCellRenderer()); lobbyPanel.add(new JScrollPane(userList)); // Experiments JPanel expPanel = new JPanel(); expPanel.setBorder(BorderFactory.createTitledBorder("Experiments")); expPanel.setLayout(new GridLayout(2, 1)); // Running experiments JPanel runningExpPanel = new JPanel(); runningExpPanel.setLayout(new BoxLayout(runningExpPanel, BoxLayout.PAGE_AXIS)); runningExpPanel.setBorder(BorderFactory.createEtchedBorder()); JButton btnInitiateShutdown = new JButton("Initiate Shutdown"); btnInitiateShutdown.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int selection = JOptionPane.showConfirmDialog(ServerPanel.this, "This will block new experiments from starting, " + "wait for ones in progress to finish, and then shut down server. Proceed?", "Initiate Shutdown", JOptionPane.OK_CANCEL_OPTION); if (selection == JOptionPane.OK_OPTION ) { server.shutdown(); } } }); runningExpPanel.add(btnInitiateShutdown); runningExpsLabel = new JLabel(runningExpsText + 0); runningExpPanel.add(runningExpsLabel); runningExpModel = new DefaultListModel<ExperimentControllerImpl>(); runningExpList = new JList<ExperimentControllerImpl>(runningExpModel); runningExpList.setCellRenderer(new RunningExpCellRenderer()); runningExpPanel.add(new JScrollPane(runningExpList)); // Done experiments JPanel doneExpPanel = new JPanel(); doneExpPanel.setLayout(new BoxLayout(doneExpPanel, BoxLayout.PAGE_AXIS)); doneExpPanel.setBorder(BorderFactory.createEtchedBorder()); doneExpsLabel = new JLabel(doneExpsText + 0); doneExpPanel.add(doneExpsLabel); doneExpModel = new DefaultListModel<ExperimentControllerImpl>(); doneExpList = new JList<ExperimentControllerImpl>(doneExpModel); doneExpPanel.add(new JScrollPane(doneExpList)); expPanel.add(runningExpPanel); expPanel.add(doneExpPanel); add(lobbyPanel); add(expPanel); timeTicker = new Timer(1000, this); timeTicker.start(); } @Override public void actionPerformed(ActionEvent e) { if( e.getActionCommand() != null && e.getActionCommand().equals(updateStatusCmd) ) { lobby.setMessage(statusMsg.getText()); } else if( e.getSource() == timeTicker && runningExpModel.size() > 0 ) { // Only bother with this if there are running experiments runningExpList.repaint(); } } public void updateLobbyModel() { SwingUtilities.invokeLater(new Runnable() { public void run() { Set<HITWorkerImpl> lobbyppl = lobby.getLobbyUsers(); // Update user count currentUsers.setText("Users: " + lobbyppl.size()); // Update users list userListModel.updateModel(lobbyppl); } }); } private class ServerLobbyCellRenderer extends JLabel implements ListCellRenderer<HITWorkerImpl> { private static final long serialVersionUID = -9092662058995935206L; @Override public Component getListCellRendererComponent(JList<? extends HITWorkerImpl> list, HITWorkerImpl id, int index, boolean isSelected, boolean cellHasFocus) { Object status = lobby.getStatus(id); if( status != null) setIcon( (Boolean) status == true ? LobbyPanel.ready : LobbyPanel.notReady ); setText( id.getUsername() ); // TODO render textual messages setEnabled(true); // not list.isEnabled()); otherwise the icon won't draw setFont(list.getFont()); setOpaque(true); return (JLabel) this; } } public void newExperiment(final ExperimentControllerImpl exp) { SwingUtilities.invokeLater(new Runnable() { public void run() { runningExpsLabel.setText(runningExpsText + server.getExpsInProgress()); runningExpModel.addElement(exp); } }); } private class RunningExpCellRenderer extends JLabel implements ListCellRenderer<ExperimentControllerImpl> { private static final long serialVersionUID = 5708685323712954603L; @Override public Component getListCellRendererComponent(JList<? extends ExperimentControllerImpl> list, ExperimentControllerImpl exp, int index, boolean isSelected, boolean cellHasFocus) { setText( String.format("%s %s R:%d (%d)", Utils.paddedClockString(System.currentTimeMillis() - exp.getStartTime()), exp.toString(), exp.getCurrentRound(), exp.getGroup().groupSize() )); setEnabled(true); // not list.isEnabled()); otherwise the icon won't draw setFont(list.getFont()); setOpaque(true); return (JLabel) this; } } public void finishedExperiment(final ExperimentControllerImpl exp) { SwingUtilities.invokeLater(new Runnable() { public void run() { runningExpsLabel.setText(runningExpsText + server.getExpsInProgress()); runningExpModel.removeElement(exp); doneExpsLabel.setText(doneExpsText + server.getExpsCompleted()); doneExpModel.addElement(exp); } }); } public class UsernameComparator implements Comparator<HITWorkerImpl> { @Override public int compare(HITWorkerImpl o1, HITWorkerImpl o2) { String u1 = o1.getUsername(); String u2 = o1.getUsername(); if( u1 != null ) { int comp = u1.compareTo(u2); if( comp != 0 ) return comp; } return o1.getHitId().compareTo(o2.getHitId()); } } }