violetlib/vaqua

Extended JTextField with DocumentListener and custom AbstractBorder causes random character display corruption

Closed this issue · 5 comments

Using latest VAQua build and Java21 (but also occurs in prior java version).
Apple macOS of course. Occurs in multiple macOS versions.

So, you start typing.. When you use W (for example) the edge of the W is cropped and letters start to merge. If you cursor left and insert characters, the right hand side of the text does not move (it's like it's falling off the edge). Then click CMD-A to select all, and everything reappears.

This does NOT occur in any other LaF, only VAQua (and on Mac).

  1. Refer this video which shows the issue
  2. We've looked long and hard and cannot see that we are doing anything to interrupt the painting / display etc. It's almost like a) it does not understand the width of characters, and b) that something is drawing over the right hand side of the box.
  3. The app's lead developer has suggested perhaps it might be AquaTextFieldUI.java :: getLayoutSizeFromText() where 'if (b instanceof AquaTextComponentBorder)' would be false in this case.
QuickSearchField_bug.mov

I have attached the code for the key Classes we use...

We create the instance doing this:

this.searchField = new QuickSearchField(){
    @Override
    public void updateUI() {
        super.updateUI();
        this.setBackground(MDColors.getSingleton().defaultBackground);
        this.setOuterBackground(MDColors.getSingleton().headerBG);
        this.setForeground(MDColors.getSingleton().defaultTextForeground);
    }
};


final Document document = this.searchField.getDocument();
document.addDocumentListener(new DocumentListener(){

    @Override
    public void changedUpdate(DocumentEvent evt) {
        AccountPanel.this.searchUpdated();
    }

    @Override
    public void removeUpdate(DocumentEvent evt) {
        AccountPanel.this.searchUpdated();
    }

    @Override
    public void insertUpdate(DocumentEvent evt) {
        AccountPanel.this.searchUpdated();
        AccountPanel.this.textUpdated(evt.getDocument());
    }
});
this.searchField.addFocusListener(new FocusAdapter(){

    @Override
    public void focusGained(FocusEvent e) {
        AccountPanel.this.searchField.setCaretPosition(document.getLength());
    }
});

private void searchUpdated() {
    String searchString = this.searchField.getText().trim();
    DisposablePanel currentPanel = this.getVisiblePanel();
    if (currentPanel instanceof AccountDetailPanel) {
        ((AccountDetailPanel)currentPanel).updateSearch(searchString);
    }
    this.helperWindow.updateText(this.searchField, searchString);
}


private void textUpdated(Document document) {
    String text = this.searchField.getText();
    if (text.trim().length() < 3) {
        return;
    }
}

QuickSearchField_bug.txt

Could it be that with a custom border, there is no code running (or something) in that method above?

I haven't studied your code, but it looks like you are painting text but also calling the superclass which will also paint text, so perhaps these two operations are interacting badly. Also, I don't recommend setting a custom border. If you need an extra border, it would be better to embed the text field in a panel and set the custom border on the panel.

I don't think this is the case. You are referring to:

    @Override
    public void paintComponent(Graphics g) {
        this.paintBorder(g);
        super.paintComponent(g);
        String txt = this.placeholderText;
        if (StringUtils.isBlank(txt)) {
            return;
        }
        if (!this.hasFocus() && StringUtils.isBlank(this.getText())) {
            Insets insets = this.border.getBorderInsets(this, new Insets(0, 0, 0, 0));
            g.setFont(this.getFont());
            g.setColor(Color.gray);
            g.drawString(txt, insets.left, this.getHeight() - insets.bottom - g.getFontMetrics().getDescent());
            g.setColor(this.getForeground());
        }
    }

we paint the border, then super to paint the text.. We do not paint the text ourselves... The bit below paints gray placeholder text when the field is empty and not focussed..

.. I guess a key question is what does the VAQua code do in AquaTextFieldUI.java :: getLayoutSizeFromText() when 'if (b instanceof AquaTextComponentBorder' fails as it's not an AquaTextComponentBorder?

And can you explain why you don't recommend setting a custom border? Thanks

The code generally assumes that when an application sets the border, it indicates that the pure native look and feel is not wanted for that component, at least for components where the native look includes a border.

I resolved this issue by triggering repaint(s) in various places.. Not great, but it works....