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.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.DefaultComboBoxModel; 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.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 List> playerTypeChoices = Collections.emptyList(); private List removeButtonsEnabled = Collections.emptyList(); private List playerNamesEditable = Collections.emptyList(); private JTabbedPane tabbedPane; private JPanel playerSetupPanel; private JPanel playerSettingsViewport; private JPanel optionsPanel; private JButton addPlayerButton; private JLabel errorMessageLabel; private JButton startButton; private JButton offerButton; 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 JButton loadButton; private JButton networkButton; private JButton backButton; private JPanel buttonPanel; 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 offerGameEvent = new Event(); private Event backEvent = 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; } @Override public IEvent getOfferGameEvent() { return offerGameEvent; } 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 IEvent getBackEvent() { return backEvent; } @Override public void setPlayerTypeChoices(List> choices) { playerTypeChoices = choices; } @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(List enable) { removeButtonsEnabled = enable; Iterator it = enable.iterator(); for (PlayerSettingsPanel psp : playerSettingsPanels) { if (!it.hasNext()) { psp.enableRemoveButton(false); } else { psp.enableRemoveButton(it.next()); } } } @Override public void setPlayerNamesEditable(List editable) { playerNamesEditable = editable; Iterator it = editable.iterator(); for (PlayerSettingsPanel psp : playerSettingsPanels) { if (!it.hasNext()) { psp.setPlayerNameEditable(true); } else { psp.setPlayerNameEditable(it.next()); } } } @Override public void setGameSettings(GameSettings gameSettings) { while (playerSettingsPanels.size() > gameSettings.getPlayerList().size()) { removePlayerSettingsPanel(); } for (int i = 0; i < gameSettings.getPlayerList().size(); ++i) { List choices = Collections.emptyList(); if (i < playerTypeChoices.size()) { choices = playerTypeChoices.get(i); } updatePlayerSettingsPanel(i, gameSettings.getPlayerList().get(i), choices); } 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, List choices) { if (i < playerSettingsPanels.size()) { playerSettingsPanels.get(i).setChoices(choices); playerSettingsPanels.get(i).setSettings(settings); } else { addPlayerSettingsPanel(i, settings, choices); } } private void addPlayerSettingsPanel(int i, PlayerSettings settings, List choices) { PlayerSettingsPanel panel = new PlayerSettingsPanel(i, settings, choices); playerSettingsPanels.add(panel); playerSettingsViewport.add(panel, playerSettingsViewport.getComponentCount() - 1); enableRemovePlayerButtons(removeButtonsEnabled); setPlayerNamesEditable(playerNamesEditable); } 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); buttonPanel = new JPanel(); buttonPanel.setLayout(new GridBagLayout()); add(buttonPanel, c); startButton = createButton("Spiel starten", startGameEvent); loadButton = createButton("Spiel laden...", loadGameEvent); networkButton = createButton("Netzwerkspiel...", networkGameEvent); offerButton = createButton("Spiel anbieten", offerGameEvent); backButton = createButton("Zur\u00fcck", backEvent); addDefaultButtons(); setBorder(new CompoundBorder(new LineBorder(Color.BLACK), new EmptyBorder( 10, 10, 10, 10))); } void resetTabbedPane() { tabbedPane.setSelectedIndex(0); } private void addDefaultButtons() { buttonPanel.removeAll(); GridBagConstraints c = new GridBagConstraints(); c.fill = GridBagConstraints.BOTH; c.gridwidth = 1; c.weightx = 1; c.weighty = 1; buttonPanel.add(startButton, c); c.weightx = 0; buttonPanel.add(Box.createHorizontalStrut(10), c); c.weightx = 1; buttonPanel.add(loadButton, c); c.weightx = 0; buttonPanel.add(Box.createHorizontalStrut(10), c); c.weightx = 1; c.gridwidth = GridBagConstraints.REMAINDER; buttonPanel.add(networkButton, c); } private void addNetworkButtons() { buttonPanel.removeAll(); GridBagConstraints c = new GridBagConstraints(); c.fill = GridBagConstraints.BOTH; c.gridwidth = 1; c.weightx = 1; c.weighty = 1; buttonPanel.add(offerButton, c); c.weightx = 0; buttonPanel.add(Box.createHorizontalStrut(10), c); c.weightx = 1; c.gridwidth = GridBagConstraints.REMAINDER; buttonPanel.add(backButton, c); } private JButton createButton(String title, final Event event) { JButton button = new JButton(title); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { event.emit(); } }); return button; } @Override public void enableNetworkMode(boolean enable) { if (enable) { addNetworkButtons(); } else { addDefaultButtons(); } } private class PlayerSettingsPanel extends JPanel { private int playerNumber; private JButton colorButton; private JTextField nameField; private JButton removeButton; private JComboBox playerType; private List playerTypeChoices; public void setSettings(PlayerSettings settings) { if (!nameField.getText().equals(settings.getName())) { nameField.setText(settings.getName()); } int index = playerTypeChoices.indexOf(settings.getType()); if (index != playerType.getSelectedIndex() && index >= 0 && index < playerType.getModel().getSize()) { playerType.setSelectedIndex(index); } colorButton .setIcon(ImageUtil.createColorIcon(settings.getColor(), 16, 2)); } public void setPlayerNameEditable(boolean editable) { nameField.setEditable(editable); } 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, List choices) { setLayout(new GridBagLayout()); this.playerNumber = playerNumber; createControls(settings); setChoices(choices); setSettings(settings); } public void setChoices(List playerTypeChoices) { this.playerTypeChoices = playerTypeChoices; Vector choices = new Vector(); for (Type t : playerTypeChoices) { switch (t) { case COMPUTER: choices.add("Computer"); break; case HUMAN: choices.add("Mensch"); break; case NETWORK: choices.add("Netzwerk"); break; } } playerType.setModel(new DefaultComboBoxModel(choices)); } 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); playerType = new JComboBox(); playerType.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { changeType(); } }); playerType.setPreferredSize(new Dimension(120, playerType .getPreferredSize().height)); playerType.setMinimumSize(new Dimension(120, playerType.getMinimumSize().height)); 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(); } }); 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, playerTypeChoices.get(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); } }); } } } } }