
package programs.typingtutor.gui;

import com.jgoodies.forms.factories.Borders;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
import com.sun.istack.internal.NotNull;
import com.sun.istack.internal.Nullable;
import programs.typingtutor.*;
import programs.typingtutor.characterfeeder.FeederListener;
import programs.typingtutor.characterfeeder.RandomCharacterFeeder;
import programs.typingtutor.gui.keyboardpanel.AbstractKeyboardPanel;
import programs.typingtutor.gui.keyboardpanel.KeyButton;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;

import static programs.typingtutor.Constants.STANDARD_CAPACITY;

/**
 * Select the keys which should be available during a lesson.
*/
public final class KeySelectionPanel extends JPanel implements ActionListener {
  private static final transient String[] LESSON_TYPES = {"FINGER", "POSITION", "CHARACTERS"};

  private JLabel title;
  
  private SpeedPanel speedPanel;

  private KeyboardLessonPanel keyboardLessonPanel;

  private JLabel[] lessonTitle;

  private JButton[] allButtons;

  private JCheckBox[] lessonPosCheckBoxes;

  private JCheckBox[] lessonFingerCheckBoxes;

  private JCheckBox[] lessonCharCheckBoxes;

  private Set<FeederListener> feederListeners;
  
  @Nullable
  private RandomCharacterFeeder currentFeeder;
  
  @NotNull
  private Map<Integer,BitSet> checkBoxConfiguration;

  /**
   * Constructor.
   */
  public KeySelectionPanel() {
    feederListeners = new HashSet<>(STANDARD_CAPACITY);
    checkBoxConfiguration = new HashMap<>(STANDARD_CAPACITY);
    currentFeeder = null;
    buildComponents();
    buildLayout();
    setBorder(Borders.DLU4_BORDER);
  }

  /**
   * Add a FeederListener
   * @param feederListener FeederListener to add
   */
  public void addFeederListener(@NotNull FeederListener feederListener) {
    feederListeners.add(feederListener);
  }

  /**
   * Cleanup the component before closing.
   */
  public void cleanup() {
    TypingTutorPreferences preferences = TypingTutorPreferences.getPreferencesInstance();
    if (currentFeeder != null)
      speedPanel.removeSpeedChangeListener(currentFeeder);
    speedPanel.removeSpeedChangeListener(preferences);
  }

  /**
   * Inform the listeners that the feeder has been updated
   */
  public void fireFeederUpdated() {
    if (currentFeeder == null) {
      GlobeLog.lg.debug("Create RandomCharacterFeeder");
      currentFeeder = new RandomCharacterFeeder();
      speedPanel.addSpeedChangeListener(currentFeeder);
    }    
    
    SortedSet<Integer> selectedCodepoints = keyboardLessonPanel.getSelectedCodepoints();
    GlobeLog.lg.debug(String.format("RandomCharacterFeeder update with codepoints: %s",Calculator.printIntegerAsRange(selectedCodepoints)));
    currentFeeder.setKeys(selectedCodepoints);
    currentFeeder.setKeystrokesPerCodePoint(KeyboardLayout.getCurrentLayout().getKeyStrokesPerCodePoint());
    currentFeeder.setSpeed(speedPanel.getCurrentSpeedInStrokesPerMinute());
      
    for (FeederListener listener : feederListeners)
      listener.feederProvided(currentFeeder);
  }
  
  /**
   * Ask the speed panel because it contains JTextFields.
   * @return false if it should be used
   */
  public boolean dispatchKeyEvent() {
    return speedPanel.dispatchKeyEvent();
  }

  /**
   * Remove a FeederListener
   * @param feederListener FeederListener to remove
   */
  public void removeFeederListener(@NotNull FeederListener feederListener) {
    feederListeners.remove(feederListener);
  }


  @NotNull
  public String toString() {
    return "KeySelectionPanel -- " + super.toString();
  }

  /** @noinspection OverlyComplexMethod*/
  public void actionPerformed(@NotNull ActionEvent evt) {
    Object src = evt.getSource();
    if (src instanceof JCheckBox) {
      JCheckBox rCheckBox = (JCheckBox) src;
      String type = rCheckBox.getActionCommand();
      if ("FINGER".equals(type)) {
        for (int index=0; index<lessonFingerCheckBoxes.length; index++) {          
          if (rCheckBox.equals(lessonFingerCheckBoxes[index])) {
            keyboardLessonPanel.filterKeysByFingerLessons(FingerLesson.values()[index], rCheckBox.isSelected());
            break;
          }        
        }
      }
      else if ("POSITION".equals(type)) {
        for (int index=0; index<lessonPosCheckBoxes.length; index++) {          
          if (rCheckBox.equals(lessonPosCheckBoxes[index])) {
            keyboardLessonPanel.filterKeysByPositionLesson(PositionLesson.values()[index], rCheckBox.isSelected());
            break;
          }         
        }
      }
      else if ("CHARACTER".equals(type)) {
        for (int index=0; index<lessonCharCheckBoxes.length; index++) {
          if (rCheckBox.equals(lessonCharCheckBoxes[index])) {
            keyboardLessonPanel.filterKeysByCharLesson(CharLesson.values()[index],rCheckBox.isSelected());
            break;
          }
        }
      }   
    }
    else if (src instanceof JButton) {
      JButton rButton = (JButton) src;
      String type = rButton.getActionCommand();
      if ("FINGER".equals(type)) {
        Set<FingerLesson> fingerLessons = new HashSet<>(Arrays.asList(fingerLessons()));
        for (FingerLesson lesson : fingerLessons) 
          keyboardLessonPanel.filterKeysByFingerLessons(lesson, true);
      }
      else if ("POSITION".equals(type)) {
        Set<PositionLesson> posLessons = new HashSet<>(Arrays.asList(PositionLesson.values()));
        posLessons.remove(PositionLesson.SPACE_BAR);
        for (PositionLesson posLesson : posLessons) 
          keyboardLessonPanel.filterKeysByPositionLesson(posLesson, true);
      }
      else if ("CHARACTER".equals(type)) {
        Set<CharLesson> charLessons = new HashSet<>(Arrays.asList(CharLesson.values()));
        for (CharLesson charLesson : charLessons) 
          keyboardLessonPanel.filterKeysByCharLesson(charLesson, true);
      }
    }
  }

  /** @noinspection ObjectAllocationInLoop,FeatureEnvy*/
  private void buildComponents() {
    TypingTutorPreferences preferences = TypingTutorPreferences.getPreferencesInstance();
    speedPanel  = new SpeedPanel(preferences.getTypingSpeed(),preferences.getTypingUnit());
    speedPanel.addSpeedChangeListener(preferences);
  
    lessonTitle = new JLabel[LESSON_TYPES.length];
    for (int index =0; index <LESSON_TYPES.length; index++) {
      //noinspection ObjectAllocationInLoop
      lessonTitle[index] = new JLabel(PropRes.get(LESSON_TYPES[index]));
      lessonTitle[index].setFont(GUI.TINY);
    }

    title = new JLabel(PropRes.get("ENABLED_KEYS"), JLabel.CENTER);
    title.setFont(GUI.TITLE);

    String[] text     = {PropRes.get("ALL_FINGER"),PropRes.get("ALL_POSITION"),PropRes.get("ALL_CHARACTERS")};
    String[] command  = {"FINGER","POSITION","CHARACTER"};
    allButtons = new JButton[text.length];
    for (int index=0; index<text.length; index++)  {
      allButtons[index] = new JButton(text[index]);
      allButtons[index].setFont(GUI.TINY);
      allButtons[index].setActionCommand(command[index]);
      allButtons[index].addActionListener(this);
    }

    FingerLesson[] fingerValues = fingerLessons();
    lessonFingerCheckBoxes = new JCheckBox[fingerValues.length];
    for (int index = 0; index < lessonFingerCheckBoxes.length; index++) 
      lessonFingerCheckBoxes[index] = buildCheckBox("",PropRes.get(fingerValues[index].name()),"FINGER");

    PositionLesson[] posValues = PositionLesson.values();
    lessonPosCheckBoxes = new JCheckBox[posValues.length];
    for (int index = 0; index < lessonPosCheckBoxes.length; index++) 
      lessonPosCheckBoxes[index] = buildCheckBox("",PropRes.get(posValues[index].name()),"POSITION");

    CharLesson[] charValues = CharLesson.values();
    lessonCharCheckBoxes = new JCheckBox[charValues.length];
    for (int index = 0; index < lessonCharCheckBoxes.length; index++) 
      lessonCharCheckBoxes[index] = buildCheckBox(PropRes.get(charValues[index].name()),null,"CHARACTER");
    
    keyboardLessonPanel = new KeyboardLessonPanel();    
    keyboardLessonPanel.setModifierOnCheckBoxes();
  }

  /**
   * Return an array of FingerLesson values without THUMB
   * @return array of FingerLesson values for checkboxes
   */
  private FingerLesson[] fingerLessons() {
    FingerLesson[] fingerValues = FingerLesson.values();
    return Arrays.copyOf(fingerValues, fingerValues.length-1);
  }

  /**
   * Checkbox builder.
   * @param title         Title of JCheckBox
   * @param tooltipText   Tooltip text of JCheckBox
   * @param actionCommand Action command
   * @return JCheckBox instance
   */
  @NotNull
  private JCheckBox buildCheckBox(@NotNull String title, @Nullable String tooltipText, @NotNull String actionCommand) {
    JCheckBox result = new JCheckBox(title);
    result.setFont(GUI.TINY);
    result.addActionListener(this);
    result.setSelected(true);
    if (tooltipText != null)
      result.setToolTipText(tooltipText);      
    result.setActionCommand(actionCommand);
    return result;
  }

  private void buildLayout() {
    StringBuilder cols = new StringBuilder("default:g");
    final int maxNumberOfColumns = keyboardLessonPanel.getMaxNumberOfKeyboardButtonsInRow();
    for (int index = 0; index < maxNumberOfColumns; index++)
      cols.append(',').append(KeyButton.DEFAULT_WIDTH).append("px");
    cols.append(",default:g");
    
    final int numberOfColumns = maxNumberOfColumns+2;
  
    final int numRows = PositionLesson.values().length;
    StringBuilder rows = new StringBuilder("default,default,default");
    for (int index = 0; index < numRows; index++)
      rows.append(',').append(KeyButton.DEFAULT_HEIGHT).append("px");
    rows.append(",default,default");
    
    setLayout(new FormLayout(cols.toString(), rows.toString()));
    CellConstraints cc = new CellConstraints();
    add(speedPanel, cc.xyw(1,1,numberOfColumns,CellConstraints.DEFAULT,CellConstraints.DEFAULT));

    
    add(title, cc.xyw(2,2,maxNumberOfColumns,CellConstraints.CENTER,CellConstraints.DEFAULT));
    add(keyboardLessonPanel,cc.xywh(2,4,maxNumberOfColumns,numRows,CellConstraints.FILL,CellConstraints.FILL));
    
    //-------------------------- Position -----------------------------
    
    Box positionBox = Box.createVerticalBox();
    positionBox.add(lessonTitle[1]);
    positionBox.add(allButtons[1]);
    
    add(positionBox, cc.xywh(1,2,1,2,CellConstraints.RIGHT,CellConstraints.BOTTOM));
    
    PositionLesson[] posValues = PositionLesson.values();
    for (int index = 0; index < posValues.length-1; index++) 
      add(lessonPosCheckBoxes[index],cc.xy(1,4+index,CellConstraints.RIGHT,CellConstraints.CENTER));

    //----------------------------- Finger ---------------------------------

    Box fingerBox = Box.createVerticalBox();
    fingerBox.add(lessonTitle[0]); // Finger
    
    add(allButtons[0], cc.xy(1,numRows+4,CellConstraints.RIGHT,CellConstraints.TOP));
    
    final int outerPinkyStart = keyboardLessonPanel.getLastOuterPinkyIndex()+2;
    final int fingerLessonLength = fingerLessons().length;
    for (int index = 0; index < fingerLessonLength; index++) 
      add(lessonFingerCheckBoxes[index],cc.xy(index+outerPinkyStart,numRows+4,CellConstraints.CENTER,CellConstraints.DEFAULT));
    add(lessonTitle[0],cc.xyw(outerPinkyStart,numRows+5, maxNumberOfColumns,CellConstraints.CENTER,CellConstraints.TOP));

    //------------------------------- Character -----------------------------

    Box charBox = Box.createVerticalBox();
    charBox.add(lessonTitle[2]);
    charBox.add(allButtons[2]);
    add(charBox,cc.xy(numberOfColumns,3,CellConstraints.CENTER,CellConstraints.BOTTOM));
    
    Box charLessonBox = Box.createVerticalBox();
    CharLesson[] charValues = CharLesson.values();
    for (int index = 0; index < charValues.length; index++) 
      charLessonBox.add(lessonCharCheckBoxes[index]);
    add(charLessonBox,cc.xywh(numberOfColumns,4,1,numRows,CellConstraints.CENTER,CellConstraints.CENTER));
  }

  /**
   * Class showing a Keyboard with modifiers, enabling specific keys.
   */
  private class KeyboardLessonPanel extends AbstractKeyboardPanel<KeyButton> implements ActionListener {    
    /**
     * Constructor.
     */
    public KeyboardLessonPanel() {
      super(KeyButton.DEFAULT_HEIGHT);
      buildComponents();
    }
  
    /**
     * Set the FingerLesson keys which are currently active.
     * @param flesson    allowed FingerLesson
     * @param enabled    if the lesson is enabled
     */
    public void filterKeysByFingerLessons(@NotNull FingerLesson flesson, boolean enabled) {
      boolean bChanged = false;
    
      final int currentModifier = getCurrentModifier();
      int position = flesson.ordinal();
      
      checkBoxConfiguration.get(currentModifier).set(position, enabled);
      
      final int outerPinkyStart = getLastOuterPinkyIndex();
      for (int row = PositionLesson.DIGIT_KEYS.ordinal(); row <= PositionLesson.LOWER_KEYS.ordinal(); row++) {
        int column = position + outerPinkyStart;
        int startColumn;
        int endColumn;
        
        if (flesson == FingerLesson.LEFT_OUTER_PINKY) {
          startColumn = 0;
          endColumn   = column;
        }
        else if (flesson == FingerLesson.RIGHT_OUTER_PINKY) {
          startColumn = column;
          endColumn   = keyboardRows[row].getComponentCount()-1;
        }
        else {
          startColumn = column;
          endColumn   = column;
        }
        
        
        for (int currentColumn = startColumn; currentColumn <= endColumn; currentColumn++) {
          Component currentComponent = keyboardRows[row].getComponent(currentColumn);
          //noinspection InstanceofInterfaces
          if (currentComponent instanceof KeyButton) {
            KeyButton button = (KeyButton) currentComponent;
            if (button.isEnabled()) {
              boolean selection = enabled && lessonPosCheckBoxes[button.getPositionLesson().ordinal()].isSelected();
              bChanged |= selection != button.isKeyAllowed(currentModifier);
              button.setKeyAllowed(currentModifier,selection);
            }
          }       
        }     
      }
      
      if (bChanged)
        fireFeederUpdated();
    }
    
    /**
     * Set the keys which are currently active.
     * @param poslesson   allowed PositionLessons
     * @param enabled     if the lesson is enabled
     */
    public void filterKeysByPositionLesson(@NotNull PositionLesson poslesson, boolean enabled) {
      boolean bChanged = false;
    
      final int currentModifier = getCurrentModifier();
      int row = poslesson.ordinal();
      
      checkBoxConfiguration.get(currentModifier).set(lessonFingerCheckBoxes.length+row, enabled);
      
      final int maxColumns = keyboardRows[row].getComponentCount();
      for (int column = 0; column < maxColumns; column++) {        
        Component currentComponent = keyboardRows[row].getComponent(column);
        //noinspection InstanceofInterfaces
        if (currentComponent instanceof KeyButton) {
          KeyButton button = (KeyButton) currentComponent;
          boolean selection = enabled && lessonFingerCheckBoxes[button.getFingerLesson().ordinal()].isSelected();
          bChanged |= button.isKeyAllowed(currentModifier) != selection;
          button.setKeyAllowed(currentModifier,selection);
        }          
      }
      
      if (bChanged)
        fireFeederUpdated();
    }
  
  
    /**
     * Set the keys which are currently active.
     * @param charlesson  allowed CharLessons
     * @param enabled     if the charlesson is enabled
     */
    public void filterKeysByCharLesson(@NotNull CharLesson charlesson, boolean enabled) {
      boolean bChanged = false;
      
      final int currentModifier = getCurrentModifier();
      int charLesson = charlesson.ordinal();
      
      final int offset = lessonFingerCheckBoxes.length + lessonPosCheckBoxes.length;
      checkBoxConfiguration.get(currentModifier).set(offset+charLesson, enabled);
      
      // This is different from other filtering: Only if a CharLesson is deselected,
      // filter the keys. It also works on all levels of modifiers. Selection does nothing at all
      if (!enabled) {
        for (KeyButton button : keyboardButtons) {
          SortedMap<Integer,Integer> modifierMap = button.getModifierMap();
          for (int modifier : modifierMap.keySet()) {
            int codepoint = modifierMap.get(modifier);
            CharLesson codepointCharLesson = CharLesson.charLessonForCodepoint(codepoint);
            if (codepointCharLesson == charlesson) {
              bChanged |= button.isKeyAllowed(modifier);
              button.setKeyAllowed(modifier, false);
            }
          }
        }   
      }
      
      if (bChanged)
        fireFeederUpdated();
    }
    
    /**
     * Return the valid keys.
     * @return a SortedSet of codepoints using valid characters.
     */
    @NotNull
    public SortedSet<Integer> getSelectedCodepoints() {
      SortedSet<Integer> selectedCodepoints = new TreeSet<>();
      for (KeyButton button : keyboardButtons) {
        selectedCodepoints.addAll(button.getAllowedCodepoints());
      }
      return selectedCodepoints;
    }
  
    /**
     * Return the correct index for the last key which still belongs to the outer pinky finger.
     * @return correct index
     */
    public int getLastOuterPinkyIndex() {
      int result = -1;
      Box rowContainer = keyboardRows[0];
      Component[] row = rowContainer.getComponents();
      for (int index = 0; index < row.length; index++) {
        Component component = row[index];
        //noinspection InstanceofInterfaces
        if (component instanceof KeyButton) {
          KeyButton button = (KeyButton) component;
          if (button.getFingerLesson() == FingerLesson.LEFT_PINKY) {
            result = index-1;
            break;
          }     
        }      
      }
      return result;
    }
  
    /**
     * Load a current selected layout.
     * @param layout a KeyboardLayout
     */
    public void loadKeyboardLayout(@NotNull KeyboardLayout layout) {
      for (KeyButton oldButton : keyboardButtons)
        oldButton.removeActionListener(this);
      
      keyboardButtons.clear();
      keyboardButtons.addAll(layout.getKeyboard());
      for (KeyButton aButton : keyboardButtons) 
        aButton.addActionListener(this);
    }
    
    /**
     * Set a given layout.
     * @param layout a KeyboardLayout
     */
    public void setKeyboardLayout(@NotNull KeyboardLayout layout) {
      if (layout.isValidLayout()) {
        loadKeyboardLayout(layout);
        fillKeyboard(layout.getLastLeftPositionInRows());
        
      }
    }  
  
    @NotNull
    public String toString() {
      StringBuilder sb = new StringBuilder("KeyboardLessonPanel -- ");
      for (int codepoint : getSelectedCodepoints())
        sb.appendCodePoint(codepoint);
      sb.append(super.toString());
      return sb.toString();
    }
    
  /** 
   * Update modifier.
   */
  protected void updateModifier() {
    super.updateModifier();
    
    setModifierOnCheckBoxes();
  }
  
  private void setModifierOnCheckBoxes() {
    int modifier = getCurrentModifier();
    // Now reset selection
    final int bitSetLength = lessonFingerCheckBoxes.length + lessonPosCheckBoxes.length + lessonCharCheckBoxes.length;
    BitSet configuration;
    if (!checkBoxConfiguration.containsKey(modifier)) {
      configuration = new BitSet(bitSetLength);
      configuration.set(0,bitSetLength,true);
      checkBoxConfiguration.put(modifier, configuration);      
    }
    else
      configuration = checkBoxConfiguration.get(modifier);
      
    int index = 0;
    for (JCheckBox lessonFingerCheckBox : lessonFingerCheckBoxes) 
      lessonFingerCheckBox.setSelected(configuration.get(index++));
    for (JCheckBox lessonPosCheckBox : lessonPosCheckBoxes) 
      lessonPosCheckBox.setSelected(configuration.get(index++));
    for (JCheckBox lessonCharCheckBox : lessonCharCheckBoxes) 
      lessonCharCheckBox.setSelected(configuration.get(index++));    
  }
    
    /**
     * Update modifiable button.
     * @param selectedButton modifiable button which was selected
     */
    protected void updateModifiableButton(@NotNull KeyButton selectedButton) {
      selectedButton.updateSelection();
      // If a button is selected, reenable the finger, position and charlesson as valid
      // without changing the other keys
      if (selectedButton.isSelected()) {
        PositionLesson positionLesson = selectedButton.getPositionLesson();
        if (positionLesson != PositionLesson.SPACE_BAR) {
          int position = positionLesson.ordinal();
          if (!lessonPosCheckBoxes[position].isSelected()) {
            lessonPosCheckBoxes[position].removeActionListener(this);
            lessonPosCheckBoxes[position].setSelected(true);
            lessonPosCheckBoxes[position].addActionListener(this);
          }
        }
        
        FingerLesson fingerLesson = selectedButton.getFingerLesson();
        if (fingerLesson != FingerLesson.THUMB) {
          int finger = fingerLesson.ordinal();
          if (!lessonFingerCheckBoxes[finger].isSelected()) {
            lessonFingerCheckBoxes[finger].removeActionListener(this);
            lessonFingerCheckBoxes[finger].setSelected(true);
            lessonFingerCheckBoxes[finger].addActionListener(this);
          }          
        }
        
        int currentModifier = getCurrentModifier();
        Map<Integer,Integer> modifierMap = selectedButton.getModifierMap();
        if (modifierMap.containsKey(currentModifier)) {
          int codepoint = modifierMap.get(currentModifier);
          CharLesson charLesson = CharLesson.charLessonForCodepoint(codepoint);
          for (CharLesson aCharLesson : CharLesson.values()) {
            int index = aCharLesson.ordinal();
            if (!lessonCharCheckBoxes[index].isSelected() && aCharLesson == charLesson) {
              lessonCharCheckBoxes[index].removeActionListener(this);
              lessonCharCheckBoxes[index].setSelected(true);
              lessonCharCheckBoxes[index].addActionListener(this);
            }
          }
        }        
      }
      fireFeederUpdated();
    } 
  
    protected final void buildComponents() {
      super.buildComponents();
  
      KeyboardLayout curKeyboard = KeyboardLayout.getDefaultKeyboardLayout();
      if (curKeyboard != null) 
        setKeyboardLayout(curKeyboard);
    }
  
    private void fillKeyboard(@NotNull int[] lastLeftPositionInRows) {
      int minLastLeftKey = Integer.MAX_VALUE;
      for (int lastLeftPosition : lastLeftPositionInRows)
        minLastLeftKey = Math.min(minLastLeftKey, lastLeftPosition);
  
      // Clean rows
      for (int row = PositionLesson.DIGIT_KEYS.ordinal(); row <= PositionLesson.LOWER_KEYS.ordinal(); row++) 
        keyboardRows[row].removeAll();
  
      boolean rowSwitch;
      int oldRow = -1;
      final int lastIndex = keyboardButtons.size()-1;
      int buttonindex = 0;
      for (KeyButton aButton : keyboardButtons) {
        if (buttonindex == lastIndex)
          break;
             
        int buttonRow = aButton.getPositionLesson().ordinal();
        rowSwitch = (oldRow != buttonRow);
        
        int lastLeftPositionInRow = lastLeftPositionInRows[buttonRow];
        while (rowSwitch && minLastLeftKey < lastLeftPositionInRow) {
          keyboardRows[buttonRow].add(getSubstituitionPanel(KeyButton.getStdDimension()));
          lastLeftPositionInRow--;
        }
        keyboardRows[buttonRow].add(aButton);
        oldRow = buttonRow;
        buttonindex++;
      }
      
      int maxNumKeys = getMaxNumberOfKeyboardButtonsInRow();
      int maxWidth   = KeyButton.DEFAULT_WIDTH*maxNumKeys;
      
      // Add modifier button at last row
      Box ctrlBox = keyboardRows[NUM_OF_KYB_ROWS-1];
      for (int index = 0; index < 3; index++) 
        ctrlBox.add(modifierButtons.get(index));
      
      Dimension buttonDim = KeyButton.getStdDimension();
      KeyButton spaceButton = keyboardButtons.get(lastIndex);
      Constants.setConstantSize(spaceButton, new Dimension(maxWidth-getModifierButtonsWidth(), buttonDim.height));
      
      ctrlBox.add(spaceButton);
      
      for (int index = 3; index < LAST_ROW_MODIFIERS.length; index++) 
         ctrlBox.add(modifierButtons.get(index));
    }
  }
}