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.LayoutManager; 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.JCheckBox; 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.model.GameSettings; import jrummikub.model.PlayerSettings; import jrummikub.model.PlayerSettings.Type; 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 JTabbedPane tabbedPane; 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 JButton setVariantDefaultButton; private JButton setVariantChildrenButton; private JSpinner initialMeldThresholdSpinner; private JSpinner stoneSetNumberSpinner; private JSpinner highestValueSpinner; private JSpinner numberOfStonesDealtSpinner; private JSpinner jokerNumberSpinner; private JSpinner timeSpinner; private JCheckBox noLimitsBox; private JPanel colorSelectionPanel; private Map colorButtons = new HashMap(); private Event startGameEvent = new Event(); private Event loadGameEvent = new Event(); private Event networkGameEvent = 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>(); private Event1 changeTimeEvent = new Event1(); private Event1 changeNoLimitsEvent = new Event1(); private Event setVariantDefaultEvent = new Event(); private Event setVariantChildrenEvent = new Event(); @Override public IEvent getStartGameEvent() { return startGameEvent; } IEvent getLoadGameEvent() { return loadGameEvent; } IEvent getNetworkGameEvent() { return networkGameEvent; } @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 getChangeJokerNumberEvent() { return changeJokerNumberEvent; } @Override public IEvent2 getChangePlayerTypeEvent() { return changePlayerTypeEvent; } @Override public IEvent1 getChangeTimeEvent() { return changeTimeEvent; } @Override public IEvent1 getChangeNoLimitsEvent() { return changeNoLimitsEvent; } @Override public IEvent1> getChangeStoneColorsEvent() { return changeStoneColorsEvent; } @Override public IEvent getSetVariantDefaultEvent() { return setVariantDefaultEvent; } @Override public IEvent getSetVariantChildrenEvent() { return setVariantChildrenEvent; } @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()); timeSpinner.setValue(gameSettings.getTotalTime()); noLimitsBox.setSelected(gameSettings.isNoLimits()); 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); int row = 0; makeOptionLabel(row, "Variante:"); makeVariantPanel(row++); makeOptionLabel(row, "Auslegeschranke:"); initialMeldThresholdSpinner = makeOptionSpinner(row++, 1, 999, 1, changeInitialMeldThresholdEvent); makeOptionLabel(row, "Anzahl Steins\u00e4tze:"); stoneSetNumberSpinner = makeOptionSpinner(row++, 1, 999, 1, changeStoneSetNumberEvent); makeOptionLabel(row, "H\u00f6chster Steinwert:"); highestValueSpinner = makeOptionSpinner(row++, 3, 99, 1, changeHighestValueEvent); makeOptionLabel(row, "Anzahl Startsteine:"); numberOfStonesDealtSpinner = makeOptionSpinner(row++, 1, 999, 1, changeNumberOfStonesDealtEvent); makeOptionLabel(row, "Jokeranzahl:"); jokerNumberSpinner = makeOptionSpinner(row++, 1, 999, 1, changeJokerNumberEvent); makeOptionLabel(row, "Zeit für Spielzug:"); timeSpinner = makeOptionSpinner(row++, 1, 999, 1, changeTimeEvent); makeOptionLabel(row, "No Limits:"); noLimitsBox = makeOptionCheckbox(row++, changeNoLimitsEvent); makeOptionLabel(row, "Steinfarben:"); createColorSelectionPanel(row++); GridBagConstraints c = new GridBagConstraints(); c.gridx = 1; c.gridy = row; 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.setMargin(new Insets(3, 3, 3, 3)); 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 void makeVariantPanel(int row) { JPanel panel = makeOptionSubpanel(row, new GridLayout(1, 2, 5, 0)); setVariantDefaultButton = new JButton("Standard"); panel.add(setVariantDefaultButton); setVariantDefaultButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { setVariantDefaultEvent.emit(); } }); setVariantChildrenButton = new JButton("Kinder"); panel.add(setVariantChildrenButton); setVariantChildrenButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { setVariantChildrenEvent.emit(); } }); } private JPanel makeOptionSubpanel(int row, LayoutManager layout) { GridBagConstraints c = new GridBagConstraints(); c.anchor = GridBagConstraints.WEST; c.gridx = 1; c.gridy = row; c.insets = new Insets(2, 2, 2, 2); JPanel panel = new JPanel(layout); optionsPanel.add(panel, c); return panel; } private JCheckBox makeOptionCheckbox(int row, 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 JCheckBox box = new JCheckBox(); box.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { targetEvent.emit(box.isSelected()); } }); optionsPanel.add(box, c); return box; } 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()); tabbedPane = new JTabbedPane(); createPlayerSetupPanel(); tabbedPane.addTab("Spieler", playerSetupPanel); createOptionsPanel(); JScrollPane scrollPane = new JScrollPane(optionsPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); tabbedPane.addTab("Optionen", scrollPane); 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); c.gridwidth = 1; startButton = addButton("Spiel starten", startGameEvent, c); c.weightx = 0; add(Box.createHorizontalStrut(10), c); c.weightx = 1; addButton("Spiel laden...", loadGameEvent, c); c.weightx = 0; add(Box.createHorizontalStrut(10), c); c.weightx = 1; c.gridwidth = GridBagConstraints.REMAINDER; addButton("Netzwerkspiel...", networkGameEvent, c); setBorder(new CompoundBorder(new LineBorder(Color.BLACK), new EmptyBorder(10, 10, 10, 10))); } void resetTabbedPane() { tabbedPane.setSelectedIndex(0); } private JButton addButton(String title, final Event event, GridBagConstraints c) { JButton button = new JButton(title); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { event.emit(); } }); add(button, c); return button; } 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(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, 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); } }); } } } } }