package jrummikub.view.impl; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JSpinner; import javax.swing.JTabbedPane; import javax.swing.JTextField; import javax.swing.JToggleButton; import javax.swing.SpinnerNumberModel; import javax.swing.border.CompoundBorder; import javax.swing.border.EmptyBorder; import javax.swing.border.LineBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import jrummikub.control.turn.TurnControlFactory; import jrummikub.control.turn.TurnControlFactory.Type; import jrummikub.model.GameSettings; import jrummikub.model.PlayerSettings; import jrummikub.model.StoneColor; import jrummikub.util.Event; import jrummikub.util.Event1; import jrummikub.util.Event2; import jrummikub.util.IEvent; import jrummikub.util.IEvent1; import jrummikub.util.IEvent2; import jrummikub.view.ISettingsPanel; @SuppressWarnings("serial") class SettingsPanel extends JPanel implements ISettingsPanel { private JPanel playerSetupPanel; private JPanel playerSettingsViewport; private JPanel optionsPanel; private JButton addPlayerButton; private JLabel errorMessageLabel; private JButton startButton; private boolean removeButtonsEnabled = true; private List playerSettingsPanels = new ArrayList(); private JSpinner initialMeldThresholdSpinner; private JSpinner stoneSetNumberSpinner; private JSpinner highestValueSpinner; private JSpinner numberOfStonesDealtSpinner; private JSpinner jokerNumberSpinner; private JPanel colorSelectionPanel; private Map colorButtons = new HashMap(); private Event startGameEvent = new Event(); private Event addPlayerEvent = new Event(); private Event1 removePlayerEvent = new Event1(); private Event2 changePlayerColorEvent = new Event2(); private Event2 changePlayerNameEvent = new Event2(); private Event2 changePlayerTypeEvent = new Event2(); private Event1 changeInitialMeldThresholdEvent = new Event1(); private Event1 changeJokerNumberEvent = new Event1(); private Event1 changeStoneSetNumberEvent = new Event1(); private Event1 changeNumberOfStonesDealtEvent = new Event1(); private Event1 changeHighestValueEvent = new Event1(); private Event1> changeStoneColorsEvent = new Event1>(); @Override public IEvent getStartGameEvent() { return startGameEvent; } @Override public IEvent getAddPlayerEvent() { return addPlayerEvent; } @Override public IEvent1 getRemovePlayerEvent() { return removePlayerEvent; } @Override public IEvent2 getChangePlayerColorEvent() { return changePlayerColorEvent; } @Override public IEvent2 getChangePlayerNameEvent() { return changePlayerNameEvent; } @Override public IEvent1 getChangeInitialMeldThresholdEvent() { return changeInitialMeldThresholdEvent; } @Override public IEvent1 getChangeStoneSetNumberEvent() { return changeStoneSetNumberEvent; } @Override public IEvent1 getChangeNumberOfStonesDealtEvent() { return changeNumberOfStonesDealtEvent; } @Override public IEvent1 getChangeHighestValueEvent() { return changeHighestValueEvent; } @Override public IEvent1> getChangeStoneColorsEvent() { return changeStoneColorsEvent; } @Override public void setError(SettingsError error) { switch (error) { case NO_ERROR: errorMessageLabel.setText(" "); break; case DUPLICATE_PLAYER_NAME_ERROR: errorMessageLabel .setText("Jeder Spielername darf nur einmal verwendet werden."); errorMessageLabel.setForeground(Color.RED); break; case NO_PLAYER_NAME_ERROR: errorMessageLabel.setText("Jeder Spieler muss einen Namen haben."); errorMessageLabel.setForeground(Color.RED); break; case NOT_ENOUGH_STONES_ERROR: errorMessageLabel .setText("Es gibt nicht genug Steine f\u00fcr die gew\u00e4hlte Spieleranzahl."); errorMessageLabel.setForeground(Color.RED); break; case NOT_ENOUGH_COLORS_ERROR: errorMessageLabel .setText("Es m\u00fcssen mindestens drei Farben ausgew\u00e4hlt werden."); errorMessageLabel.setForeground(Color.RED); break; case COMPUTER_PLAYERS_ONLY_WARNING: errorMessageLabel.setText("Es gibt keinen menschlichen Spieler."); errorMessageLabel.setForeground(Color.ORANGE.darker()); break; case TOO_HIGH_THRESHOLD_WARNING: errorMessageLabel .setText("Die gew\u00e4hlte Schranke ist m\u00f6glicherweise zu hoch"); errorMessageLabel.setForeground(Color.ORANGE.darker()); break; } } @Override public void enableStartGameButton(boolean enable) { startButton.setEnabled(enable); } @Override public void enableAddPlayerButton(boolean enable) { addPlayerButton.setVisible(enable); } @Override public void enableRemovePlayerButtons(boolean enable) { removeButtonsEnabled = enable; for (PlayerSettingsPanel psp : playerSettingsPanels) { psp.enableRemoveButton(enable); } } @Override public void setGameSettings(GameSettings gameSettings) { while (playerSettingsPanels.size() > gameSettings.getPlayerList().size()) { removePlayerSettingsPanel(); } for (int i = 0; i < gameSettings.getPlayerList().size(); ++i) { updatePlayerSettingsPanel(i, gameSettings.getPlayerList().get(i)); } initialMeldThresholdSpinner .setValue(gameSettings.getInitialMeldThreshold()); stoneSetNumberSpinner.setValue(gameSettings.getStoneSetNumber()); highestValueSpinner.setValue(gameSettings.getHighestValue()); numberOfStonesDealtSpinner.setValue(gameSettings.getNumberOfStonesDealt()); jokerNumberSpinner.setValue(gameSettings.getJokerNumber()); for (StoneColor color : StoneColor.values()) { colorButtons.get(color).getModel() .setSelected(gameSettings.getStoneColors().contains(color)); } playerSettingsViewport.revalidate(); } private void updatePlayerSettingsPanel(int i, PlayerSettings settings) { if (i < playerSettingsPanels.size()) { playerSettingsPanels.get(i).setSettings(settings); } else { addPlayerSettingsPanel(i, settings); } } private void addPlayerSettingsPanel(int i, PlayerSettings settings) { PlayerSettingsPanel panel = new PlayerSettingsPanel(i, settings); playerSettingsPanels.add(panel); playerSettingsViewport.add(panel, playerSettingsViewport.getComponentCount() - 1); } private void removePlayerSettingsPanel() { PlayerSettingsPanel p = playerSettingsPanels.remove(playerSettingsPanels .size() - 1); playerSettingsViewport.remove(p); } private void createPlayerSetupPanel() { playerSetupPanel = new JPanel(); playerSetupPanel.setLayout(new BorderLayout()); playerSettingsViewport = new JPanel(); playerSettingsViewport.setLayout(new BoxLayout(playerSettingsViewport, BoxLayout.Y_AXIS)); JScrollPane scrollPane = new JScrollPane(playerSettingsViewport, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); playerSetupPanel.add(scrollPane); JPanel addPlayerPanel = new JPanel(); addPlayerPanel.setLayout(new FlowLayout(FlowLayout.TRAILING, 0, 2)); playerSettingsViewport.add(addPlayerPanel); addPlayerButton = new JButton("+"); addPlayerButton.setFont(addPlayerButton.getFont().deriveFont(Font.BOLD)); addPlayerButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { addPlayerEvent.emit(); } }); addPlayerPanel.add(addPlayerButton); } private JComponent makeFiller() { return new Box.Filler(new Dimension(0, 0), new Dimension(0, 0), new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE)); } private void createOptionsPanel() { optionsPanel = new JPanel(); GridBagLayout layout = new GridBagLayout(); optionsPanel.setLayout(layout); makeOptionLabel(0, "Auslegeschranke:"); initialMeldThresholdSpinner = makeOptionSpinner(0, 1, 999, 1, changeInitialMeldThresholdEvent); makeOptionLabel(1, "Anzahl Steins\u00e4tze:"); stoneSetNumberSpinner = makeOptionSpinner(1, 1, 999, 1, changeStoneSetNumberEvent); makeOptionLabel(2, "H\u00f6chster Steinwert:"); highestValueSpinner = makeOptionSpinner(2, 3, 99, 1, changeHighestValueEvent); makeOptionLabel(3, "Anzahl Startsteine:"); numberOfStonesDealtSpinner = makeOptionSpinner(3, 1, 999, 1, changeNumberOfStonesDealtEvent); makeOptionLabel(4, "Jokeranzahl:"); jokerNumberSpinner = makeOptionSpinner(4, 1, 999, 1, changeJokerNumberEvent); makeOptionLabel(5, "Steinfarben:"); createColorSelectionPanel(5); GridBagConstraints c = new GridBagConstraints(); c.gridx = 1; c.gridy = 6; c.fill = GridBagConstraints.BOTH; c.weightx = 1; c.weighty = 1; optionsPanel.add(makeFiller(), c); } private void createColorSelectionPanel(int row) { GridBagConstraints c = new GridBagConstraints(); c.anchor = GridBagConstraints.WEST; c.gridx = 1; c.gridy = row; colorSelectionPanel = new JPanel(); optionsPanel.add(colorSelectionPanel, c); for (StoneColor color : StoneColor.values()) { createColorButton(color); } } private void createColorButton(final StoneColor color) { JToggleButton button = new JToggleButton(ImageUtil.createColorIcon( StonePainter.getColor(color), 12, 1)); button.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { emitColorChangeEvent(); } }); colorSelectionPanel.add(button); colorButtons.put(color, button); } private void emitColorChangeEvent() { Set colors = new HashSet(); for (StoneColor color : StoneColor.values()) { if (colorButtons.get(color).getModel().isSelected()) { colors.add(color); } } changeStoneColorsEvent.emit(colors); } private JSpinner makeOptionSpinner(int row, int min, int max, int step, final Event1 targetEvent) { GridBagConstraints c = new GridBagConstraints(); c.anchor = GridBagConstraints.WEST; c.gridx = 1; c.gridy = row; c.insets = new Insets(2, 2, 2, 2); final JSpinner spinner = new JSpinner(); spinner.setModel(new SpinnerNumberModel(min, min, max, step)); spinner .setPreferredSize(new Dimension(60, spinner.getMinimumSize().height)); spinner.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { targetEvent.emit((Integer) spinner.getValue()); } }); optionsPanel.add(spinner, c); return spinner; } private void makeOptionLabel(int row, String name) { GridBagConstraints c = new GridBagConstraints(); c.anchor = GridBagConstraints.EAST; c.gridx = 0; c.gridy = row; c.insets = new Insets(4, 4, 4, 4); JLabel label = new JLabel(name); optionsPanel.add(label, c); } SettingsPanel() { setLayout(new GridBagLayout()); final JTabbedPane tabbedPane = new JTabbedPane(); createPlayerSetupPanel(); tabbedPane.addTab("Spieler", playerSetupPanel); createOptionsPanel(); tabbedPane.addTab("Optionen", optionsPanel); GridBagConstraints c = new GridBagConstraints(); c.fill = GridBagConstraints.BOTH; c.gridwidth = GridBagConstraints.REMAINDER; c.weightx = 1; c.weighty = 1; add(tabbedPane, c); errorMessageLabel = new JLabel(" "); c.weighty = 0; add(errorMessageLabel, c); startButton = new JButton("Spiel starten"); startButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { startGameEvent.emit(); } }); add(startButton, c); setBorder(new CompoundBorder(new LineBorder(Color.BLACK), new EmptyBorder( 10, 10, 10, 10))); } private class PlayerSettingsPanel extends JPanel { private int playerNumber; private JButton colorButton; private JTextField nameField; private JButton removeButton; private JComboBox playerType; public void setSettings(PlayerSettings settings) { if (!nameField.getText().equals(settings.getName())) { nameField.setText(settings.getName()); } // playerType.setSelectedIndex(0); int index = Arrays.binarySearch(TurnControlFactory.Type.values(), settings.getTurnControlType()); if (index != playerType.getSelectedIndex()) { playerType.setSelectedIndex(index); } colorButton .setIcon(ImageUtil.createColorIcon(settings.getColor(), 16, 2)); } public void enableRemoveButton(boolean enable) { removeButton.setEnabled(enable); } class NameChangeListener implements DocumentListener { @Override public void insertUpdate(DocumentEvent e) { changeName(); } @Override public void removeUpdate(DocumentEvent e) { changeName(); } @Override public void changedUpdate(DocumentEvent e) { changeName(); } } public PlayerSettingsPanel(int playerNumber, PlayerSettings settings) { setLayout(new GridBagLayout()); this.playerNumber = playerNumber; createControls(settings); setSettings(settings); } private void createControls(PlayerSettings settings) { GridBagConstraints c = new GridBagConstraints(); GridBagConstraints c2 = new GridBagConstraints(); colorButton = new JButton(); nameField = new JTextField(settings.getName()); colorButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { new ColorMenu(); } }); c.gridy = 0; add(colorButton, c); nameField.getDocument().addDocumentListener(new NameChangeListener()); c2.gridy = 0; c2.weightx = 1; c2.fill = GridBagConstraints.HORIZONTAL; add(nameField, c2); String[] choices = { "Mensch", "Computer" }; playerType = new JComboBox(choices); playerType.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { changeType(); } }); add(playerType, c); removeButton = new JButton("\u00d7"); removeButton.setFont(removeButton.getFont().deriveFont(Font.BOLD)); removeButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { remove(); } }); removeButton.setEnabled(removeButtonsEnabled); add(removeButton, c); } @Override public Dimension getPreferredSize() { Dimension preferredSize = super.getPreferredSize(); for (Component component : this.getComponents()) { preferredSize .setSize(preferredSize.width, Math.max(preferredSize.height, component.getPreferredSize().height)); } return preferredSize; } @Override public Dimension getMaximumSize() { Dimension preferredSize = super.getPreferredSize(); for (Component component : this.getComponents()) { preferredSize .setSize(Integer.MAX_VALUE, Math.max(preferredSize.height, component.getPreferredSize().height)); } return preferredSize; } private void changeName() { changePlayerNameEvent.emit(playerNumber, nameField.getText()); } private void changeColor(Color c) { changePlayerColorEvent.emit(playerNumber, c); } private void changeType() { changePlayerTypeEvent.emit(playerNumber, TurnControlFactory.Type.values()[playerType.getSelectedIndex()]); } private void remove() { removePlayerEvent.emit(playerNumber); } private class ColorMenu extends JPanel { ColorMenu() { setLayout(new GridLayout(4, 4, 1, 1)); JPopupMenu menu = new JPopupMenu(); for (Color c : PLAYER_COLORS) { add(new ColorButton(c, menu)); } menu.setLayout(new BorderLayout()); menu.add(this); menu.show(colorButton, 0, colorButton.getHeight()); } private class ColorButton extends JLabel { ColorButton(final Color c, final JPopupMenu menu) { final Icon normalIcon = ImageUtil.createColorIcon(c, 16, 1); final Icon hoverIcon = ImageUtil.createColorIcon(c, 16, 2); setIcon(normalIcon); addMouseListener(new MouseAdapter() { @Override public void mouseEntered(MouseEvent e) { setIcon(hoverIcon); } @Override public void mouseExited(MouseEvent e) { setIcon(normalIcon); } @Override public void mouseClicked(MouseEvent e) { menu.setVisible(false); changeColor(c); } }); } } } } @Override public IEvent1 getChangeJokerNumberEvent() { return changeJokerNumberEvent; } @Override public IEvent2 getChangePlayerTypeEvent() { return changePlayerTypeEvent; } }