Different behaviour when adding text at the end of a TextEditor

gui

#1

Hi,

I am trying to implement a TextEditor which adapts its size depending on the length of the entered text.

It works when adding text in the middle of what has been entered already; when adding something to the end, however, the text is “shaking” in an irritating way - as if it was first deleted and reinserted again.

Any idea why this happens and how it could be implemented without this “shaking” behaviour?

This explanation doesn’t make much sense without looking at the behaviour of the TextEditor. I therefore append the source code to this message.

Thanks for your help, Dietrich

PS: I am on a MacBook running macOS Sierra, 10.12.6.

=== code ===

// -*- mode: C++ -*-

#pragma once

#include "../JuceLibraryCode/JuceHeader.h"

class MyTextEditor : public TextEditor,
                     private TextEditor::Listener
{
public:
    MyTextEditor ()
    {
        addListener (this);
    }
    
    void textEditorTextChanged (TextEditor &) override
    {
        // Recalculate the size of the text editor
        // when the text has been edited
        calculateSize();
    }
        
    void calculateSize()
    {
        // Calculate width and height 
        // from the width and height of the text
        int width  = getTextWidth()  + 4;
        int height = getTextHeight() + 4;

        // Ensure minimal width
        int minWidth = 60;
        if (width < minWidth)
            width = minWidth;
        
        // Set size
        setSize (width, height);
    }
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MyTextEditor)
};
    
class MainContentComponent : public Component
{
public:

    MainContentComponent()
    {
        _textEditor = new MyTextEditor();
        _textEditor->setText ("Some text...");
        addAndMakeVisible (_textEditor);
        
        setSize (600, 400);
    }

    void resized() override
    {
        _textEditor->setTopLeftPosition (100, 100);
        _textEditor->calculateSize();
    }

        
private:
    ScopedPointer<MyTextEditor> _textEditor;
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};

// fin.

#2

Adding just 4 pixel padding is not enough room. When you insert character it will be bigger than 4 pixels, this means that the text will not fit into the TextEditor anymore. The TextEditor will scroll only after which it will call the textEditorTextChanged callback. You then resize the TextEditor - now the text fits in your TextEditor, so the TextEditor does not need to scroll anymore and it will redraw - hence the jitter.

So you can either increase the padding so that a single character will always fit at the end of the editor. Or you grab the key press event in the parent component and forward it on to the texteditor. This way you have a chance to resize the texteditor before the texteditor receives the key event. You’d have to make sure though that the key press would actually insert a character though (i.e. you’ll need to handle backspace, arrow keys accordingly).


#3

Hi Fabian,

Thank you very much for your detailed explanation! Is the scrolling algorithm part of juce or is it implemented in the macOS AppKit framework?

Or you grab the key press event in the parent component and forward it on to the texteditor.

I tried to do that - but the only way I found to get hold of the key events was to put another “overlay” component on top of the editor and to add a key listener to it. Unfortunately this would hide the text as well :slight_smile: Is there some more intelligent way to intervene the key events?

Thanks again,
Dietrich


#4

You can subclass TextEditor and then override the keyPress method. Check if the key will expand the editor or not and then forward it on to the base class. Something like this:

class MyTextEditor : public TextEditor
{
public:
    .
    .
    .
    bool keyPressed (const KeyPress& key) override
    {
        // do your stuff
        return TextEditor::keyPressed (key);
    }
};

#5

Hi Fabian,

Thank you again for your fast answer!

You can subclass TextEditor and then override the keyPress method.

That’s what I thought as well. But it seems that the keyPress method of the TextEditor is never called for alphanumerical characters - and therefore neither its overwritten version…

Here are some functions which actually are called:

  JuceNSViewClass::keyDown()
  JuceNSViewClass::insertText()
  TextEditor::insertTextAtCaret()
  TextEditor::resized()
  JuceNSViewClass::keyUp()
  TextEditor::keyStateChanged()

but I was not able to use these functions for intercepting the input.

(Actually for some input the keyPressed method is called and therefore can be intercepted: [delete] is one of them, the arrow keys are others. But for characters like ‘a’, ‘b’, ‘c’ it is not…)

I tried to understand what actually happens by reading the sources - but found them quite difficult to understand. So I finally decided to write an email in the hope that there is an easier solution…

Any idea how to proceed?

Thanks again for your help,

Dietrich


#6

Actually - I think I answered to my own question:

I probably just have to overwrite

TextEditor::insertTextAtCaret()

…for some reason I thought that the scrolling happens before - but it seems to happen after…

I write more later - just wanted to prevent that you invest your time unnecessarily…

Let’s see - I will report my results later…

Dietrich


#7

Hi again,

Thanks to your help I finally found the way to get rid of the jitter. The key was to override the insertTextAtCaret method rater than the keyPressed method.

The implementation of text input for the TextEditor is based on the insertText method as described in the section “Handling Keyboard Actions and Inserting Text” of the “Cocoa Event Handling Guide” [1]. The insertText method on the other side calls the insertTextAtCaret method of the currently active text input target rather than the keyPressed method, which is only used for arrow, home, delete keys and combinations like copy and paste [2]. So I had to override the insertTextAtCaret method to intercept the input in order to be able to resize the editor before the scrolling happens. Doing so no jitter happens anymore when adding text to the end of the editor…

Thanks again for putting me on this trail!

Dietrich

[1] Cocoa Event Handling Guide, Handling Keyboard Actions and Inserting Text
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/EventOverview/HandlingKeyEvents/HandlingKeyEvents.html#//apple_ref/doc/uid/10000060i-CH7-SW13

[2] See the file JUCE/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm for more detailled information.