anchor.diff:    begin
       DeleteSelectedText;
     end
  -  else if Length(fText) >= MaxLen then Exit;
  +  else if Length(fText) >= GetMaxLength then Exit;
   
     Insert(Key, fText, CursorPos + 1);
     CursorPos := CursorPos + 1; //Before ValidateText so it moves the cursor back if the new char was invalid
  
positive.code: end;
  
  
  procedure TKMEdit.SetSelectionStart(aValue: Integer);
  begin
    if fSelectable then
      fSelectionStart := EnsureRange(aValue, 0, Length(fText));
  end;
  
  
  procedure TKMEdit.SetSelectionEnd(aValue: Integer);
  begin
    if fSelectable then
      fSelectionEnd := EnsureRange(aValue, 0, Length(fText));
  end;
  
  
  procedure TKMEdit.SetText(aText: UnicodeString);
  begin
    fText := aText;
    ValidateText; //Validate first since it could change fText
    CursorPos := Math.Min(CursorPos, Length(fText));
    //Setting the text should place cursor to the end
    fLeftIndex := 0;
    SetCursorPos(Length(Text));
  end;
  
  
  function TKMEdit.IsCharValid(aChar: WideChar): Boolean;
  begin
    Result := IsCharAllowed(aChar, fAllowedChars);
  end;
  
  
  //Validates fText basing on predefined sets of allowed or disallowed chars
  //It iterates from end to start of a string - deletes chars and moves cursor appropriately
  procedure TKMEdit.ValidateText;
  var I: Integer;
  begin
    //Parse whole text incase user placed it from clipboard
    //Validate contents
    for I := Length(fText) downto 1 do
    if not IsCharValid(fText[I]) then
    begin
      Delete(fText, I, 1);
      if CursorPos >= I then //Keep cursor in place
        CursorPos := CursorPos - 1;
    end;
  
    //Validate length
    if Length(fText) > MaxLen then
      fText := Copy(fText, 0, MaxLen);
  end;
  
  
  //Key events which have no effect should not be handled (allows scrolling while chat window open with no text entered)
  function TKMEdit.KeyEventHandled(Key: Word; Shift: TShiftState): Boolean;
  begin
    Result := True;
  
    //Don't include backspace/delete because edits should always handle those. Otherwise when you
    //press backspace repeatedly to remove all characters it will apply other shortcuts like
    //resetting the zoom if you press it once too many times.
    case Key of
      VK_UP,
      VK_DOWN,
      VK_LEFT,
      VK_RIGHT,
      VK_HOME,
      VK_END: Result := (fText <> ''); //These keys have no effect when text is blank
    end;
  
    //We want these keys to be ignored by TKMEdit
    if Key in [VK_F1..VK_F12, VK_ESCAPE, VK_RETURN, VK_TAB] then Result := False;
  
    //Ctrl can be used as an escape character, e.g. CTRL+B places beacon while chat is open
    if ssCtrl in Shift then Result := (Key in [Ord('A'), Ord('C'), Ord('X'), Ord('V')]);
  
    // If key is ignored, then check if can still handle it (check via OnIsKeyEventHandled)
    if not Result and Assigned(OnIsKeyEventHandled) then
      Result := OnIsKeyEventHandled(Self, Key);
  
  end;
  
  
  procedure TKMEdit.DeleteSelectedText;
  begin
    Delete(fText, fSelectionStart+1, fSelectionEnd-fSelectionStart);
    if CursorPos = fSelectionEnd then
      CursorPos := CursorPos - (fSelectionEnd-fSelectionStart);
    ResetSelection;
  end;
  
  
  function TKMEdit.KeyDown(Key: Word; Shift: TShiftState): Boolean;
  begin
    Result := KeyEventHandled(Key, Shift);
    if inherited KeyDown(Key, Shift) or ReadOnly then Exit;
  
negative.code: end;
  
  
  procedure TKMSelectableEdit.MouseUp(X, Y: Integer; Shift: TShiftState; Button: TMouseButton);
  begin
    if ReadOnly then Exit;
    inherited;
  