Feature #2016
implement CURRENT-WINDOW, ACTIVE-WINDOW and DEFAULT-WINDOW system handles
100%
Subtasks
Related issues
History
#1 Updated by Greg Shah about 11 years ago
CURRENT-WINDOW is assignable but neither DEFAULT-WINDOW or ACTIVE-WINDOW can be assigned.
DEFAULT-WINDOW is a handle to the unnamed window that exists at the start of every GUI session AND which is the only window that exists in a CHUI environment.
CURRENT-WINDOW is a handle to the window that will be the default parent for any frames/dialogs created in the current procedure (unless the code uses an explicit IN WINDOW phrase). It can be modified for the scope of a given procedure using the proc_handle:CURRENT-WINDOW. It can be assigned, to allow application control over the default parent. Initially (until tha app assigns to it) this will be equivalent to the DEFAULT-WINDOW.
ACTIVE-WINDOW is a handle to the window that most recently received an ENTRY event.
All 3 handles can be used to read/write attributes and methods. All 3 handles are full fledged window widgets. These are just well-known named handles that can be used to reference the associated window.
Common use cases:
CURRENT-WINDOW = some-window-handle. CURRENT-WINDOW = ACTIVE-WINDOW. THIS-PROCEDURE:CURRENT-WINDOW = some-window-handle. /* this one is not the CURRENT-WINDOW handle, but is highly related */ my-handle-var = CURRENT-WINDOW. my-handle-var = DEFAULT-WINDOW. my-handle-var = ACTIVE-WINDOW. RUN some-proc (INPUT CURRENT-WINDOW, ...). my-num-var = CURRENT-WINDOW:HWND. CURRENT-WINDOW:SENSITIVE = true. WAIT-FOR CLOSE of CURRENT-WINDOW. ON WINDOW-CLOSE OF CURRENT-WINDOW DO:
These handles should be maintained in the context-local portion of LogicalTerminal. This will need to integrate down to the client, so that changes are bidirectionally reflected. For example, the ENTRY event needs to maintain the ACTIVE-WINDOW on the client AND when control transfers back to the server, this must be reflected there too. Likewise, when CURRENT-WINDOW is assigned on the server it must be pushed down to the client the next time control transfers there.
We may have some fledgling support today, but it needs to be expanded to be complete.
#2 Updated by Costin Savin about 11 years ago
What will be the approach for system handles besides CURRENT-WINDOW that are assigned as left value.
It seems that for DEFAULT-WINDOW and ACTIVE-WINDOW (and also SESSION) an instruction which assigns a variable handle to them is valid though it does not change their value as opposed to CURRENT-WINDOW:
DEFINE VARIABLE win AS HANDLE NO-UNDO. DEFINE SUB-MENU myfile MENU-ITEM m1 LABEL "Save" MENU-ITEM m2 LABEL "Save as" MENU-ITEM m3 LABEL "Exit". DEFINE MENU mybar MENUBAR SUB-MENU myfile LABEL "File". CREATE WINDOW win ASSIGN MENUBAR = MENU mybar:HANDLE. DEFAULT-WINDOW = win. ACTIVE-WINDOW = win. IF DEFAULT-WINDOW = win THEN MESSAGE "Default changed to win". IF DEFAULT-WINDOW = win THEN MESSAGE "Active Window changed to win". CURRENT-WINDOW = win. IF CURRENT-WINDOW = win THEN MESSAGE "Current-window changed".
What we do in the case of the system handles which shouldn't be setable? Throw an error at conversion, emit a set method and do nothing or emit a readOnlySystemHandle() method which throws an error?
#3 Updated by Costin Savin about 11 years ago
- File cs_upd20130222c.zip added
Added intermediate update merged to latest revision, this should cover the cases showed here minus the case for which we try to assign to a readable system-handle.
#4 Updated by Greg Shah about 11 years ago
Is the assignment of DEFAULT-WINDOW and ACTIVE-WINDOW (and SESSION) silently tolerated (and ignored) at runtime in the 4GL? Or is your comment regarding valid code only?
Throw an error at conversion, emit a set method and do nothing or emit a readOnlySystemHandle() method which throws an error?
We should do exactly what the 4GL does. If it generates an error at runtime, then we generate the same error. If it silently ignores the assignment, then we would provide a similar assignment that does nothing. Normally, we would prefer to just drop the code from the converted application. But it is possible that in the 4GL, the expression on the right side of the assignment operator (the rvalue) could have side-effects that must be maintained.
#5 Updated by Constantin Asofiei about 11 years ago
- File window_handle.p added
- File ca_upd20130223a.zip added
Correct, 4GL silently ignores assignments of the SESSION, FILE-INFO, ERROR-STATUS, LAST-EVENT, SOURCE-PROCEDURE, TARGET-PROCEDURE, ACTIVE-WINDOW and DEFAULT-WINDOW. If THIS-PROCEDURE is used, then it gets compile-time error. The solution is:Is the assignment of DEFAULT-WINDOW and ACTIVE-WINDOW (and SESSION) silently tolerated (and ignored) at runtime in the 4GL?
- for system handles SESSION, FILE-INFO, ERROR-STATUS, LAST-EVENT: as their asHandle implementation always returns a new handle instance, calling i.e.
SessionUtils.asHandle().assign(h)
will have no effect. - for TARGET- and SOURCE-PROCEDURE: the ProcedureManager.target/sourceProcedure APIs always return a new handle instance, so no problem here either.
- for DEFAULT-WINDOW and ACTIVE-WINDOW: LogicalTerminal.setDefaultWindow and LogicalTerminal.setActiveWindow are emitted, which silently ignore the assignment.
With these changes, I don't think WindowWidget.assign can be called (and I don't think it should be called at all), so I removed it. Also, I left LogicalTerminal.sessionWindow intact (as now it is returned by both currentWindow and activeWindow), as I don't want to break anything.
#6 Updated by Constantin Asofiei about 11 years ago
Conversion regression testing passed on my system (no changes in generated code).
#7 Updated by Greg Shah about 11 years ago
I assume this update is merged up to bzr 10190? It is ready for testing?
#8 Updated by Constantin Asofiei about 11 years ago
Yes, is merged and ready for testing.
#9 Updated by Greg Shah about 11 years ago
The code looks good. It is in conversion testing now.
#10 Updated by Greg Shah about 11 years ago
Conversion testing has passed. Please check it in to bzr and distribute via email.
#11 Updated by Constantin Asofiei about 11 years ago
Committed to bzr revision 10191.
#12 Updated by Constantin Asofiei about 11 years ago
There is a problem when CURRENT/DEFAULT/ACTIVE-WINDOW are not used in an assignment, as in:
function func0 returns handle. return current-window. end.
This should convert to something like LogicalTerminal.currentWindowHandle
#13 Updated by Greg Shah about 11 years ago
OK, please do fix it.
#14 Updated by Constantin Asofiei about 11 years ago
- File ca_upd20130223g.zip added
Fixed the issues at comment 12.
#15 Updated by Greg Shah about 11 years ago
The change looks fine. This is being conversion tested now.
#16 Updated by Greg Shah about 11 years ago
There are major changes in Majic. And those changes don't compile. This is an example change:
diff -r generated/20130223c/src/aero/timco/majic/util/VchviewVchHdr.java generated/20130223d/src/aero/timco/majic/util/VchviewVchHdr.java 136c136 < list4.addEvent("window-close", currentWindow()); --- > list4.addEvent("window-close", LogicalTerminal.currentWindowHandle()); 223c223 < currentWindow().apply("window-close"); --- > LogicalTerminal.currentWindowHandle().apply("window-close"); 232c232 < currentWindow().apply("window-close"); --- > LogicalTerminal.currentWindowHandle().apply("window-close");
Here are some representative compile errors (different file but same idea):
[javac] /home/ges/timco/src/aero/timco/majic/so/Jmatloff.java:208: error: no suitable method found for addEvent(String[],handle) [javac] list7.addEvent(new String[] [javac] ^ [javac] method EventList.addEvent(String,boolean) is not applicable [javac] (actual argument String[] cannot be converted to String by method invocation conversion) [javac] method EventList.addEvent(String) is not applicable [javac] (actual and formal argument lists differ in length) [javac] method EventList.addEvent(String[],boolean) is not applicable [javac] (actual argument handle cannot be converted to boolean by method invocation conversion) [javac] method EventList.addEvent(String[]) is not applicable [javac] (actual and formal argument lists differ in length) [javac] method EventList.addEvent(String[],CommonFrame) is not applicable [javac] (actual argument handle cannot be converted to CommonFrame by method invocation conversion) [javac] method EventList.addEvent(String,CommonFrame) is not applicable [javac] (actual argument String[] cannot be converted to String by method invocation conversion) [javac] method EventList.addEvent(String,handle) is not applicable [javac] (actual argument String[] cannot be converted to String by method invocation conversion) [javac] method EventList.addEvent(String,GenericWidget,boolean) is not applicable [javac] (actual and formal argument lists differ in length) [javac] method EventList.addEvent(String,GenericWidget[],boolean) is not applicable [javac] (actual and formal argument lists differ in length) [javac] method EventList.addEvent(String[],GenericWidget,boolean) is not applicable [javac] (actual and formal argument lists differ in length) [javac] method EventList.addEvent(String,GenericWidget) is not applicable [javac] (actual argument String[] cannot be converted to String by method invocation conversion) [javac] method EventList.addEvent(String[],GenericWidget) is not applicable [javac] (actual argument handle cannot be converted to GenericWidget by method invocation conversion) [javac] method EventList.addEvent(String,GenericWidget[]) is not applicable [javac] (actual argument String[] cannot be converted to String by method invocation conversion) [javac] method EventList.addEvent(String[],GenericWidget[]) is not applicable [javac] (actual argument handle cannot be converted to GenericWidget[] by method invocation conversion) [javac] method EventList.addEvent(String[],GenericWidget[],boolean) is not applicable [javac] (actual and formal argument lists differ in length) [javac] /home/ges/timco/src/aero/timco/majic/so/Jmatloff.java:600: error: cannot find symbol [javac] LogicalTerminal.currentWindowHandle().apply("window-close"); [javac] ^ [javac] symbol: method apply(String) [javac] location: class handle [javac] /home/ges/timco/src/aero/timco/majic/so/Serv21.java:3775: error: cannot find symbol [javac] LogicalTerminal.currentWindowHandle().apply("window-close");
#17 Updated by Constantin Asofiei about 11 years ago
I was thinking for the implementation of the window system-handles to allow them to convert to an API which returns the actual widget, to avoid the handle unwrapping when events/attributes are accessed... but I don't think we can get away with this, as these handles (beside RETURN) can be set for a handle parameter in a func/proc call. I think it will be too time consuming to determine all cases when the window system handles need to act as a real handle and when it can act as a widget, so the solution will be to convert them as real handles, the way SELF was converted too.
#18 Updated by Constantin Asofiei about 11 years ago
- File ca_upd20130224b.zip added
- File window_handle.p added
Attached update makes the window system handles to convert as true handles; also, I've removed the THIS-PROCEDURE hack which converted it to currentWindow, if was used with WAIT-FOR, ON or APPLY.
Conversion regression testing has passed on my machine.
#19 Updated by Greg Shah about 11 years ago
Checked in as bzr revision 10196.
#20 Updated by Constantin Asofiei over 10 years ago
Check #1791 note 30 and forward for a discussion for findings about the current-/default-/active-window behaviour in batch and chui modes. Also, looks like width (and maybe other) attributes can not be changed in chui mode.
#21 Updated by Constantin Asofiei over 10 years ago
What needs to be done in CHUI/batch mode, for M7 (part of #2018):
- the window system handles all point to the same resource (#1791 - note 40). This might mean that in CHUI mode only one WINDOW resource exists, for a client session.
- check what happens when the window system handle is the left-side operand in an assignment (and it tries to assign it to a handle, system handle or to unknown). This should be either no-op, error message or validation of the right-side operand (followed by possible assignment).
There should be no need to touch UI-related code on P2J client side.
#22 Updated by Vadim Gindin over 10 years ago
Here are test cases:
1) same.p - testing equality of all three handles.
if current-window <> default-window then message "current do not equal to default". if current-window <> active-window then message "current do not equal to active". if default-window <> active-window then message "default do not equal to active".
Works for chui and batch modes.
2) width.p - try to assign an attribute of window system handles.
def var w as int. w = current-window:width. current-window:width=101. if current-window:width <> w then message "current width was changed". w = active-window:width. active-window:width=101. if active-window:width <> w then message "active width was changed". w = default-window:width. default-window:width=101. if default-window:width <> w then message "default width was changed". message "end.".
CHUI. Shows an error "** Unable to set attribute WIDTH on WINDOW widget in the current environment. (4079)"
3 times and "end." string at the end of output.
BATCH. Shows one error "Cannot access the WIDTH attribute because the widget does not exist. (3140)" once.
It seems that in batch mode these handles do no point on window.
3) width-noerror.p - try to use handles with NO-ERROR
key
output to winhandles. def var w as int. w = current-window:width no-error. message error-status:error error-status:num-messages error-status:get-message(1). w = active-window:width no-error. message error-status:error error-status:num-messages error-status:get-message(1). w = default-window:width no-error. message error-status:error error-status:num-messages error-status:get-message(1). message "end.".
CHUI. No errors. works
BATCH. error 3140 3 times and "end."
#23 Updated by Constantin Asofiei over 10 years ago
Vadim Gindin wrote:
Here are test cases:
1) same.p - testing equality of all three handles.
[...]Works for chui and batch modes.
OK, this proves there is only one window in CHUI mode. Please test in batch mode if the window is unknown or not.
2) width.p - try to assign an attribute of window system handles.
3) width-noerror.p - try to use handles withNO-ERROR
key
This findings are OK, but they can be implemented later. What I meant was to test cases like current-window = some-handle.
- what happens with the system handle?
#24 Updated by Vadim Gindin over 10 years ago
Constantin Asofiei wrote:
Vadim Gindin wrote:
Here are test cases:
1) same.p - testing equality of all three handles.
[...]Works for chui and batch modes.
OK, this proves there is only one window in CHUI mode. Please test in batch mode if the window is unknown or not.
window is unknown:message current-window active-window default-window
outputs ? ? ?
2) width.p - try to assign an attribute of window system handles.
3) width-noerror.p - try to use handles withNO-ERROR
keyThis findings are OK, but they can be implemented later. What I meant was to test cases like
current-window = some-handle.
- what happens with the system handle?
I added couple of simple tests and found that current-window behaves little different that other handles.
def var wh as widget-handle. output to 3333. wh = ?. message "aw before: " active-window. active-window = wh. message "aw after: " active-window. message "dw before: " default-window. default-window = wh. message "dw after: " default-window.
In the result default-window and active-window remains unchanged. But the analogue procedure for the current-window shows and error.
def var wh as widget-handle. output to 3333. message "cw before: " current-window." current-window = wh. message "cw after: " current-window.
Shows an error "Unable to evaluate field for assignment. (143)".
If we will assign some value to wh for example create button wh
Progress will show the other error:
"Unable to assign widget to CURRENT-WINDOW. (3130)"
In batch mode this behavior doesn't changing.
If we assign to wh the active-window for example - all will work.
Finally I can't assign other valid window handle because it is not possible to create window in CHUI mode.
#25 Updated by Vadim Gindin over 10 years ago
no-error in the case of current-window errors sets ERROR-STATUS:ERROR flag to true.
#26 Updated by Vadim Gindin over 10 years ago
- File vig_upd20131226a.zip added
Here is an update. Sorry that it is merged with 1782 changes. It's not final. In this task there is only setCurrentWindow
method implementations.
I found 2 problems:
1) current-window = h no-error
- no-error is losed after conversion in this statement.
2) current-window = ?
is converted to setCurrentWindow(new unkown())
. Should I add additional setCurrentWindow method for this case or I should modify converstion rules to wrap ?
to handle variable or widget variable?
#27 Updated by Constantin Asofiei over 10 years ago
- in the implementations for all
current/default/active-window
APIs inLogicalTerminal
, where it saysdo nothing
I would prefer to be like this:if (isChui()) { // in chui or batch mode, this is a no-op. return. }
Please double check if we need to explicitly check for batch mode. This way, it's obvious we will need to do something different in GUI mode. - please check and implement the
CURRENT-WINDOW
attribute for procedure widgets too. I suspect the behaviour will be the same.
Shows an error "Unable to evaluate field for assignment. (143)".
I think the correct error message is ** x.p: Unable to evaluate field for assignment. (143)
where x.p
is the program name. More, the Unable to assign widget to CURRENT-WINDOW. (3130)
has two spaces between the widget
and to
words. Please make sure the error messages are emitted the same way as 4GL. The various boolean parameters for ErrorManager
APIs are very helpful.
1)
current-window = h no-error
- no-error is losed after conversion in this statement.
Please fix it.
2)
current-window = ?
is converted tosetCurrentWindow(new unkown())
. Should I add additional setCurrentWindow method for this case or I should modify converstion rules to wrap?
to handle variable or widget variable?
The correct solution is to emit a new handle()
instead of new unknown()
. If the conversion changes look simple, go ahead and fix it (low priority).
#28 Updated by Vadim Gindin over 10 years ago
I'm trying to fix no-error loose during conversion of this statement:
current-window = h no-error.
I am viewing the grammar rule "assignment" (line 11992) in progress.g and It is a bit complex. Could you explain me what does the following statement mean:
- = #([ASSIGNMENT, "assignment"], (eq, l, e), rv, k); //line 12114
and where variables "eq", "l", "e", "rv" and "k" are defined?
This statement was parsed to this ast node:
<ast col="16" id="1120986464286" line="8" text="=" type="EXPRESSION"> <annotation datatype="java.lang.Boolean" key="wasassign" value="true"/> <annotation datatype="java.lang.Long" key="peerid" value="1159641169959"/> <ast col="1" id="1120986464289" line="8" text="current-window" type="SYS_HANDLE"> <annotation datatype="java.lang.Long" key="oldtype" value="507"/> <annotation datatype="java.lang.Long" key="tempidx" value="197"/> <annotation datatype="java.lang.Long" key="peerid" value="1159641169960"/> <ast col="0" id="1120986464292" line="0" text="expression" type="EXPRESSION"> <annotation datatype="java.lang.Long" key="peerid" value="1159641169961"/> <ast col="18" id="1120986464293" line="8" text="wh" type="VAR_HANDLE"> <annotation datatype="java.lang.Long" key="oldtype" value="2348"/> <annotation datatype="java.lang.Long" key="refid" value="1120986464259"/> <annotation datatype="java.lang.Long" key="peerid" value="1159641169962"/> </ast> </ast> </ast> </ast>
And this node does not contain "no-error". Probably the case is in the progress.g
#29 Updated by Greg Shah over 10 years ago
First, in regard to your specific problem, I doubt the issue is in the parser. The NO-ERROR should emit as:
ErrorManager.silentErrorEnable();
// converted statement or expression
ErrorManager.silentErrorDisable();
This brackets the converted code with code that controls the "silent error" flag which is our equivalent to NO-ERROR.
Please see rules/convert/language_statements.rules and search for all instances of prog.kw_no_error to see the code.
To see if the problem is in the parser, look in the <sourcefile>.p.ast and search for line="<linenum>" then examine the tree there. If the KW_NO_ERROR node exists in the tree then the problem is somewhere else.
Please show the node above the EXPRESSION node in the tree. Is that an ASSIGNMENT? Are you looking at the AST before or after the TRPL rules may have changed it?
About the progress.g grammar:
and where variables "eq", "l", "e", "rv" and "k" are defined?
Look above there to see the definitions. For example, on line 12068 there is this code:
(k:KW_NO_ERROR)?
The k: is a label that defines a local AST variable named k. In actions (the Java code in {} inserted inline with the rest of the grammar), this is named #k. But for regular grammar references, just k will work. The other variables are defined as labels for the other parts of the matched text. These can be single tokens or they can be the root node of a sub-tree.
Could you explain me what does the following statement mean:
- = #([ASSIGNMENT, "assignment"], (eq, l, e), rv, k); //line 12114
This is a manual tree building statement. We do this in rare cases where the parser's default tree that is built is not suitable for our needs. By specifying the rule name with a following ! (as in assignment! on line 11992), the parser is told to bypass default tree building. If we want a tree returned, we will have to build it ourselves.
The ## is a shorthand to reference the root AST node of the subtree that will be returned by the current parser rule.
The #([ASSIGNMENT, "assignment"], ) creates a new node with a token type ASSIGNMENT and the token text "assignment".
Anything that appears after the comma is a list of child nodes that will be inserted, if they exist, in the left to right order they are specified in the grammar.
The (eq, l, e) creates a new sub-tree that has eq as the root node and 2 children, child 0 as l and child 1 as e.
The entire subtree rooted at eq will be inserted as the first child of the ASSIGNMENT node. This is the left side of the assignment operator, or the lvalue.
The rv (which is the rvalue for the assignment) and k (the no-error) will be the child 1 and child 2 respectively, if they actually exist.
Any node listed in this tree building syntax that does not exist is just silently ignored. It will not cause a parsing error.
(k:KW_NO_ERROR)?
The k: is a label that defines a local AST variable named k. In actions (the Java code in {} inserted inline with the rest of the grammar), this is named #k. But for regular grammar references, just k will work. The other variables are defined as labels for the other parts of the matched text. These can be single tokens or they can be the root node of a sub-tree.
Could you explain me what does the following statement mean:
- = #([ASSIGNMENT, "assignment"], (eq, l, e), rv, k); //line 12114
This is a manual tree building statement. We do this in rare cases where the parser's default tree that is built is not suitable for our needs. By specifying the rule name with a following ! (as in assignment! on line 11992), the parser is told to bypass default tree building. If we want a tree returned, we will have to build it ourselves.
The ## is a shorthand to reference the root AST node of the subtree that will be returned by the current parser rule.
The #([ASSIGNMENT, "assignment"], ) creates a new node with a token type ASSIGNMENT and the token text "assignment".
Anything that appears after the comma is a list of child nodes that will be inserted, if they exist, in the left to right order they are specified in the grammar.
The (eq, l, e) creates a new sub-tree that has eq as the root node and 2 children, child 0 as l and child 1 as e.
The entire subtree rooted at eq will be inserted as the first child of the ASSIGNMENT node. This is the left side of the assignment operator, or the lvalue.
The rv (which is the rvalue for the assignment) and k (the no-error) will be the child 1 and child 2 respectively, if they actually exist.
Any node listed in this tree building syntax that does not exist is just silently ignored. It will not cause a parsing error.
#30 Updated by Vadim Gindin over 10 years ago
Thank you for the explanation. I wrote an ast node in my previous comment (#28).
#31 Updated by Constantin Asofiei over 10 years ago
Vadim, to identify if the problem is in the parser, then is best to check the AST generated by the FRONT phase, as this is the pristine AST generated by the parser (plus the fixups). If you do this, you will notice that a current-window = h no-error.
assignment looks like:
<ast col="0" id="12884901924" line="0" text="assignment" type="ASSIGNMENT"> <ast col="16" id="12884901925" line="4" text="=" type="ASSIGN"> <ast col="1" id="12884901928" line="4" text="current-window" type="SYS_HANDLE"> <annotation datatype="java.lang.Long" key="oldtype" value="507"/> <annotation datatype="java.lang.Long" key="tempidx" value="197"/> </ast> <ast col="0" id="12884901931" line="0" text="expression" type="EXPRESSION"> <ast col="18" id="12884901932" line="4" text="h" type="VAR_HANDLE"> <annotation datatype="java.lang.Long" key="oldtype" value="2348"/> <annotation datatype="java.lang.Long" key="refid" value="12884901891"/> </ast> </ast> </ast> <ast col="20" id="12884901935" line="4" text="no-error" type="KW_NO_ERROR"/> </ast>
and the
NO-ERROR
is emitted properly. The root cause I think is in the annotations/frame_scoping.rules
like 4085:<!-- re-write CURRENT/ACTIVE/DEFAULT-WINDOW assignment to an expression, as this is no longer a valid assignment node --> <rule>parent.type == prog.assign and type == prog.sys_handle and (#(int) getNoteLong("oldtype") == prog.kw_cur_win or #(int) getNoteLong("oldtype") == prog.kw_act_win or #(int) getNoteLong("oldtype") == prog.kw_def_win) <action>node = copy.getNextSibling()</action> <action>node.move(copy, 0)</action> <action>node = copy.parent</action> <action>node.type = prog.expression</action> <action>node = copy.parent.parent</action> <action>copy.parent.putAnnotation("wasAssign", true)</action> <action>copy.parent.move(node.parent, node.indexPos)</action> <action>node.remove()</action> </rule>
#32 Updated by Vadim Gindin over 10 years ago
Thank you Constantin!
I ran the conversion process with f2+cb
options and got my first result, but when I ran the conversion process only with f2
option I got the result - same as you have wrote. Why it is different? The docs saying that cb
means
unreachable code, annotations, frame generator, base structure, core conversion and brew
I can only suspect that the transformation from assignment
to expression
is going in the annotation processing step. Isn't it?
Another one question: how did you find frame_scoping.rules block? Did you know that "somewhere in this file this block exists" or did you just use text search to find this block by string "kw_cur_win" for example?
#33 Updated by Constantin Asofiei over 10 years ago
Vadim Gindin wrote:
I can only suspect that the transformation from
assignment
toexpression
is going in the annotation processing step. Isn't it?
Correct, but add some printfln logging to the frame_scoping.rules
code I mention to confirm.
There were two phases:Another one question: how did you find frame_scoping.rules block? Did you know that "somewhere in this file this block exists" or did you just use text search to find this block by string "kw_cur_win" for example?
- confirm that progress.g emits the no-error clause properly (by checking the AST from the F2 phase).
- search the P2J project for
prog.kw_cur_win
and see where it is used. Fortunately, there were not many places using it, so it was easy to find the problematic rule.
#34 Updated by Vadim Gindin over 10 years ago
I corrected the rules block you specified to force it transfer KW_NO_ERROR too and got this ast node (after annotation processing):
<ast col="16" id="1120986464286" line="8" text="=" type="EXPRESSION"> <annotation datatype="java.lang.Boolean" key="wasassign" value="true"/> <annotation datatype="java.lang.Long" key="peerid" value="1159641169959"/> <ast col="1" id="1120986464289" line="8" text="current-window" type="SYS_HANDLE"> <annotation datatype="java.lang.Long" key="oldtype" value="507"/> <annotation datatype="java.lang.Long" key="tempidx" value="197"/> <annotation datatype="java.lang.Long" key="peerid" value="1159641169960"/> <ast col="0" id="1120986464292" line="0" text="expression" type="EXPRESSION"> <annotation datatype="java.lang.Long" key="peerid" value="1159641169961"/> <ast col="18" id="1120986464293" line="8" text="wh" type="VAR_HANDLE"> <annotation datatype="java.lang.Long" key="oldtype" value="2348"/> <annotation datatype="java.lang.Long" key="refid" value="1120986464259"/> <annotation datatype="java.lang.Long" key="peerid" value="1159641169962"/> </ast> </ast> </ast> <ast col="21" id="1120986464296" line="8" text="no-error" type="KW_NO_ERROR"/> </ast>
It don't produce correct correspondent Java code for KW_NO_ERROR and looks not good intuitively. KW_NO_ERROR
probably should not be included inside EXPRESSION
. Am I right? The question is how this ast node should be to be correct and to produce correspondent Java code? May be it should be wrapped inside a statement node in this way:
<ast type="statement"> <ast type="expression"> ... </ast> <ast col="21" id="1120986464296" line="8" text="no-error" type="KW_NO_ERROR"/> </ast>
Thank you.
#35 Updated by Constantin Asofiei over 10 years ago
KW_NO_ERROR probably should not be included inside EXPRESSION. Am I right? The question is how this ast node should be to be correct and to produce correspondent Java code?
I think you are on the right path, but the solution may be much simpler. Take a look at this rule in convert/language_statement.rules:95
:
<rule> (type == prog.statement and descendant(prog.kw_no_error, 4)) or (type == prog.assignment and descendant(prog.kw_no_error, 1)) <action> createJavaAst(java.static_method_call, "ErrorManager.silentErrorEnable", closestPeerId) </action> </rule>
This emits the
silentErrorEnable
call if the kw_no_error
is a child of an assignment
node or a 4-th level descendent of a statement
node. I think is enough to add a check here for a type == prog.expression and descendant(prog.kw_no_error, 1)
to properly emit it (same applies for the silentErrorDisable
call on line 600).#36 Updated by Vadim Gindin over 10 years ago
Yes It will be simpler. But It looks like the last change you propose is a solution of some private task. I added first change in the frame_scoping.rules to emit KW_NO_ERROR in a some place in the target node. I'm not sure that this place is correct. If I can think that it is correct than I can go further and add the next change that emits Java calls for this KW_NO_ERROR place. Are you sure that it is good solution?
#37 Updated by Constantin Asofiei over 10 years ago
Vadim Gindin wrote:
I added first change in the frame_scoping.rules to emit KW_NO_ERROR in a some place in the target node. I'm not sure that this place is correct.
After we rewrite this ASSIGNMENT to an expression, as you noticed, the KW_NO_ERROR was lost. Adding it as a child for the EXPRESSION node is OK, but for this to be emitted you need to explicitly treat this case too, in convert/language_statement.rules
. I don't see a reason for complicating the AST (so that it will have a parent STATEMENT and the KW_NO_ERROR be emitted as a child for some inner node), so is easier to treat this custom case explicitly (as I mentioned in previous note). There are situations when we need to leverage between "do we complicate things further so that an existing rule should not be touched" and "do we change a rule and treat a custom case explicitly, as it proves much easier and straightforward to fix"; and I think this is a case where it's a lot easier to treat it explicitly, than complicating the AST.
#38 Updated by Vadim Gindin over 10 years ago
I understand, thank you. Now it produces this Java code:
ErrorManager.silentErrorEnable()setCurrentWindow(wh)ErrorManager.silentErrorDisable();
It seems it because KW_NO_ERROR is inside expression and unfortunately I forced to wrap EXPRESSION inside STATEMENT node. Isn't it? If so how to do it?
#39 Updated by Constantin Asofiei over 10 years ago
It seems it because KW_NO_ERROR is inside expression and unfortunately I forced to wrap EXPRESSION inside STATEMENT node. Isn't it? If so how to do it?
Hmm... yes, I think we need to wrap it in a statement node. The location for this fix is back in frame_scoping.rules. The rules at line 4092 look like this:
<rule>parent.type == prog.assign and type == prog.sys_handle and (#(int) getNoteLong("oldtype") == prog.kw_cur_win or #(int) getNoteLong("oldtype") == prog.kw_act_win or #(int) getNoteLong("oldtype") == prog.kw_def_win) <action>node = copy.getNextSibling()</action> <action>node.move(copy, 0)</action> <action>node = copy.parent</action> <action>node.type = prog.expression</action> <action>node = copy.parent.parent</action> <action>copy.parent.putAnnotation("wasAssign", true)</action> <action>copy.parent.move(node.parent, node.indexPos)</action> <action>node.remove()</action> </rule>
The last two actions are moving the now-renamed
ASSIGN
node in the position of the ASSIGNMENT
node and then remove the ASSIGNMENT
node. I think will be enough to replace the last two rules with something like: node.type = prog.statement
: node
already points to ASSIGNMENT
and this will rename it into a STATEMENT
node.
If this works, post here the new AST.
#40 Updated by Vadim Gindin over 10 years ago
Yes it works and moreover there are no more need to move KW_NO_ERROR manually. Here is the new AST node:
<ast col="0" id="1120986464285" line="0" text="statement" type="STATEMENT"> <ast col="16" id="1120986464286" line="8" text="=" type="EXPRESSION"> <annotation datatype="java.lang.Boolean" key="wasassign" value="true"/> <annotation datatype="java.lang.Long" key="peerid" value="1159641169960"/> <ast col="1" id="1120986464289" line="8" text="current-window" type="SYS_HANDLE"> <annotation datatype="java.lang.Long" key="oldtype" value="507"/> <annotation datatype="java.lang.Long" key="tempidx" value="197"/> <annotation datatype="java.lang.Long" key="peerid" value="1159641169961"/> <ast col="0" id="1120986464292" line="0" text="expression" type="EXPRESSION"> <annotation datatype="java.lang.Long" key="peerid" value="1159641169962"/> <ast col="18" id="1120986464293" line="8" text="wh" type="VAR_HANDLE"> <annotation datatype="java.lang.Long" key="oldtype" value="2348"/> <annotation datatype="java.lang.Long" key="refid" value="1120986464259"/> <annotation datatype="java.lang.Long" key="peerid" value="1159641169963"/> </ast> </ast> </ast> </ast> <ast col="21" id="1120986464296" line="8" text="no-error" type="KW_NO_ERROR"/> </ast>
Produced Java code is correct.
#41 Updated by Constantin Asofiei over 10 years ago
Great. Please finish the changes I mentioned in note 27 (expect the current-window = ?
case) and post the update here.
#42 Updated by Vadim Gindin over 10 years ago
Constantin, could you help me. I didn't find in ErrorManager
a possibility to include in the error message the name of procedure. By the way Progress shows the full name of procedure file (including the path). Two questions:
1) How to find the procedure name (full name)?
2) Should I include it in the error message manually, or there is exists the way to do it in ErrorManager?
#43 Updated by Constantin Asofiei over 10 years ago
Vadim Gindin wrote:
Constantin, could you help me. I didn't find in
ErrorManager
a possibility to include in the error message the name of procedure. By the way Progress shows the full name of procedure file (including the path). Two questions:
1) How to find the procedure name (full name)?
4GL has the PROGRAM-NAME
function, with PROGRAM-NAME(1)
returning the full name of the currently executing program. This converts to EnvironmentOps.getSourceName
- so you can use this to find the program name.
2) Should I include it in the error message manually, or there is exists the way to do it in ErrorManager?
The error must be included in the message manually.
#44 Updated by Vadim Gindin over 10 years ago
Thank you, but Progress shows full path to procedure file starting from root folder "/". EnvironmentOps.getSourceName(1)
returns only winhandles/assign_current_window.p
#45 Updated by Constantin Asofiei over 10 years ago
Vadim Gindin wrote:
Thank you, but Progress shows full path to procedure file starting from root folder "/".
EnvironmentOps.getSourceName(1)
returns onlywinhandles/assign_current_window.p
Have you tested the program from command line, in 4GL? Also, to which error message do you refer, exactly? Because this case:
def var wh as handle. current-window = wh.
shows this error:
** a.p: Unable to evaluate field for assignment. (143)
#46 Updated by Vadim Gindin over 10 years ago
Yes, I checked it again. My exact message is:** /home/vig/winhandles/simple2.p: Unable to evaluate field for assignment. (143)
It seems it depends on exact command in Progress. I ran my script with full name /home/vig/winhandles/simple2.p
and you probably added some directory to PROPATH
variable and specified a.p
from this directory. Isn't it?
#47 Updated by Constantin Asofiei over 10 years ago
I ran the program using pro -p a.p
; if you run it using pro -p somefolder/a.p
, then you will have somefolder/a.p
reported. I think the full-path problem is a non-issue for us at this time, just report the program name as reported by the EnvironmentOps.getSourceName(1)
; this will do for now, make sure to add a comment that in some cases the full path is provided.
#48 Updated by Vadim Gindin over 10 years ago
- File vig_upd20140107a.zip added
Here is an update with last changes.
#49 Updated by Vadim Gindin over 10 years ago
- File vig_upd20140107a.zip added
Sorry I forgot to add the comment about the path. Here is updated archive.
#50 Updated by Constantin Asofiei over 10 years ago
- please try not to post updates merged with other tasks; if you find it difficult to work more than one task in the same project, you can always use a second project to work the second task. This update doesn't even compile with rev 10427, and it makes it difficult to double-check any P2J runtime.
- I don't see any changes for the
ExternalProgramWrapper.currentWindow/setCurrentWindow
APIs (i.e.this-procedure:current-window
assignment and access) - have you tested this? - for the
Unable to assign widget to CURRENT-WINDOW. (3130)
message: you need to set theprefix
flag for theErrorManager.recordOrThrowError
to false, to not emit the**
prefix... as I recall, this message is not prefixed.
#51 Updated by Vadim Gindin over 10 years ago
Sorry, I forgot about this-procedure:current-window. Here is the test:
def var wh as widget-handle. if this-procedure:current-window <> ? then message "this-procedure:current-window is unexpected - " this-procedure:current-window. /* outputs following error: **Unable to assign UNKNOWN value to attribute CURRENT-WINDOW of PROCEDURE WIDGET. (4083) */ /*this-procedure:current-window = wh.*/ /* outputs following error: **Unable to set attribute CURRENT-WINDOW for PROCEDURE widget. (4708) */ /*create button wh. this-procedure:current-window = wh.*/ this-procedure:current-window = active-window. if this-procedure:current-window <> active-window then message "this-procedure:current-window was not changed ".
Interesting results.
1. Default value of this-procedure:current-window
is ? as documented.
2. When assigned value is ? or invalid Progress shows an error:
**Unable to assign UNKNOWN value to attribute CURRENT-WINDOW of PROCEDURE WIDGET. (4083)
3. When assigned value is valid handle but handle of widget other than window Progress shows an error:
**Unable to set attribute CURRENT-WINDOW for PROCEDURE widget. (4708)
4. If assigned value is correct window handle - this-procedure:current-window
attribute takes the value of it. It differs from current-window handle.
#52 Updated by Vadim Gindin over 10 years ago
- File vig_upd20140108a.zip added
Here is an update with last changes. I merged it with current revision and checked if it is ok again. If I correctly understand here should be no compilation errors. I also removed from this update other changes interfering with it. Hope it will be ok.
#53 Updated by Constantin Asofiei over 10 years ago
- In files where the history entry is from 2014, update the copyright years in the file's header.
- LogicalTerminal:8629 - you have some tab characters. In Eclipse, you can enable visible white-spaces, as sometimes Eclipse inserts tabs instead of spaces.
- LT.setProcCurrentWindow doesn't need to validate the phandle (as it is always called on a valid phandle), but please leave behind a TODO:, as in GUI mode we may need some implementation. Same for LT.currentWindow(handle).
- LT.currentWindow: phandle.getResource() returns the ExternalProgramWrapper, not the referent. The referent is retrieved via phandle.get(). Same in LT.setProcCurrentWindow(). I'm curious why you didn't got a NPE during testing...
- ProcedureManager.getCurrentWindow - always return a copy of the handle, never expose the saved reference to outside code.
- ProcedureManager.setCurrentWindow: the code should have been
locateProcedure(referent).currentWindow.assign(winHandle);
notlocateProcedure(referent).currentWindow = winHandle;
, which saved the outside reference. - ProcedureManager - you've added an extra line after ContextContainer inner class...
- ExternalProgramWrapper.currentWindow - change PersistentProcedure.currentWindow signature to return a handle instead of WindowWidget. This will be consistent with how legacy resources are exposed to the outside world in P2J (always via handles).
#54 Updated by Vadim Gindin over 10 years ago
- File vig_upd20140109b.zip added
Next update with last changes.
#55 Updated by Constantin Asofiei over 10 years ago
Go ahead and get it tested. Before committing, fix the tab characters in LT.setProcCurrentWindow:8627.
#56 Updated by Constantin Asofiei over 10 years ago
Vadim, on note 24 you mentioned that "window is unknown:" in batch mode. You forgot to implement this case in LT.currentWindow()
and other APIs.
#57 Updated by Vadim Gindin over 10 years ago
You're right.. Now I'm trying to implement it (batch-mode support) and I'm getting following problem. CURRENT-WINDOW in batch-mode has unknown value and my test does following:
def var w as int. w = current-window:width. current-window:width = 101.
It will be converted to following Java code:
w.assign(handle.unwrapSizeable(currentWindow()).getWidthChars()); handle.unwrapSizeable(currentWindow()).setWidthChars(new integer(101));
currentWindow()
in my implementation will return new handle()
. For this value handle.unwrapSizeable(..)
method will consider it as invalid handle and will return proxy object that outputs 2 messages:
Invalid handle. Not initialized or points to a deleted object (3135) Cannot access the WIDTH-CHARS attribute because the widget does not exist (3140)
Problems:
1) Java outputs 2 messages independently of current usage of invalid handle. But Progress in my testcase returns only the second message. Should I change this common behavior of hande
or I should make a custom behavior for this case. Is yes - how to do it?
2) Attribute name is WIDTH-CHARS, but Progress outputs WIDTH. I suspect it is the same because there are no setWidth() method in Sizable
. As I can understand the attribute name is got from LegacyAttribute
annotation of method getWidthChars()
and so we are getting WIDTH-CHARS. How to get just WIDTH attribute name?
#58 Updated by Constantin Asofiei over 10 years ago
I understand the problem. Unfortunately, I think this will need conversion changes, to let the handle.unwrap call know that this is a window system handle (or some other modality to inform the runtime). For now, limit on checking that the window system handles and procedure:current-window are reported correctly in batch mode (using valid-handle and unknown equality tests).
Greg: we can add a low-priority task for this problem, as this looks like an exotic case.
2) Attribute name is WIDTH-CHARS, but Progress outputs WIDTH. I suspect it is the same because there are no setWidth() method in Sizable. As I can understand the attribute name is got from LegacyAttribute annotation of method getWidthChars() and so we are getting WIDTH-CHARS. How to get just WIDTH attribute name?
This suggests that the LegacyAttribute
annotation should be enhanced to record the name used in error message, beside the real attribute name. Document this problem in #2194.
#59 Updated by Greg Shah over 10 years ago
Greg: we can add a low-priority task for this problem, as this looks like an exotic case.
OK.
2) Attribute name is WIDTH-CHARS, but Progress outputs WIDTH. I suspect it is the same because there are no setWidth() method in Sizable. As I can understand the attribute name is got from LegacyAttribute annotation of method getWidthChars() and so we are getting WIDTH-CHARS. How to get just WIDTH attribute name?
This suggests that the LegacyAttribute annotation should be enhanced to record the name used in error message, beside the real attribute name. Document this problem in #2194.
Actually, I think this is just a consequence of some ridiculous keyword abbreviation + ambiguity in the 4GL.
The 4GL docs report both as valid keywords: WIDTH (non-reserved keyword with no abbreviations) and WIDTH-CHARS (non-reserved keyword, with minimum abbreviation as WIDTH). The obvious problem here is that by this definition, there is no way to tell whether WIDTH is WIDTH or WIDTH-CHARS. In fact, we have even coded it as a minimum abbreviation of WIDTH- in order to differentiate the two cases.
Annoyingly, WIDTH and WIDTH-CHARS are both used in actual syntax diagrams throughout the docs, without ever suggesting that these are synonyms. But the 4GL itself definitely treats these two as synonyms in many (if not all) places. WIDTH-CHARS is only specified as an attribute name in the docs. But WIDTH can be used in this attribute case and it is treated as WIDTH-CHARS (as synonyms). We also know that WIDTH-CHARS can be used instead of WIDTH in a frame phrase (this is undocumented). What we have not yet tested is whether WIDTH-CHARS can be used anywhere else that WIDTH is specified in the docs. It is very likely that WIDTH-CHARS is really just a longer form of WIDTH and the two are completely interchangeable. But we won't know that without further testing.
Is there ever any way the 4GL can report WIDTH-CHARS as the attribute name? If not, then let's just code it as WIDTH and we are done.
#60 Updated by Constantin Asofiei over 10 years ago
Greg Shah wrote:
Is there ever any way the 4GL can report WIDTH-CHARS as the attribute name? If not, then let's just code it as WIDTH and we are done.
What I checked is the oposite: if the WIDTH attribute can be queried for a window handle:
def var h as handle. h = current-window. message h:width h:width-chars. message can-query(h, "width") can-query(h, "width-chars").
this outputs:
80 80 yes yes
The attribute name, when the
3140
error message is displayed, P2J computes it from the Legacy annotations. And there we can specify only one name... This complicates things as can-query
needs to handle both names. We can choose to set the name displayed in the error message at LegacyAttribute annotation and change the CAN-QUERY/CAN-SET implementation to handle known aliases (not abbreviations) for a certain attribute.#61 Updated by Greg Shah over 10 years ago
We can choose to set the name displayed in the error message at LegacyAttribute annotation and change the CAN-QUERY/CAN-SET implementation to handle known aliases (not abbreviations) for a certain attribute.
This sounds reasonable.
#62 Updated by Vadim Gindin over 10 years ago
- File vig_upd20140115a.zip added
Here is the update with batch-mode support added.
#63 Updated by Constantin Asofiei over 10 years ago
LogicalTerminal.setCurrentWindow(handle h)
- have you tested the assignment using the NO-ERROR clause too? Because I think you are missingreturn
statements in the first two if blocks:if (h == null || !h._isValid() || h.isUnknown()) { // proc can be full name with path of procedure file or relative. String proc = EnvironmentOps.getSourceName(1).getValue(); String ms = proc + ": Unable to evaluate field for assignment"; ErrorManager.recordOrThrowError(143, ms); return; } if (!(h.getResource() instanceof WindowWidget)) { String ms = "Unable to assign widget to CURRENT-WINDOW"; ErrorManager.recordOrThrowError(3130, ms, false); return; }
Same forLogicalTerminal.setProcCurrentWindow
.ExternalProgramWrapper.currentWindow
andExternalProgramWrapper.setCurrentWindow
- is the behaviour for batch mode implemented properly?
#64 Updated by Vadim Gindin over 10 years ago
- File vig_upd20140116a.zip added
Constantin Asofiei wrote:
LogicalTerminal.setCurrentWindow(handle h)
- have you tested the assignment using the NO-ERROR clause too? Because I think you are missingreturn
statements in the first two if blocks:
[...]
Same forLogicalTerminal.setProcCurrentWindow
.
Yes I did test it but my tests did not allow me to find this issue. It probably need to test it on GUI for it. But you are right of course ). I added return
to these methods and to ExternalProgramWrapper methods too.
ExternalProgramWrapper.currentWindow
andExternalProgramWrapper.setCurrentWindow
- is the behaviour for batch mode implemented properly?
Not exactly. I retested it again and found out that the Progress does not set error-status:error
flag when no-error
is used. This problem does not interferes with batch-mode. This mode supported properly. The only difference in behavior is that the values are ?.
Please take a look at new update (merged with the last revision).
#65 Updated by Constantin Asofiei over 10 years ago
Vadim Gindin wrote:
Yes I did test it but my tests did not allow me to find this issue. It probably need to test it on GUI for it. But you are right of course ). I added
return
to these methods and to ExternalProgramWrapper methods too.
GUI has nothing to do with testing this. In batch or not, the test needs to look like:
def var h as handle. output to foo.txt. do on error undo,leave: current-window = h. message "no error raised". end. current-window = h no-error. message error-status:error error-status:num-messages error-status:get-message(1).
This will show you if error condition is raised and the exact error message. Expand this test for the other window handles to confirm their behaviour, as I don't see tests for this in uast/winhandles.
ExternalProgramWrapper.currentWindow
andExternalProgramWrapper.setCurrentWindow
- is the behaviour for batch mode implemented properly?Not exactly. I retested it again and found out that the Progress does not set
error-status:error
flag whenno-error
is used. This problem does not interferes with batch-mode. This mode supported properly. The only difference in behavior is that the values are ?.
A test like this:
def var h as handle. output to bar.txt. do on error undo,leave: this-procedure:current-window = h. message "no error raised". end. this-procedure:current-window = h. message error-status:error error-status:num-messages error-status:get-message(1). output close.
Shows that your assumption is correct - no error is raised. But an error message does get displayed:
**Unable to assign UNKNOWN value to attribute CURRENT-WINDOW on PROCEDURE widget. (4083) no error raised **Unable to assign UNKNOWN value to attribute CURRENT-WINDOW on PROCEDURE widget. (4083) no 0
Use the
ErrorManager.displayErrorRedirected
to display the message on the terminal (for interactive mode) or to file (for redirected mode).#66 Updated by Vadim Gindin over 10 years ago
Constantin,
I've committed tests I have for this task. I already have similar tests. Could you comment, why do you insist on the block do on error ... end
?
About the method ErrorManager.displayErrorRedirected
. Is it preferable than ErrorManager.recordOrShowError(4078, msg, false, false, false);
?
#67 Updated by Constantin Asofiei over 10 years ago
Sorry, my bad. The test was missing a no-error clause for the last this-procedure:current-window = h.
assignment; so is OK to use recordOrShowError
, as you noted. The idea for displayErrorRedirected
was to not add the message to the ErrorManager's error list, but my assumption was incorrect.
Why do I insist on DO... ON ERROR
? This is the best way to determine if an ERROR
condition is raised or not. We can't rely only on ERROR-STATUS:ERROR
flag (checked after using NO-ERROR
). I've learned to double check everything in 4GL, as sometimes you can get behaviour not matching the documentation or even common sense.
#68 Updated by Constantin Asofiei over 10 years ago
As Vadim N. noted on #1655 note 63: the problem we have with WIDTH
and WIDTH-CHARS
can be found with HEIGHT
and HEIGHT-CHARS
too.
#69 Updated by Vadim Gindin over 10 years ago
So I tested it again. It works. I'm going to run regression testing.
#70 Updated by Constantin Asofiei over 10 years ago
OK, place the final update here for review, before releasing it.
#71 Updated by Vadim Gindin over 10 years ago
There is no changes since last update vig_upd20140116a.zip
#72 Updated by Constantin Asofiei over 10 years ago
Great, you can release 0116a.zip after it passes testing.
#73 Updated by Vadim Gindin over 10 years ago
vig_upd20140116a.zip passed regression testing. Committed as rev #10443.
#74 Updated by Greg Shah over 10 years ago
- Status changed from New to Closed
ChUI and batch support is complete, which is sufficient for M7. GUI support will be implemented in #2229.
#75 Updated by Greg Shah over 7 years ago
- Target version changed from Milestone 7 to Runtime Support for Server Features