package bluej.stride.slots;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream;

import javafx.application.Platform;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Cursor;
import bluej.stride.framedjava.ast.NameDefSlotFragment;
import bluej.stride.framedjava.ast.ParamFragment;
import bluej.stride.framedjava.ast.TextSlotFragment;
import bluej.stride.framedjava.ast.TypeSlotFragment;
import bluej.stride.framedjava.elements.CodeElement;
import bluej.stride.framedjava.errors.CodeError;
import bluej.stride.framedjava.errors.EmptyError;
import bluej.stride.framedjava.frames.CodeFrame;
import bluej.stride.framedjava.slots.TypeSlot;
import bluej.stride.generic.Frame;
import bluej.stride.generic.FrameContentRow;
import bluej.stride.generic.InteractionManager;
import bluej.utility.Utility;
import bluej.utility.javafx.FXRunnable;
import bluej.utility.javafx.JavaFXUtil;
import bluej.utility.javafx.SharedTransition;
import bluej.utility.javafx.binding.ConcatMapListBinding;
import bluej.utility.javafx.binding.DeepListBinding;

public class FormalParameters
{       
protected final InteractionManager editor;
   
   protected final ObservableList<FormalParameter> params;
   
   protected final Frame parentFrame;
   
   protected final CodeFrame<? extends CodeElement> codeParentFrame;
   
   private final FrameContentRow row;
   
   protected String stylePrefix;
   
   private final SlotLabel spacer;
   
   private final SlotLabel open = new SlotLabel("(");
   
   private final SlotLabel close = new SlotLabel(")");
   
   private boolean normalView = true;
   
   private FXRunnable updateSlots;

   
   public FormalParameters(InteractionManager editor, Frame parentFrame, CodeFrame<? extends CodeElement> codeParentFrame,
                           
   FrameContentRow row, String stylePrefix)
   {        
      this.editor = editor;
       
      this.parentFrame = parentFrame;
       
      this.codeParentFrame = codeParentFrame;
      if (parentFrame != codeParentFrame)
           
      throw new IllegalArgumentException("codeFrame and codeParentFrame should be identical");
       
      this.row = row;
       
      this.stylePrefix = stylePrefix;
      params = FXCollections.observableArrayList();

      open.getStyleClass().add("bracket-label");
       
      spacer = new SlotLabel(" ");
      JavaFXUtil.addStyleClass(spacer, "param-spacer");
      spacer.setOpacity(0.0);
      spacer.setCursor(Cursor.TEXT);
      spacer.setOnMouseClicked(e -> {
      if (normalView)
               {
         addNewAfter(null).requestFocus(Focus.LEFT);
         e.consume();                 
         }         
      });
   close.getStyleClass().add("bracket-label");     
   }

   
public FormalParameter findFormal(HeaderItem slot)
   {
   return params.stream().filter(p -> p.getType() == slot || p .getName() == slot).findFirst().orElse(null);     
   }
    
   
private FormalParameter createFormal(TypeSlotFragment type, NameDefSlotFragment name)
   {        
   BooleanProperty freshProperty = new SimpleBooleanProperty(true);
   Runnable checkStillFocused = () -> {
           
   
   freshProperty.set(freshProperty.get() && params.stream().anyMatch(p -> p.isFocused()));         
   };
final TypeSlot typeSlot = new TypeSlot(editor, parentFrame, codeParentFrame, row, TypeSlot.Role.DECLARATION, stylePrefix)
       {            
   @Override
           
   protected BooleanExpression getFreshExtra(CodeError err)
           {
      if (err instanceof EmptyError)
                   
      return freshProperty;
               
      else{ return super.getFreshExtra(err);
         }             
      }

           
   @Override
           
   public void lostFocus()
           {
      super.lostFocus();
               
      
      
      if (freshProperty.get())
      Platform.runLater(checkStillFocused);             
      }         
   };
final VariableNameDefTextSlot nameSlot = new VariableNameDefTextSlot(editor, parentFrame, codeParentFrame, row, stylePrefix)
       {            
   @Override
           
   protected BooleanExpression getFreshExtra(CodeError err)
           {
      if (err instanceof EmptyError)
                   
      return freshProperty;
               
      else{ return super.getFreshExtra(err);
         }             
      }

           
   @Override
           
   public void lostFocus()
           {
      super.lostFocus();
               
      
      
      if (freshProperty.get())
      Platform.runLater(checkStillFocused);             
      }         
   };
TypeSlot paramType = typeSlot;
paramType.setText(type);
paramType.setSimplePromptText("paramType");
paramType.addClosingChar(' ');
paramType.addFocusListener(parentFrame);
paramType.addBackspaceAtStartListener(() -> backSpacePressedAtStart(paramType));
paramType.addDeleteAtEndListener(() -> deletePressedAtEnd(paramType));
paramType.onTopLevelComma((before, after) -> {
FormalParameter newFormal = addNewBefore(findFormal(paramType));
newFormal.getName().setText(before);
paramType.setText(after);
           

if (Platform.isFxApplicationThread())
JavaFXUtil.runNowOrLater(() -> paramType.requestFocus(Focus.LEFT));         
});
TextSlot<NameDefSlotFragment> paramName = initialiseTextSlot("paramName", name, nameSlot);
        
       
return new FormalParameter(paramType, paramName);     
}
   
private <F extends TextSlotFragment> TextSlot initialiseTextSlot(String promptText, F value, TextSlot<F> textSlot)
   {
   textSlot.setPromptText(promptText);
   textSlot.setText(value);

       
   textSlot.addValueListener(new SlotValueListener() {            
      public boolean valueChanged(HeaderItem slot, String oldValue, String newValue, FocusParent<HeaderItem> parent)
           {
         if (newValue.contains(",")) {
            FormalParameter newFormal = addNewAfter(findFormal(slot));
            if (Platform.isFxApplicationThread())
            JavaFXUtil.runPlatformLater(() -> newFormal.requestFocus(Focus.LEFT));
                   
            return false;                 
            }

         if (newValue.contains(")")) {
            if (newValue.endsWith(")")) {
               parent.focusRight(textSlot);                     
               }
                   
            return false;                 
            }

               
         return true;             
         }

           
      @Override
           
      public void backSpacePressedAtStart(HeaderItem slot)
           {
         FormalParameters.this.backSpacePressedAtStart(slot);             
         }

           
      @Override
           
      public void deletePressedAtEnd(HeaderItem slot)
           {
         FormalParameters.this.deletePressedAtEnd(slot);             
         }         
      });
   textSlot.addFocusListener(parentFrame);
   textSlot.addValueListener(SlotTraversalChars.IDENTIFIER);
       
   return textSlot;     
   }
    
   
public void addFormal(TypeSlotFragment type, NameDefSlotFragment name)
   {
   params.add(createFormal(type, name));     
   }

   
private FormalParameter addNewBefore(FormalParameter before)
   {        
   return insertBefore(before, createFormal(new TypeSlotFragment("", ""), new NameDefSlotFragment("")));     
   }
    
   
private FormalParameter addNewAfter(FormalParameter after)
   {        
   return insertAfter(after, createFormal(new TypeSlotFragment("", ""), new NameDefSlotFragment("")));     
   }

   
private FormalParameter insertBefore(FormalParameter before, FormalParameter slot)
   {
   params.add(before == null ? 0 : params.indexOf(before), slot);
   editor.modifiedFrame(parentFrame, false);
       
   return slot;     
   }
    
   
private FormalParameter insertAfter(FormalParameter after, FormalParameter slot)
   {
   params.add(after == null ? 0 : params.indexOf(after) + 1, slot);
   editor.modifiedFrame(parentFrame, false);
       
   return slot;     
   }

   

| Ensures that there is at least one parameter in the parameters list. | @return true if we had to add a new parameter, false if there already was at least one | public boolean ensureAtLeastOneParameter() { if (params.isEmpty()) { addNewAfter(null); return true; } else { return false; } }
| | |public FormalParameter addSlot(TypeSlotFragment type, NameDefSlotFragment name) | |{}return insertSlot(null, prepareSlot(type, name)); | |} | |public FormalParameter addEmptySlot() | |{} return addSlot(new TypeSlotFragment(""), new NameDefSlotFragment("")); } public void setSlots(List<String> types, List<String> names) {}if (types.size() != names.size()) {} throw new IllegalArgumentException("Types and names must be same length"); } while (slotCount() > 0) {}deleteFirstSlot(); } for (int i = 0; i < types.size(); i++) {} insertSlot(null, prepareSlot(new TypeSlotFragment(types.get(i)), new NameDefSlotFragment(names.get(i)))); | |} | |} | |public boolean showError(JavaCompileError err) | |{} for (FormalParameter pairSlot : slots) {}if (pairSlot.showError(err)) {} return true; | |} | |} | |return false; | |} | |@Override | |public void checkForEmptySlot() | |{}if ( isEmpty() ) {}if ( !slots.get(0).isFocused() ) {}cleanup(); | |deleteFirstSlot(); | |} | |} | |} | |@Override | |protected void mergeTwoSlotsContents(FormalParameter slot, FormalParameter prevSlot) | |{} // TODO What we want in this case? | |} | |@Override | |protected FormalParameter getWrapperSlot(Slot slot) | |{}return slots.stream().filter(p -> p.getName() == slot || p.getType() == slot).findFirst().orElse(null); | |} | |@Override | |protected boolean isLastInTheWrapper(Slot slot) | |{}return getWrapperSlot(slot).getName().equals(slot); | |} | |@Override | |public void requestFocus(Focus on) | |{}if (slots.size() == 0) {}addEmptySlot(); | |} | |if (on == Focus.LEFT) {}slots.get(0).requestFocus(on); | |} | |else if (on == Focus.RIGHT) {}slots.get(slots.size() - 1).requestFocus(on); | |} | |} | Here's the outcome of an updated design decision discussion. Given params: | | Void foo(int a, String b) | | Here are some numbered cursor positions of interest: | | Void foo|(|int| |a|, |String b|) | 0 1 2 3 4 5 6 | | You can press backspace at 1, 3, 5 without character directly before. For those: | | 1 [start of type of first param]: | Move cursor left to end of method name, don't delete anything | 3 [start of name of any param]: | Move cursor left to end of type name, don't delete anything | 5 [start of type of non-first param]: | If current parameter is totally blank, delete it and move left. | Otherwise, delete parameter beforehand. | (Note: if both are blank, it doesn't matter which we delete) | private boolean backSpacePressedAtStart(HeaderItem slot) { FormalParameter formal = findFormal(slot); if (formal.getType() == slot) { int index = params.indexOf(formal); if (index == 0) { row.focusLeft(formal.getType()); return true; } else { if (formal.getName().isAlmostBlank() && formal.getType().isAlmostBlank()) { deleteFormal(formal); params.get(index - 1).getName().requestFocus(Focus.RIGHT); return true; } else { deleteFormal(params.get(index - 1)); } } } else { formal.getType().requestFocus(Focus.RIGHT); return true; } return false; }
| Here's the outcome of an updated design decision discussion. Given params: | | Void foo(int a, String b) | | Here are some numbered cursor positions of interest: | | Void foo|(|int| |a|, |String b|) | 0 1 2 3 4 5 6 | | You can press delete at 0, 2, 4, 6 without character directly after. For those: | | 0 [end of method name]: | Delete first parameter | 2 [end of type of any param]: | Do nothing | 4 [end of name of non-last param]: | If the current parameter is empty, delete it. | Otherwise, delete the parameter to the right. | (Note: if both are blank, it doesn't matter which we delete) | 6 [end of name of last param]: | Do nothing | private boolean deletePressedAtEnd(HeaderItem slot) { FormalParameter formal = findFormal(slot); if (formal.getType() == slot) { } else { int index = params.indexOf(formal); if (index == params.size() - 1) { } else { if (formal.getName().isAlmostBlank() && formal.getType().isAlmostBlank()) { deleteFormal(formal); params.get(index).getType().requestFocus(Focus.LEFT); return true; } else { deleteFormal(params.get(index + 1)); } } } return false; } private void deleteFormal(FormalParameter param) { param.cleanup(); params.remove(param); editor.modifiedFrame(parentFrame, false); } public void checkForEmptySlot() { if ( params.size() == 1 && params.get(0).isEmpty() && !params.get(0).isFocused() ) { deleteFormal(params.get(0)); } } private ObservableList<HeaderItem> boundSlots; public ObservableList getSlots() { if (boundSlots == null) { boundSlots = FXCollections.observableArrayList(); ConcatMapListBinding.bind(boundSlots, params, FormalParameter::getSlots); DeepListBinding<HeaderItem> binding = new DeepListBinding<HeaderItem>(boundSlots) { { updateSlots = this::update; } @Override protected Stream> getListenTargets() { return Stream.concat(Stream.of(params), params.stream().map(FormalParameter::getSlots)); } @Override protected Stream calculateValues() { ArrayList<ObservableList<HeaderItem>> commas = new ArrayList<>(); for (int i = 0; i < params.size() - 1; i++) { SlotLabel comma = new SlotLabel(", "); JavaFXUtil.addStyleClass(comma, "formal-comma"); commas.add(FXCollections.observableArrayList(comma)); } Stream<HeaderItem> start = (params.size() == 0) ? Stream.of(open, spacer) : Stream.of(open); return Utility.concat(start, Utility.interleave(params.stream().map(FormalParameter::getSlots), commas.stream()).flatMap(List::stream), Stream.of(close)); } }; binding.startListening(); } return boundSlots; } public boolean isEmpty() { return params.isEmpty(); } public void escape(HeaderItem src) { if (params.size() == 1 && params.get(0).isEmpty()) { row.focusDown(src); } } public void focusBeginning() { if (!params.isEmpty()) params.get(0).requestFocus(Focus.LEFT); } private class FormalParameter { private final TypeSlot type; private final TextSlot<NameDefSlotFragment> name; protected FormalParameter(TypeSlot type, TextSlot<NameDefSlotFragment> name) { this.type = type; this.name = name; }
| | |public String getText() | |{} return type.getText() + " " + name.getText(); } public void positionCaretName(int position) {} name.positionCaret(position); } */ public void cleanup() { | |type.cleanup(); | |name.cleanup(); | |} | |public TypeSlot getType() | |{ | |return type; | |} | |public TextSlot<NameDefSlotFragment> getName() | |{ | |return name; | |} | |/* | |public void setText(String text) | |{} // TODO TEST IT | |type.setText(text); | |} | |public void positionCaret(int position) | |{} // TODO TEST IT | |type.positionCaret(position); | |} public ParamFragment getSlotElement() { return new ParamFragment(type.getSlotElement(), name.getSlotElement()); }
| | |public void both(Consumer<TextSlot<?>> f) | |{}f.accept(type); | |f.accept(name); | |} public boolean isEmpty() { return type.isEmpty() && name.isEmpty(); } public boolean isFocused() { return getType().isFocused() || getName().isFocused(); } public void requestFocus(Focus on) { if (on == Focus.LEFT) { type.requestFocus(on); } else if (on == Focus.RIGHT) { name.requestFocus(on); } else if (on == Focus.SELECT_ALL) { bluej.utility.Debug.message("Focus.SELECT_ALL in PairParameterSlot::requestFocus is not implemented"); } } public ObservableList getSlots() { return FXCollections.observableArrayList(type, name); } } public List getSlotElement() { return Utility.mapList(params, FormalParameter::getSlotElement); } public void deleteFirstParam() { if (params.size() > 0) params.remove(0); } public <T> void setParams(List<T> src, Function<T, String> getType, Function<T, String> getName) { params.setAll(Utility.mapList(src, x -> createFormal(new TypeSlotFragment(getType.apply(x), getType.apply(x)), new NameDefSlotFragment(getName.apply(x))))); } public Stream getVars() { return params.stream().map(FormalParameter::getName).map(TextSlot::getText); } public void setView(Frame.View view, SharedTransition animate) { normalView = view == Frame.View.NORMAL; if (updateSlots != null) { updateSlots.run(); } } }
top, use, map, class FormalParameters

.   FormalParameters
.   findFormal
.   createFormal
.   getFreshExtra
.   lostFocus
.   getFreshExtra
.   lostFocus
.   initialiseTextSlot
.   valueChanged
.   backSpacePressedAtStart
.   deletePressedAtEnd
.   addFormal
.   addNewBefore
.   addNewAfter
.   insertBefore
.   insertAfter
.   ensureAtLeastOneParameter
.   backSpacePressedAtStart
.   deletePressedAtEnd
.   deleteFormal
.   checkForEmptySlot
.   getSlots
.   getListenTargets
.   calculateValues
.   isEmpty
.   escape
.   focusBeginning

top, use, map, class FormalParameter

.   FormalParameter
.   getSlotElement
.   isEmpty
.   isFocused
.   requestFocus
.   getSlots
.   getSlotElement
.   deleteFirstParam
.   setParams
.   getVars
.   setView




608 neLoCode + 102 LoComm