package eu.hgross.blaubot.ui; import java.awt.Component; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import javax.swing.DefaultListModel; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import eu.hgross.blaubot.core.BlaubotDevice; import eu.hgross.blaubot.core.BlaubotFactory; import eu.hgross.blaubot.core.BlaubotKingdom; import eu.hgross.blaubot.core.BlaubotServer; import eu.hgross.blaubot.core.IBlaubotServerLifeCycleListener; import eu.hgross.blaubot.core.acceptor.ConnectionMetaDataDTO; import eu.hgross.blaubot.core.acceptor.IBlaubotConnectionAcceptor; import eu.hgross.blaubot.util.Log; /** * Shows the server state and connected kingdoms. */ public class BlaubotServerPanel extends JPanel { private static final String LOG_TAG = "StatusViewPanel"; private static final long UPDATE_INTERVAL = 350; private BlaubotServer mBlaubotServer; /** * Starts/Stops the whole server (if registered) */ private final JButton mStartStopButton; /** * The container which holds the selected kingdom's view */ private final JPanel mBlaubotKingdomViewContainer; /** * The listview of kindomgs of the split pane */ private final JList<String> mKingdomListView; /** * the model for the kindom list */ private final DefaultListModel<String> mKingdomListViewModel; /** * Shows informations about the server (acceptor, ip, ...) */ private final JLabel mServerInfoLabel; /** * Holds connected BlaubotKingdoms and their views. */ private final Map<BlaubotKingdom, BlaubotKingdomView> mBlaubotKingdomViews; /** * Schedules the updateUiTask for the start/stop button state. * Yeah it's hacky and should be refactored. */ private ScheduledExecutorService scheduledThreadPoolExecutor; /** * updates the start/stop state * TODO add listeners for start/stop to the server instead of polling */ private Runnable updateUiTask = new Runnable() { @Override public void run() { setButtonText(); } }; public BlaubotServerPanel() { super(); this.scheduledThreadPoolExecutor = Executors.newSingleThreadScheduledExecutor(); scheduledThreadPoolExecutor.scheduleAtFixedRate(updateUiTask, (long) 0, UPDATE_INTERVAL, TimeUnit.MILLISECONDS); setLayout(new GridBagLayout()); this.mBlaubotKingdomViews = new ConcurrentHashMap<>(); this.mKingdomListViewModel = new DefaultListModel<>(); this.mKingdomListView = new JList<>(mKingdomListViewModel); this.mKingdomListView.setMinimumSize(new Dimension(100, 200)); this.mKingdomListView.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); this.mKingdomListView.addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { final String selectedKingdomUniqueDeviceId = mKingdomListView.getSelectedValue(); if (selectedKingdomUniqueDeviceId == null) { return; } // find blaubot kingdom BlaubotKingdom kingdom = null; for (BlaubotKingdom k : mBlaubotKingdomViews.keySet()) { if (k.getKingDevice().getUniqueDeviceID().equals(selectedKingdomUniqueDeviceId)) { kingdom = k; } } if (kingdom == null) { return; } final BlaubotKingdomView blaubotKingdomView = mBlaubotKingdomViews.get(kingdom); mBlaubotKingdomViewContainer.removeAll(); mBlaubotKingdomViewContainer.add(blaubotKingdomView); updateUI(); } }); this.mBlaubotKingdomViewContainer = new JPanel(); this.mStartStopButton = new JButton(); this.mStartStopButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (mBlaubotServer == null) { if (Log.logWarningMessages()) { Log.w(LOG_TAG, "Blaubot not set - ignoring onClick."); } return; } mStartStopButton.setEnabled(false); if (mBlaubotServer.isStarted()) { mBlaubotServer.stopBlaubotServer(); } else { mBlaubotServer.startBlaubotServer(); } } }); this.mServerInfoLabel = new JLabel(); GridBagConstraints c = new GridBagConstraints(); c.insets = new Insets(2, 2, 2, 2); c.fill = GridBagConstraints.HORIZONTAL; c.gridx = 0; c.gridy = 0; c.gridheight = 1; c.gridwidth = 2; add(mStartStopButton, c); c.gridy++; add(mServerInfoLabel, c); c.gridwidth = 1; c.gridx = 0; c.gridy++; add(new JLabel("Connected kingdoms:"), c); c.gridx = 1; add(new JLabel("Details:"), c); c.fill = GridBagConstraints.BOTH; c.gridx = 0; c.gridy++; add(mKingdomListView, c); c.gridx = 1; add(mBlaubotKingdomViewContainer, c); } private IBlaubotServerLifeCycleListener mServerLifeCycleListener = new IBlaubotServerLifeCycleListener() { @Override public void onKingdomConnected(BlaubotKingdom kingdom) { BlaubotKingdomView bkv = new BlaubotKingdomView(); bkv.registerBlaubotKingdomInstance(kingdom); Component prev = mBlaubotKingdomViews.put(kingdom, bkv); if (prev != null) { removeView((BlaubotKingdomView) prev, kingdom); } mKingdomListViewModel.addElement(kingdom.getKingDevice().getUniqueDeviceID()); boolean nothingSelected = mKingdomListView.getSelectedIndex() == -1; if (nothingSelected) { mKingdomListView.setSelectedIndex(0); } updateUI(); } @Override public void onKingdomDisconnected(BlaubotKingdom kingdom) { System.out.println("disc: " + kingdom); Component remove = mBlaubotKingdomViews.remove(kingdom); if (remove != null) { removeView((BlaubotKingdomView) remove, kingdom); } updateUI(); } private void removeView(BlaubotKingdomView view, BlaubotKingdom kingdom) { view.unregisterBlaubotKingdomInstance(); mKingdomListViewModel.removeElement(kingdom.getKingDevice().getUniqueDeviceID()); // remove from container, if set mBlaubotKingdomViewContainer.remove(view); } }; private void setButtonText() { if (mBlaubotServer == null) { return; } final boolean started = mBlaubotServer.isStarted(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { // await either started or stopped if (started) { mStartStopButton.setText("Stop BlaubotServer"); } else { mStartStopButton.setText("Start BlaubotServer"); } mStartStopButton.setEnabled(true); } }); } public void registerBlaubotServerInstance(BlaubotServer blaubotServer) { if (this.mBlaubotServer != null) { unregisterBlaubotServerInstance(); } this.mBlaubotServer = blaubotServer; blaubotServer.addServerLifeCycleListener(mServerLifeCycleListener); setUpInfoLabel(); setButtonText(); } public void unregisterBlaubotServerInstance() { if (this.mBlaubotServer != null) { mBlaubotServer.removeServerLifeCycleListener(mServerLifeCycleListener); } this.mBlaubotServer = null; } /** * Updates the general server information textview. */ private void setUpInfoLabel() { final String labelTemplate = "" + "<html>" + " <table>" + " <tr>" + " <th>Acceptors: </th>" + " <td>%s</td>" + " </tr>" + " <tr>" + " <th>ConnectionMetaData: </th>" + " <td>%s</td>" + " </tr>" + " </table>" + "</html>"; SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (mBlaubotServer == null) { mServerInfoLabel.setText("No server instance registered to GUI."); } else { final List<IBlaubotConnectionAcceptor> acceptors = mBlaubotServer.getAcceptors(); final ArrayList<ConnectionMetaDataDTO> connectionMetaData = new ArrayList<>(); for (IBlaubotConnectionAcceptor acceptor : acceptors) { connectionMetaData.add(acceptor.getConnectionMetaData()); } mServerInfoLabel.setText(String.format(labelTemplate, acceptors, connectionMetaData)); } } }); } /** * Creates the gui for the server and displays the returned JFrame in a new Thread. * * @param blaubotServer the server instance * @return the JFrame that is shown. */ public static JFrame createAndshowGui(final BlaubotServer blaubotServer) { FutureTask<JFrame> futureTask = new FutureTask<JFrame>(new Callable<JFrame>() { @Override public JFrame call() throws Exception { BlaubotServerPanel serverPanel = new BlaubotServerPanel(); serverPanel.registerBlaubotServerInstance(blaubotServer); JFrame frame = new JFrame(); frame.getContentPane().setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0)); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setMinimumSize(new Dimension(900, 500)); frame.add(serverPanel); frame.pack(); frame.setVisible(true); frame.setTitle("Blaubot Server"); return frame; } }); new Thread(futureTask).start(); try { return futureTask.get(); } catch (Exception e) { throw new RuntimeException(e); } } public static void main(String[] args) throws ClassNotFoundException { final BlaubotServer websocketServer = BlaubotFactory.createBlaubotWebsocketServer(new BlaubotDevice("Server1")); BlaubotServerPanel.createAndshowGui(websocketServer); } }