Write access for node from JComponent

Hello, I created a custom component with swing but I can't change properties of the corresponding concept (properties: isFinished : boolean)

component provider: (node, editorContext)->JComponent { 
  final JCheckBox check = new JCheckBox(); 
  check.addItemListener(new ItemListener() { 
    public void itemStateChanged(ItemEvent e) { 
      node.model/.getRepository().getModelAccess().runWriteAction({ => node.isFinished = check.isSelected(); }); 
    } 
  }); 
  check; 
}

The error message I get when clicking the checkbox is

IllegalModelChangeError: registered node can only be modified inside undoable command
or in 'loading' model [items]

How can I accomplish this? Thanks in advance.
0
22 comments
Take a look at the UI language in the NYoSh code base (org.campagnelab.UI
and org.campagnelab.ui.code).

http://campagnelab.org/extending-mps-editors-with-buttons-just-got-easier/

The org.campagnelab.ui.code.Swing.Button class uses the
runWriteActionInCommand:

public void actionPerformed(ActionEvent event) {

  ModelAccess.instance().runWriteActionInCommand(callback);
}

runWriteActionInCommand is marked deprecated in the MPS platform, but there
is no alternative suggested in the deprecated annotation, and it seems to
be the only one of these methods that works in this case.

You can get the UI code in the NYoSh repo at
https://github.com/CampagneLaboratory/NYoSh

Fabien




On Sat, Aug 23, 2014 at 8:01 AM, fxlex - Meta Programming System <
jetforum@jetbrains.com> wrote:

* Meta Programming System
<http://forum.jetbrains.com/forum/Meta-Programming-System> * > * Write
access for node from JComponent
<http://forum.jetbrains.com/thread/Meta-Programming-System-1015> * 8:01 am
fxlex <http://forum.jetbrains.com/user/fxlex>

Hello, I created a custom component with swing but I can't change
properties of the corresponding concept (properties: isFinished : boolean)

component provider: (node, editorContext)->JComponent {
   final JCheckBox check = new JCheckBox();
   check.addItemListener(new ItemListener() {
     public void itemStateChanged(ItemEvent e) {
       node.model/.getRepository().getModelAccess().runWriteAction({ => node.isFinished = check.isSelected(); });
     }
   });
   check;
}


The error message I get when clicking the checkbox is

IllegalModelChangeError: registered node can only be modified inside undoable command
or in 'loading' model [items]


How can I accomplish this? Thanks in advance.

  JetBrains Forum | Build #176 (May/07/2014 4:41PM) | Feedback
<http://confluence.jetbrains.net/display/JETF/Feedback>




--
Fabien Campagne, PhD – http://campagnelab.org

Assistant Professor,    Dept. of Physiology and Biophysics
          Institute for Computational Biomedicine
Associate Director,      Biomedical Informatics Core,
       Clinical Translational Science Center

Weill Medical College of Cornell University
phone:  (646)-962-5613  1305 York Avenue
fax:    (646)-962-0383  Box 140
New York, NY 10021

Do you speak next-gen?

See how GobyWeb can help simplify your NGS projects at
http://gobyweb.campagnelab.org
0
Your solution nearly worked for me. I imported jetbrains.mps.smodel@java_stub in editor and changed the code to

component provider: (node, editorContext)->JComponent { 
  final JCheckBox check = new JCheckBox(); 
  check.addItemListener(new ItemListener() { 
    public void itemStateChanged(ItemEvent e) { 
      if (e.getStateChange() == ItemEvent.SELECTED) { 
        ModelAccess.instance().runWriteActionInCommand(new Runnable() { 
          public void run() { 
            node.isFinished = true; 
          } 
        }); 
      } else { 
        ModelAccess.instance().runWriteActionInCommand(new Runnable() { 
          public void run() { 
            node.isFinished = false; 
          } 
        }); 
      } 
    } 
  }); 
  check; 
}

The checkbox can be unchecked but checking takes 2 clicks. check.isSelected() is true after the first click but ticked only after 2 clicks which is a bit annoying. Any ideas?
0
There'a an access language for this.
Look at "read action"/"write action"/"command". The last seems to be what you need
0
Sometimes, you are led to make some operations on all the JButtons, JMenus, etc. contained in a JFrame.You may have to output a listing of the controls or output a file showing the state of a Swing window.
0
Hi, I came across this thread while I was also dealing with the integration of a JCheckBox. Don't know if fxlex is still looking for help, but maybe others like me will find this bit helpful.

[fxlex]
The checkbox can be unchecked but checking takes 2 clicks. check.isSelected() is true after the first click but ticked only after 2 clicks which is a bit annoying. Any ideas?"


I experienced the same issue. I was not able to diagnose it, but I solved it by accident, so it's something.

Code
public class ShowAsTableCheckBox extends JCheckBox { 
      
  public ShowAsTableCheckBox(final node<SomeDef> node, final SRepository srepo) { 
    this.addActionListener(new ActionListener() { 
      public void actionPerformed(ActionEvent actionEvent) { 
        final JCheckBox cbx = actionEvent.getSource() as JCheckBox; 
        try { 
          srepo.getModelAccess().executeCommand(new Runnable() { 
            public void run() { 
              try { 
 if (cbx.isSelected()) { 
   node.presentation.set(< table >); 
 } else { 
   node.presentation.set(< text >); 
 } 
              } catch (Throwable t) { 
 error "Nested OMG!", t; 
              } 
            } 
          }); 
        } catch (Exception ex) { 
          error "OMG!", ex; 
        } 
      } 
    }); 
    this.setSelected(node.presentation.is(< table >)); 
  }    
}


The part that did the trick is the very last line. Try initializing the checkbox with a state based on your model. To clarify: SomeDef has a property called "presentation" which is of an enum type with the values "table" or "text".

This is how I use this Checkbox in my editor:
Usage
component provider: (node, editorContext)->JComponent { 
  new ShowAsTableCheckBox(node, editorContext.getRepository()); 
}


I do not know if this is the right thing in general when it comes to altering your model via a Swing component, but it's the only way I found so far.
1
As Fabien suggested, you should use the deprecated ModelAccess:

check.addActionListener(new ActionListener() { 
  public void actionPerformed(ActionEvent p0) { 
    ModelAccess.instance().runWriteActionInCommand(new Runnable() { 
      public void run() { 
        // change your properties
      } 
    }); 
  } 
});
0
@msch95: can you please elaborate on why the deprecated modelaccess is to prefer? Is there something wrong with the approach I suggested? Thanks.
0
Hey Robert!

To be honest, I haven't tried it yet. My sample above just worked out for me.

Fabien wrote:
runWriteActionInCommand is marked deprecated in the MPS platform, but there
is no alternative suggested in the deprecated annotation, and it seems to
be the only one of these methods that works in this case.
0
The question seems to be relevant again so i created a editor cell for checkboxes. You can find the project here (created with MPS 3.2 EAP 4).
I also wrote a short post about it.
0
Hi, this is great! I'd love to look into your implementation since I'm sure I can learn a lot.
However, when I get the project from GitHub, I tried to open it with MPS 3.2.3 and with MPS 3.3 RC1, both times, I was unsuccesful, meaning that I cannot open any concept or editor or anything. I get a bunch of IDE exceptions, and I cannot build the language :(

I know you mentioned you used MPS 3.2 EAP 4, but I start to wonder: how many installations of MPS does one need to have? Or, in other words, do I need to get exactly MPS 3.2 EAP 4 to look at this project?

Btw., in your blog post, you say it is MPS 2.3 EAP 4.

Thanks,
Robert
0
Also, I wonder if you did not mean MPS 3.3 EAP 4 from the start? If not, can you point me to MPS 3.2 EAP 4, please? I cannot find it here: https://confluence.jetbrains.com/display/MPS/JetBrains+MPS+3.2+Download+Page
0
Yes that is a double typo. It is MPS 3.3 EAP 4. The code is based on Fabien's org.campagnelab.mps.UI plugin. I'll update it to 3.3 RC1.
0
Okay, I'm pretty confident now that you were referring to MPS 3.3 EAP 4.

Anyways, with MPS 3.3 RC1, what I had to do was, after opening the project,
. postponing the migration (wizard pops up automatically)
. delete the "Test" model in the com.github.fxlex.ui.sandbox solution
. add a reference to from com.github.fxlex.ui.sandbox -> to the demolang language

That allowed me to open the Test editor.
0
Didn't see that in time, thanks for updating!
0
Should now work with 3.3 RC1.
0
Hi fxlex,
I used your project as a tutorial and I think it helped me to learn a lot, thanks! The problem that remains for me is that the checkbox behaves as if it has three states.
When I look at your demolang, the checkbox behaves "as expected" (clicking it toggles between 'selected' and 'unselected').

When I use the checkbox cell in my editor, however, I want the following checkbox handler implementation:

(editorContext, node)->void { 
  node.enumProp.set(< enumMember >); 
}


With this model operation, the checkbox falls back into its weird "three-state-like" behavior you also experienced in you original question.

In my original implementation with a custom JCheckBox, I got rid of this behavior by initializing the CheckBox's selection property with the default enum value (see http://forum.jetbrains.com/message/Meta-Programming-System-1015-5).

Any idea what the reason for this behavior might be? When I remove my line  node.enumProp.is(< enumMember >); and set the value of a text property instead (like in your demo language), the checkbox behaves fine again.
I also tried to not use the set-method, but rather do this:

(editorContext, node)->void { 
  node.enumProp= enum member value/EnumType: enumMember/;
}


Same result.

Next thing I tried is to enhance your checkbox cell with a "initial value" property since this did the trick for me in my original implementation.

However, as a MPS novice I couldn't make it work. Here is what I tried:
. I added a child to the CheckBox concept called initValueCondition:

concept CheckBox extends EditorCellModel    
  implements <none>          
              
  instance can be root: false 
  alias: checkbox 
  short description: swing CheckBox 
 
  properties: 
  identifier : string 
 
  children: 
  onSelected : OnSelected[1]   
  onDeSelected : OnDeSelected[1] 
  initValueCondition : QueryFunction_NodeCondition[1]           
 
  references: 
  << ... >>  


. In the inspected cell layout, I added this like so:

<default> editor for concept CheckBox       
  node cell layout:          
     [> checkbox <]          
              
  inspected cell layout:     
     [/       
      [> id: { identifier } <]              
      <constant>             
      Initial Value:         
      [> value % initValueCondition % <]    
      <constant>             
      [> on selected: % onSelected % <]     
      [> on de-selected: % onDeSelected % <]
     /]       


The user would than be able to declare the checkbox's initial value like so:

id: cbxSwitchesAsTable  
         
Initial Value:          
value (editorContext, node)->boolean { 
        node.enumProp.is(< enumValueA >); 
      }       
         
on selected: (node, editorContext)->void { 
  node.enumProp.set(< enumValueA >); 
}  
on de-selected: (node, editorContext)->void { 
  node.presentation.set(< enumValueB >); 
}


However, I do not really know how to "get" the value of the query and pass it down to the CheckBox constructor call.
Any ideas or pointers where I can find something like that?

I tried to handle this query similiar than you do the CheckBoxCallBacks (due to lack of better knowledge). I declared an abstract  type called CheckBoxFunction with an abstract method getValue().

In the reduction rule for the CheckBox, I added the implementation of this abstract type and pass it on to the CheckBox:

(node, editorContext)->JComponent { 
  CheckBoxCallBack OnSelectedCallBack = new CheckBoxCallBack(node, editorContext) { 
    public void process(node<> n, EditorContext editorContext) { 
      node<CheckBox> node = (node<CheckBox>) n; 
      $COPY_SRCL$ 
    } 
  }; 
  CheckBoxCallBack OnDeSelectedCallBack = new CheckBoxCallBack(node, editorContext) { 
    public void process(node<> n, EditorContext editorContext) { 
      node<CheckBox> node = (node<CheckBox>) n; 
      $COPY_SRCL$ 
    } 
  }; 
  CheckBoxFunction InitCheckBox = new CheckBoxFunction() { 
    public boolean getValue() { 
      $COPY_SRCL$[return getValue()]; 
    } 
  }; 
   
  string strIdentifier = "$"; 
  return new CheckBox(node : CheckBox, strIdentifier, editorContext, InitCheckBox, OnSelectedCallBack, OnDeSelectedCallBack); 
}


Honestly, I do not know what the "return getValue()" statement does in the COPY_SRCL macro.
Anyways, it compiles and the generated code looks okay as far as I can tell.

However, my editor where I use the checkbox falls back to the defualt editor presentation without giving me any warnings, or errors.

Any ideas how to achieve what I tr to do and where I headed in the wrong direction?
0
"return getValue()" does nothing except preventing a syntax error. From the documentation:
$COPY_SRCL$ - copies the specified collection of nodes and replaces the wrapped code


You can get the statements from a QueryFunction with the $COPY_SRCL macro:

queryFunction.body.statement

In your case you are returning a value from your function and just "copying" the statements doesn't work. You can take a look at reduce_CellModel_JComponent on how to properly handle this case or you try something like this:
public abstract class StatementClass <T> { 
   
  public abstract T get(); 
   
}


StatementClass<Boolean> somethingHelper = new StatementClass<Boolean>() { 
  public Boolean get() { 
    $COPY_SRCL[$return null]; 
  } 
};

Boolean obj = somethingHelper.get();

Then you can pass  obj to the CheckBox constructor.

If your editor shows the default representation or throws a NullPointer you could preview the generated text of an editor that uses the checkbox and check the code for syntax errors.
0
Thanks fxlex, I assumed that "return getValue()" does nothing other than preventing a syntax error. I did study the documentation for COPY_SRCL, but I had trouble understanding what "wrapped code" is referring to (it is obvious when you know it, but without an example and previous exposure to MPS one can only assume it's the code between the brackets).

Turns out I did pretty much what you propose, but I had a bad cast left in my query that caused my editor to fallback to the default representation. It didn't give me a warning, nor an error, so I wonder what goes wrong at runtime, but I do not need that cast anyways so I could savely remove it (it was a leftover from a previous attempt to make the "three state problem" go away: I tried to pass the node, the first argument of the CheckBox constructor, as a node<CheckBox> to be able to access the CheckBox children and properties; this, of course, is not needed).

Anyways, it finally works. I can initialize my CheckBox like so:

id: cbxSwitchesAsTable  
         
Initial Value:          
value (editorContext, node)->boolean { 
         return node.presentation.is(< table >); 
      }
         
on selected: (node, editorContext)->void { 
  node.presentation.set(< table >); 
}  
on de-selected: (node, editorContext)->void { 
  node.presentation.set(< text >); 
}


In this example, the checkbox allows the user to switch between a table or textual representation in the editor. I still don't understand why, without the initialization, the checkbox needs two clicks to show the "check", but with the initialization, it works just fine.

Let me know if you would be interested in me adding this functionality to your github project.

Thanks again,
Robert
0
I am glad you got it working. Yes you can add it to the project. I think other people with the same issue would benefit from it.
0
I still don't understand why, without the initialization, the checkbox needs two clicks to show the "check", but with the initialization, it works just fine.


You need this initialization because the default state of JCheckBox is unchecked. If you open the editor and the property is already set, your checkbox does not "project" the correct state of the model.

That you have to click twice is just an effect of this violation. The reason is, that MPS rebuilds the editor after a model change. On the first click, the property is set to checked, but because the checkbox is recreated, it is unchecked after the rebuild. Then you click a second time and your "on select" is invoked again, but because the same value is written to the property that it already has, there is no need to rebuild the editor and the checkbox is not recreated.
0
Interesting. Thanks for that!
0
Just for the record, Robert's way to run a model modification command is correct. Static ModelAcess.instance() shall not be used, it has been deprecated wittingly, and o.j.m.openapi.module.ModelAccess.executeCommand() is proper replacement for runWriteActionInCommand().
0

Please sign in to leave a comment.