-1

I'm developing a new look and feel and now I have a bug with the component JtextField when to use it inside the JTabbledPane component.

So the bug is this

enter image description here

I have this code for my JTextFieldUI, it extends BasicLookAndFell

public class MyPersonalFieldUI extends BasicTextFieldUI {

    protected static final String PROPERTY_LINE_COLOR = "lineColor";
    protected static final String PROPERTY_SELECTION_COLOR = "selectionColor";
    protected static final String PROPERTY_SELECTION_TEXT_COLOR = "selectedTextColor";
    protected static final String ProprietyPrefix = "TextField";

    protected boolean drawLine;
    protected JTextComponent textComponent;
    protected Color background;
    protected Color foreground;
    protected Color activeBackground;
    protected Color activeForeground;
    protected Color inactiveBackground;
    protected Color inactiveForeground;
    protected Color colorLineInactive;
    protected Color colorLineActive;
    protected Color colorLine;
    protected FocusListener focusListenerColorLine;
    protected PropertyChangeListener propertyChangeListener;
    protected PropertyChangeSupport propertyChangeSupport;

    public MyPersonalFieldUI() {
        this(true);
    }

    public MyPersonalFieldUI(boolean drawLine) {
        super();
        this.drawLine = drawLine;
        this.focusListenerColorLine = new FocusListenerColorLine();
        this.propertyChangeListener = new MaterialPropertyChangeListener();
        this.propertyChangeSupport = new PropertyChangeSupport(this);
    }

    @Override
    protected String getPropertyPrefix() {
        return ProprietyPrefix;
    }

    public static ComponentUI createUI(JComponent c) {
        return new MyPersonalFieldUI();
    }

    @Override
    public void installUI(JComponent c) {
        super.installUI(c);
        this.textComponent = (JTextField) c;
    }

    @Override
    protected void installDefaults() {
        super.installDefaults();
        installMyDefaults();
    }

    @Override
    public void uninstallUI(JComponent c) {
        super.uninstallUI(c);

        c.setFont(null);
        c.setBackground(null);
        c.setForeground(null);
        c.setBorder(null);
        c.setCursor(null);

    }

    @Override
    protected void uninstallDefaults() {
        super.uninstallDefaults();
        getComponent().setBorder(null);
    }

    @Override
    protected void installListeners() {
        super.installListeners();
        getComponent().addFocusListener(focusListenerColorLine);
        getComponent().addPropertyChangeListener(propertyChangeListener);
        propertyChangeSupport.addPropertyChangeListener(propertyChangeListener);
    }

    @Override
    protected void uninstallListeners() {
        getComponent().removeFocusListener(focusListenerColorLine);
        getComponent().removePropertyChangeListener(propertyChangeListener);
        propertyChangeSupport.removePropertyChangeListener(propertyChangeListener);
        super.uninstallListeners();
    }

    @Override
    public void paintSafely(Graphics g) {
        super.paintSafely(g);

        paintLine(g);
    }

    protected void logicForChangeColorOnFocus(JComponent component, Color background, Color foreground) {
        if (background == null || foreground == null) {
            return;
        }
        JTextField textField = (JTextField) component;
        if (!this.activeForeground.equals(foreground)) { //TODO this comment resolve the issue but I don't not if it introduce some bug
            textField.setSelectedTextColor(inactiveForeground);
        } else {
            textField.setSelectedTextColor(foreground);
        }
        textField.setSelectionColor(background);
    }

    protected void installMyDefaults() {
        this.background = UIManager.getColor(getPropertyPrefix() + ".background");
        this.foreground = UIManager.getColor(getPropertyPrefix() + ".foreground");
        this.activeBackground = UIManager.getColor(getPropertyPrefix() + ".selectionBackground");
        this.activeForeground = UIManager.getColor(getPropertyPrefix() + ".selectionForeground");
        this.inactiveBackground = UIManager.getColor(getPropertyPrefix() + ".inactiveBackground");
        this.inactiveForeground = UIManager.getColor(getPropertyPrefix() + ".inactiveForeground");
        colorLineInactive = UIManager.getColor(getPropertyPrefix() + "[Line].inactiveColor");
        colorLineActive = UIManager.getColor(getPropertyPrefix() + "[Line].activeColor");
        getComponent().setFont(UIManager.getFont(getPropertyPrefix() + ".font"));
        colorLine = getComponent().hasFocus() && getComponent().isEditable() ? colorLineActive : colorLineInactive;
        getComponent().setSelectionColor(getComponent().hasFocus() && getComponent().isEnabled() ? activeBackground : inactiveBackground);
        getComponent().setSelectedTextColor(getComponent().hasFocus() && getComponent().isEnabled() ? activeForeground : inactiveForeground);
        getComponent().setForeground(getComponent().hasFocus() && getComponent().isEnabled() ? activeForeground : inactiveForeground);
        getComponent().setBorder(UIManager.getBorder(getPropertyPrefix() + ".border"));
    }

    protected void logicForPropertyChange(Color newColor, boolean isForeground) {
        if (newColor == null) {
            return;
        }
        if (isForeground && (!newColor.equals(activeForeground) && !newColor.equals(inactiveForeground))) {
            this.activeForeground = newColor;
            getComponent().repaint();
        }
        if (!isForeground && !newColor.equals(activeBackground) && !newColor.equals(inactiveBackground)) {
            this.activeBackground = newColor;
            getComponent().repaint();
        }
    }

    protected void changeColorOnFocus(boolean hasFocus) {
        JTextComponent c = getComponent();
        if (c == null) {
            return;
        }
        if (hasFocus && (activeBackground != null) && (activeForeground != null)) {
            logicForChangeColorOnFocus(c, activeBackground, activeForeground);
            //TODO create a new changePropriety
            paintLine(c.getGraphics());
        }

        if (!hasFocus && (inactiveBackground != null) && (inactiveForeground != null)) {
            logicForChangeColorOnFocus(c, inactiveBackground, inactiveForeground);
            paintLine(c.getGraphics());
        }
        if (c.getGraphics() != null) {
            c.paint(c.getGraphics());
        }
    }

    protected synchronized void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
        if ((propertyName == null || propertyName.isEmpty()) || oldValue == null || newValue == null) {
            throw new IllegalArgumentException("Some property null");
        }
        //TODO refectoring this code
        if (propertyChangeSupport == null || (oldValue != null && newValue != null && oldValue.equals(newValue))) {
            return;
        }
        if (propertyChangeSupport == null || oldValue == newValue) {
            return;
        }
        propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue);
    }

    protected void paintLine(Graphics graphics) {
        if (graphics == null) {
            return;
        }
        JTextComponent c = getComponent();

        if (drawLine) {
            int x = c.getInsets().left;
            int y = c.getInsets().top;
            int w = c.getWidth() - c.getInsets().left - c.getInsets().right;
            graphics.setColor(colorLine);

            graphics.fillRect(x, c.getHeight() - y, w, 1);
        }
    }

    protected class FocusListenerColorLine implements FocusListener {

        @Override
        public void focusGained(FocusEvent e) {
            firePropertyChange(PROPERTY_LINE_COLOR, colorLineInactive, colorLineActive);

            changeColorOnFocus(true);
        }

        @Override
        public void focusLost(FocusEvent e) {
            firePropertyChange(PROPERTY_LINE_COLOR, colorLineActive, colorLineInactive);
            changeColorOnFocus(false);
        }
    }

    protected class MaterialPropertyChangeListener implements PropertyChangeListener {

        @Override
        public void propertyChange(PropertyChangeEvent pce) {
            if (getComponent() == null) {
                return;
            }
            if (pce.getPropertyName().equals(PROPERTY_SELECTION_COLOR)) {
                Color newColor = (Color) pce.getNewValue();
                logicForPropertyChange(newColor, false);
            }

            if (pce.getPropertyName().equals(PROPERTY_SELECTION_TEXT_COLOR)) {
                Color newColor = (Color) pce.getNewValue();
                logicForPropertyChange(newColor, true);
            }

            if (pce.getPropertyName().equals(PROPERTY_LINE_COLOR)) {
                Color newColor = (Color) pce.getNewValue();
                colorLine = newColor;
                getComponent().repaint();
            }

            if (pce.getPropertyName().equals("background")) {
                getComponent().repaint();
            }
        }
    }

}

Sorry for my big code but I think is necessary for find my error inside it

also, this is my minimal reproducible example

public class DemoLookAndFeel extends JFrame {

    static {
        try {
             UIManager.setLookAndFeel(new MyLookAndFeel());
        } catch (UnsupportedLookAndFeelException ex) {
        }
    }

    public void init() {
        JPanel panelOne = new JPanel();
        panelOne.add(new JTextField("write inside me and change the tab"));

        JPanel panelTwo = new JPanel();
        //panelTwo.add(new Label("Now seee the JTextField?"));

        JTabbedPane tabbedPane = new JTabbedPane();

        tabbedPane.add("One", panelOne);
        tabbedPane.add("Two", panelTwo);

        this.setContentPane(tabbedPane);

        this.setSize(800,800);
        this.pack();
        this.setLocationRelativeTo(null);
        this.setVisible(true);
    }

    private static class MyLookAndFeel extends BasicLookAndFeel {

        @Override
        public String getName() {
            return "my look and feel";
        }

        @Override
        public String getID() {
            return "qwerty";
        }

        @Override
        public String getDescription() {
            return "";
        }

        @Override
        public boolean isNativeLookAndFeel() {
            return false;
        }

        @Override
        public boolean isSupportedLookAndFeel() {
            return true;
        }

        @Override
        protected void initClassDefaults(UIDefaults table) {
            super.initClassDefaults(table); 
              table.put("TextFieldUI", MyPersonalFieldUI.class.getCanonicalName());
        }



        @Override
        protected void initSystemColorDefaults(UIDefaults table) {
            super.initSystemColorDefaults(table); 

            table.put("TextField.background", Color.GRAY);
            table.put("TextField.foreground", Color.BLACK);
            table.put("TextField.inactiveForeground", Color.BLACK);
            table.put("TextField.inactiveBackground", Color.GRAY);
            table.put("TextField.selectionBackground", Color.BLUE);
            table.put("TextField.selectionForeground", Color.WHITE);
            table.put("TextField[Line].inactiveColor", Color.WHITE);
            table.put("TextField[Line].activeColor", Color.BLUE);
        }

    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                DemoLookAndFeel demo = new DemoLookAndFeel();
                demo.init();
            }
        });
    }

}

ps:Sorry for my Big code

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
vincenzopalazzo
  • 1,487
  • 2
  • 7
  • 35
  • (1-) Cross posted: https://coderanch.com/t/716638/java/JTextFieldUI-painted- tab-JTabbedPane-personal. So you have an answer here. Why have you not updated your other question indicating you found an answer so people don't spend time answering a question that already has an answer? – camickr Sep 19 '19 at 13:52
  • @camickr I would have done it, as soon as I returned home, because I don't have my PC behind, on other occasions I always kept my questions updated in case of solutions in other forums. I just need some time, questions of hours, sorry – vincenzopalazzo Sep 19 '19 at 14:04

1 Answers1

1

The problem is on how you manipulate painting. Inside changeColorOnFocus (called by a FocusListener) method you have these lines:

if (c.getGraphics() != null) {
    c.paint(c.getGraphics());
}

Here, you use graphics and do a kind of custom painting outside of a method that is responsible for painting in component-painting-heirarchy. I wish I could explain it better, but long story short, play with Graphics only in methods that give them to you as an argument (and call its super method). In an UI class the method is paintSafely, in a component is paintComponent. They both give you Graphics object hence they are proper for custom painting.

So, the solution is inside your FocusListener to add a flag in order to know whether the component is focused, and call changeColorOnFocus inside paintSafely(Graphics g) method. Your UI class should be changed in the following parts (I don't paste it all since it is kind of big).

private boolean focused; //a field
protected class FocusListenerColorLine implements FocusListener {

    @Override
    public void focusGained(FocusEvent e) {
        firePropertyChange(PROPERTY_LINE_COLOR, colorLineInactive, colorLineActive);
        focused = true;
    }

    @Override
    public void focusLost(FocusEvent e) {
        firePropertyChange(PROPERTY_LINE_COLOR, colorLineActive, colorLineInactive);
        focused = false;
    }
}



@Override
public void paintSafely(Graphics g) {
    super.paintSafely(g);
    paintLine(g);
    changeColorOnFocus(g);
}

protected void changeColorOnFocus(Graphics g) {
    boolean hasFocus = focused;
    JTextComponent c = getComponent();
    if (c == null) {
        return;
    }
    if (hasFocus && (activeBackground != null) && (activeForeground != null)) {
        logicForChangeColorOnFocus(c, activeBackground, activeForeground);
        //TODO create a new changePropriety
        paintLine(c.getGraphics());
    }

    if (!hasFocus && (inactiveBackground != null) && (inactiveForeground != null)) {
        logicForChangeColorOnFocus(c, inactiveBackground, inactiveForeground);
        paintLine(c.getGraphics());
    }
}

Preview:

preview

George Z.
  • 6,643
  • 4
  • 27
  • 47
  • Sorry, I can do only upvoted, please if you want, can you explain it better this problem? – vincenzopalazzo Sep 19 '19 at 11:50
  • 1
    @vincenzopalazzo I wish i knew how to explain it better. Think about this: In order to paint a component, the API calls methods A, B and then C. If you do painting outside of these methods you will have problems since it is not in the sequence. So, you have to override one of these methods to "get into the sequence". So, we prefer to override C method, in order to do the custom painting last. – George Z. Sep 19 '19 at 12:10