mgarin / weblaf

WebLaF is a fully open-source Look & Feel and component library written in pure Java for cross-platform desktop Swing applications.
http://weblookandfeel.com
GNU General Public License v3.0
1.15k stars 235 forks source link

Repeatedly updating components style #591

Open husker-dev opened 4 years ago

husker-dev commented 4 years ago

When I apply my style to a WebButtonwith wrong extends value I get an exception and the button dissapears - it's ok, but after that, when I apply the style with correct extends value I also get an exception and WebButton doesn't appear.

Is there way to return back this WebButton?

Here is my log:

Wrong extends value

<skin xmlns="http://weblookandfeel.com/XmlSkin">
    <id>husker.editor.skin</id>
    <class>com.husker.editor.app.skin.CustomSkin</class>
    <supportedSystems>all</supportedSystems>
    <title>Custom skin</title>
    <description>WebLaF Editor Skin</description>
    <author>Husker</author>

   <include nearClass="com.alee.skin.web.WebSkin">resources/skin.xml</include>

    <style type="button" id="preview" extends="ico">
        <painter>
            <decorations>
                <decoration>
                    <WebShape round="0" />
                </decoration>
            </decorations>
        </painter>
    </style>

</skin>

Exception:
com.alee.managers.style.StyleException: Skin 'Custom skin' doesn't have any styles for component type: button[class javax.swing.JButton, ButtonUI, class com.alee.laf.button.WButtonUI, class com.alee.laf.button.WebButtonUI, StyleId [ id: 'button'; parent: null ]]
    at com.alee.managers.style.data.SkinInfo.getStyle(SkinInfo.java:496)
    at com.alee.managers.style.XmlSkin.getStyle(XmlSkin.java:162)
    at com.alee.managers.style.AbstractSkin.removeSkin(AbstractSkin.java:83)
    at com.alee.managers.style.StyleData.removeSkin(StyleData.java:456)
    at com.alee.managers.style.StyleData.applySkin(StyleData.java:293)
    at com.alee.managers.style.StyleData.applyCustomSkin(StyleData.java:349)
    at com.alee.managers.style.StyleManager.setSkin(StyleManager.java:1215)
    at com.alee.managers.style.StyleManager.setSkin(StyleManager.java:1199)
    at com.husker.editor.app.skin.CustomSkin.lambda$applySkin$0(CustomSkin.java:52)
    at java.lang.Thread.run(Thread.java:748)

Correct extends value

<skin xmlns="http://weblookandfeel.com/XmlSkin">
    <id>husker.editor.skin</id>
    <class>com.husker.editor.app.skin.CustomSkin</class>
    <supportedSystems>all</supportedSystems>
    <title>Custom skin</title>
    <description>WebLaF Editor Skin</description>
    <author>Husker</author>

    <include nearClass="com.alee.skin.web.WebSkin">resources/skin.xml</include>

    <style type="button" id="preview" extends="icon">
        <painter>
            <decorations>
                <decoration>
                    <WebShape round="0" />
                </decoration>
            </decorations>
        </painter>
    </style>

</skin>

Exception:
com.alee.managers.style.StyleException: Skin 'Custom skin' doesn't have any styles for component type: button[class javax.swing.JButton, ButtonUI, class com.alee.laf.button.WButtonUI, class com.alee.laf.button.WebButtonUI, StyleId [ id: 'button'; parent: null ]]
    at com.alee.managers.style.data.SkinInfo.getStyle(SkinInfo.java:496)
    at com.alee.managers.style.XmlSkin.getStyle(XmlSkin.java:162)
    at com.alee.managers.style.AbstractSkin.removeSkin(AbstractSkin.java:83)
    at com.alee.managers.style.StyleData.removeSkin(StyleData.java:456)
    at com.alee.managers.style.StyleData.applySkin(StyleData.java:293)
    at com.alee.managers.style.StyleData.applyCustomSkin(StyleData.java:349)
    at com.alee.managers.style.StyleManager.setSkin(StyleManager.java:1215)
    at com.alee.managers.style.StyleManager.setSkin(StyleManager.java:1199)
    at com.husker.editor.app.skin.CustomSkin.lambda$applySkin$0(CustomSkin.java:52)
    at java.lang.Thread.run(Thread.java:748)
husker-dev commented 4 years ago

I have found the worst solution - recreate Component and apply skin again.

mgarin commented 4 years ago

This does sound like a weird issue, i'll check this tomorrow and see if I can include a fix in v1.2.11 update.

mgarin commented 4 years ago

I found the problem - skin styles compilation happens a bit too late and that causes an issue with the part in StyleManager that applies compiled styles to existing components. It basically starts to ignore existing components once skin has changed.

I will fix this issue shortly so it won't appear in v1.2.11 anymore.

Code I used for testing this case:

public class StyleEditorTest
{
    public static void main ( final String[] args )
    {
        SwingTest.run ( new Runnable ()
        {
            @Override
            public void run ()
            {
                final WebPanel container = new WebPanel ( new BorderLayout ( 0, 15 ) );
                container.setPadding ( 15 );

                final WebButton button = new WebButton ( StyleId.of ( "preview" ), "Sample button" );
                container.add ( button, BorderLayout.NORTH );

                final WebSyntaxArea editor = new WebSyntaxArea (
                        "<skin xmlns=\"http://weblookandfeel.com/XmlSkin\">\n" +
                                "    <id>husker.editor.skin</id>\n" +
                                "    <class>com.alee.skin.light.WebLightSkin</class>\n" +
                                "    <supportedSystems>all</supportedSystems>\n" +
                                "    <title>Custom skin</title>\n" +
                                "    <description>WebLaF Editor Skin</description>\n" +
                                "    <author>Husker</author>\n" +
                                "\n" +
                                "    <include nearClass=\"com.alee.skin.light.WebLightSkin\">resources/web-light-skin.xml</include>\n" +
                                "\n" +
                                "    <style type=\"button\" id=\"preview\" extends=\"ico\">\n" +
                                "        <painter>\n" +
                                "            <decorations>\n" +
                                "                <decoration>\n" +
                                "                    <WebShape round=\"0\" />\n" +
                                "                </decoration>\n" +
                                "            </decorations>\n" +
                                "        </painter>\n" +
                                "    </style>\n" +
                                "\n" +
                                "</skin>",
                        SyntaxPreset.xml, SyntaxPreset.editable, SyntaxPreset.base, SyntaxPreset.historyLimit
                );
                container.add ( editor, BorderLayout.CENTER );

                new DocumentChangeBehavior<WebSyntaxArea> ( editor )
                {
                    @Override
                    public void documentChanged ( @NotNull final WebSyntaxArea component, @Nullable final DocumentEvent event )
                    {
                        try
                        {
                            final XmlSkin xmlSkin = new XmlSkin ( ( SkinInfo ) XmlUtils.fromXML ( editor.getText () ) );
                            StyleManager.setSkin ( xmlSkin );
                        }
                        catch ( final Exception e )
                        {
                            e.printStackTrace ();
                        }
                    }
                }.install ();

                TestFrame.show ( container );
            }
        } );
    }
}
mgarin commented 4 years ago

I've added fix for this issue and also an extra fallback for skin installation to ensure that everything else works correctly after new skin installation failed.

mgarin commented 4 years ago

This fix will be available in v1.2.11 update which will be released today or tomorrow.

husker-dev commented 4 years ago

I have got the same issue in 1.2.11 after wrong extends:

<skin xmlns="http://weblookandfeel.com/XmlSkin">
    <id>husker.editor.skin</id>
    <class>com.husker.editor.app.skin.CustomSkin</class>
    <supportedSystems>all</supportedSystems>
    <title>Custom skin</title>
    <description>WebLaF Editor Skin</description>
    <author>Husker</author>
    <include nearClass="com.alee.skin.light.WebLightSkin">resources/web-light-skin.xml</include>

    <style type="button" id="preview" extends="icon" />

</skin>
com.alee.managers.style.StyleException: Skin 'husker.editor.skin' doesn't have any styles for component type: button[class javax.swing.JButton, ButtonUI, class com.alee.laf.button.WButtonUI, class com.alee.laf.button.WebButtonUI, StyleId [ id: 'button'; parent: null ]]
    at com.alee.managers.style.data.SkinInfo.getStyle(SkinInfo.java:453)
    at com.alee.managers.style.XmlSkin.getStyle(XmlSkin.java:159)
    at com.alee.managers.style.AbstractSkin.removeSkin(AbstractSkin.java:81)
    at com.alee.managers.style.StyleData.removeSkin(StyleData.java:456)
    at com.alee.managers.style.StyleData.applySkin(StyleData.java:293)
    at com.alee.managers.style.StyleData.applyCustomSkin(StyleData.java:349)
    at com.alee.managers.style.StyleManager.setSkin(StyleManager.java:1276)
    at com.alee.managers.style.StyleManager.setSkin(StyleManager.java:1260)
    at com.husker.editor.app.skin.CustomSkin.lambda$applySkin$1(CustomSkin.java:57)
    at java.lang.Thread.run(Thread.java:748)
mgarin commented 4 years ago

Do you also get it in my code example from above?

To check it:

  1. Launch it and add space at the end of any line -> error will appear for the incorrect skin
  2. Fix the extended style from ico to icon -> style should apply correctly now
husker-dev commented 4 years ago

I useStyleManager.setSkin(JComponent, XmlSkin) inside the new Thread and it works almost fine except for wrong extends value. If I use StyleManager.setSkin(XmlSkin) it works fine with extends everytime, but inside new Thread my application freezes.

For my application I prefer change skin in new Thread

(Your example works fine)

mgarin commented 4 years ago

That is actually pretty bad, I mean changing skin on a separate Thread - it has to be done on EDT, just like any other operations with Swing. It is important because when skin is changed it applies itself to all existing components one by one and that involves a lot of work with UI elements. You can mostly work with Swing outside of EDT, but the risk is on you - once UI is big enough you're guaranteed to run into issues and weird exceptions. Same with WebLaF and any UI-related managers or features - I can only guarantee they will function correctly when run on EDT.

So I strongly recommend switching the skin setup to run on EDT and see if it solves your problem or not.

I also have a tool in place with which you can check that you're not running something that should be run on EDT on some other Thread - Forced EDT usage - you can enable the checks and see where your application crashes. Those places are also recommended to be run on EDT.

mgarin commented 4 years ago

Just a note - running stuff on EDT is not really a WebLaF requirement, it is purely dictated by Swing framework. You can check official note in Swing tutorials about EDT usage.

Considering that WebLaF generates thousands of events when skin is being replaced - it is highly possible that you will run into issues with it more often than you normally will with Swing when operating outside of EDT.

husker-dev commented 4 years ago

I understand what are you writing about, but that is different issue. I mean you have fixed issue when style applied to every component, not to one.

Here is comparison (after wrong extends value):

StyleManager.setSkin(JComponent, XmlSkin) :

StyleManager.setSkin(XmlSkin)

Applying skin to one of the components is still strange.

mgarin commented 4 years ago

Ok, that makes sense, it's been a while since I actually applied skin to a particular component so I totally missed it in your comment. Thanks for pointing it out :)

I'll try it and see if I can reproduce & fix the problem quickly.

mgarin commented 4 years ago

I found the source of problem, but it's quite a tricky thing, I'll need some time to figure out how to change it to fix the per-component skin usage (there are actually a few different problems, but in case of editor you're only seeing one of them).

As a temporary solution - you can call install () on your loaded skin before applying it:

final XmlSkin xmlSkin = new XmlSkin ( ( SkinInfo ) XmlUtils.fromXML ( text ) );
xmlSkin.install ();
StyleManager.setSkin ( component, xmlSkin );

This will ensure that corrupted skins do not break component information stored internally in StyleManager.

Just to clarify - install () call on Skin doesn't really install it as the current application skin, it will simply initialize all internal data necessary for the skin to function and in case there are some faulty styles you will receive an exception which will halt further application of that skin to any components.

mgarin commented 4 years ago

This bug actually revealed a few big flaws with the current implementation of the styles loading and usage. The good thing is - I was planning to change behavior overall anyway for the next update, so I will be working on that for quite some time, but as a result it should fix these issues and also improve initialization performance, so loading skin from the XML will be extremely fast.

mgarin commented 4 years ago

Moving this to v1.2.12, not yet sure if fix will be implemented earlier or at the same time as the performance improvements for skin initialization.