Project

General

Profile

Frames

Introduction

The frame is a key concept necessary to understand interactive input-output and redirected output in Progress 4GL since in the vast majority of cases such I/O is performed with implicitly or explicitly defined frames. Explicitly defined frames have names, so sometimes they are referred as named frames. Implicitly defined frames have no name, so they are called default or unnamed frames.

The default frame is provided by Progress 4GL for statements which don't have explicitly mentioned frame reference nor is there an active frame defined in the outer block scope. The active frame for the block scope can be defined using dedicated clause in the block operator (see Defining the Active Frame For The Block section below for more details). There is no difference between default and explicitly defined frames from the point of view of the frame behavior, scoping or hiding/pausing rules. Converted code also makes no difference between them.

Beside classification of the frames by presence of the name mentioned above, there are other classifications: shared/non-shared and DOWN/1-DOWN. Frame may belong to the few classes, but unnamed frame can't be shared because there is no way to declare such a frame in Progress 4GL (see Frame Declaration section for more details).

Each frame consists of the frame itself (with or without border) and widgets. Before starting explanation of frame conversion it is necessary to provide some details about widgets. Next introduction does not provide widget conversion details, its purpose is to give general understanding of what is the widget. Full widget conversion details are provided in appropriate section.

Defining the Active Frame For The Block

Each block statement (DO, REPEAT, FOR...WITH) can have a frame phrase. The frame name mentioned in this frame phrase becomes a default (active) frame for all statements which implicitly reference the frame inside the block until block ends. A more nested block may define another (different) active frame for statements inside that block. The active frame is restored when the nested block ends. Simplified syntax for the block statements is provided below:

block-operator … [frame-phrase]

where block-operator is one of the DO, REPEAT, FOR...WITH and relevant block-specific clauses (see the Blocks chapter of Part 4). Detailed syntax of the frame-phrase is described below in the Frame Phrase section below.

This simple example illustrates use of an active frame definition provided below:

def var ch1 as char init "Some text".

do with frame f0:
    display ch1.
end.

The display statement in this example is an implicit use of frame f0.

Widgets

Widgets are elements of frame which displays information from some variable, constant or expression. There are four types of widgets: regular widgets, layout formatting pseudo-widgets, form headers and special widgets.

Regular Widgets

Regular widgets represent simple and array variables, table fields, string literals and expressions. Widgets created for variables and table fields can be edited by the user (except ones explicitly marked as TEXT, see Widget Conversion section for more details). Other widgets are read-only entities. Regular widgets may have different appearance and behavior. For example, regular widget created for the character variable may appear in the frame as simple fill-in field, as set of radio buttons, as selection list etc. More details on widget appearance are provided in Collecting Widget Properties section.

Layout Formatting Pseudo-widgets

There are two such widgets: SKIP and SPACE. The purpose is to control frame layout formatting and otherwise they are not visible to user.

Form Headers

These widgets represent regular widgets referred in the HEADER clause of DEFINE FRAME or FORM statement. Unlike regular widgets they can't be edited and they are handled differently in frame layout.

BUTTON And BROWSE Special Widgets

These widgets represent special UI elements, which are not associated with particular variable or literal.

Button widgets are used to interact with the user and control execution flow. Buttons are defined using DEFINE BUTTON statement.

Browse is a complex widget which provides table representation of data contained in data base or variables. It also provides convenient way to navigate through data and, optionally, allow user to edit it. Strictly speaking the browse is a complex widget which contains one or more browse column widgets. These details along with the conversion of browse widget are explained further in appropriate section. Browse widget is declared using DEFINE BROWSE statement.

Frame And Widget Naming In Converted Code

All widgets are named after their Progress 4GL counterparts using the name conversion approach described in detail in the Naming section of the Other Customization chapter. In application to the widgets and frames this name conversion approach is following:

Widgets created for fields and regular variables get “camel”-cased names with the first letter always converted to lower case.

Widgets created for extent variables get names similar to regular variables with additional suffix ArrayN, where N is the index of the variable in the array.

Note that widgets for complex expressions, literals and pseudo-widgets get generated names (see more details in FORM Statement Syntax section). Generated names have form exprZZZ, where ZZZ is ordinal number of the generated name in the compilation unit.

The following example illustrates name conversion for various types of widgets (widgets15.p):

def temp-table test no-undo field tot-hrs as int field proj-mgr  as int.
def var char-var as char.
def var Int_Var as int.
def var extent-var as char extent 2.

create test.
update "String literal" tot-hrs proj-mgr char-var Int_Var extent-var with frame frame1.

Static initialization class for the frame frame1 looks like this:

...
public static class Widgets15Frame1Def
extends WidgetList
{
   ControlTextWidget expr1 = new ControlTextWidget(); // generated name for string literal

   FillInWidget totHrs = new FillInWidget();  // field tot-hrs converted to totHrs

   FillInWidget projMgr = new FillInWidget(); // field proj-mgr converted to projMgr

   FillInWidget charVar = new FillInWidget(); // char-var converted to charVar

   FillInWidget intVar = new FillInWidget();  // Int_Var converted to intVar

   FillInWidget extentVarArray0 = new FillInWidget(); // extent-var[0] converted to extentVarArray0

   FillInWidget extentVarArray1 = new FillInWidget(); // extent-var[1] converted to extentVarArray1

   public void setup(CommonFrame frame)
   {
      frame.setDown(1);
      expr1.setDataType("character");
      expr1.setFormat("x(14)");
      totHrs.setDataType("integer");
      projMgr.setDataType("integer");
      charVar.setDataType("character");
      intVar.setDataType("integer");
      extentVarArray0.setIndex(1);
      extentVarArray0.setDataType("character");
      extentVarArray1.setIndex(2);
      extentVarArray1.setDataType("character");
      intVar.setLabel("Int_Var");
      charVar.setLabel("char-var");
      charVar.setFormat("x(8)");
      totHrs.setLabel("tot-hrs");
      projMgr.setLabel("proj-mgr");
      extentVarArray0.setLabel("extent-var[1]");
      extentVarArray1.setLabel("extent-var[2]");
   }

   {
      addWidget("expr1", "", expr1);
      addWidget("totHrs", "tot-hrs", totHrs);
      addWidget("projMgr", "proj-mgr", projMgr);
      addWidget("charVar", "char-var", charVar);
      addWidget("intVar", "Int_Var", intVar);
      addWidget("extentVarArray0", "extent-var", extentVarArray0);
      addWidget("extentVarArray1", "extent-var", extentVarArray1);
   }
}
...

Interfaces created for the frames get names which are a concatenation of the compilation unit name, optional procedure name and original frame name (except default frame, see below), where each part is converted according to name conversion rules described in Naming section of the Other Customization chapter. For example, references to a named frame my-frame in external procedure named worker.p will generally have a Java interface named WorkerMyFrame.java.

A procedure name is included in cases when frame is scoped to the inner procedure. This is necessary to distinguish frames with same name scoped to different scopes (refer to Frame Scope And Life Time section for more details about these cases). If my-frame was scoped to an internal procedure named util inside worker.p, then the resulting interface name would be WorkerUtilMyFrame.java.

The default (nameless) frame gets a generated interface name of the compilation unit suffixed with FrameZZZ, where ZZZ is the ordinal of the nameless frame in the compilation unit. So the first default frame of worker.p would have its interface named WorkerFrame0.java.

A compilation unit may contain more than one default frame due to frame scoping (different default frames will be scoped to different inner procedures, refer to Frame Scope And Life Time section for more details).

Java sources for the converted frame files are placed in package under ui sub-package followed by relative package name of original compilation unit. For example, if base package is set to com.company.app, compilation unit is account/file.p and it defines the frame account-form scoped somewhere inside main procedure, then frames class will be placed in com/company/app/ui/account/FileAccountForm.java.

The Java code of the business logic will create instances of the frame and store references in local data members. These frame instance variables get names converted just like the names of regular variables. When there is a named frame in the 4GL, the variable name will be a camel-cased version with a lowercase first letter. For a frame named my-frame in worker.p, the variable will be named myFrame. As with interface names, frames scoped to specific internal procedures will have prefixes for the associated procedure name. A frame named my-frame scoped to internal procedure util in procedure worker.p will have a name of utilMyFrame. Default (nameless) frames will have instance variables named frameZZZ where ZZZ is the ordinal of the nameless frame in the compilation unit. Since these are data members of the business logic class, none of the variable names need to be prefaced with the compilation unit. So in the examples above, the Java business logic class that corresponds to external procedure worker.p is the containing class so no compilation unit component is needed in the variable names.

The following example illustrates conversion of the named frame name, named frame scoped to internal procedure and a default frame (widgets16.p):

display "text" with frame regular-named-frame. /* named frame */

procedure proc1:
    display "text" with frame proc-scoped-frame. /* named frame scoped to internal procedure */
end.

display "text". /* default frame */

The following portion of the converted business logic class illustrates the conversion of frame interface names and the local variables that reference the frames at runtime:

...
public class Widgets16
{
   Widgets16RegularNamedFrame regularNamedFrameFrame = GenericFrame.createFrame(Widgets16RegularNamedFrame.class, "regular-named-frame");

   Widgets16Frame0 frame0 = GenericFrame.createFrame(Widgets16Frame0.class, "");

   Widgets16Proc1ProcScopedFrame proc1ProcScopedFrameFrame = GenericFrame.createFrame(Widgets16Proc1ProcScopedFrame.class, "proc1-proc-scoped-frame");
...

Frame Conversion

Conversion of frames is performed in two stages executed among other conversion steps. In the first stage, information about frames, their scopes and widgets is collected and then saved as annotations in the AST. In the second stage, frame definition classes are generated from the collected information.

Each frame definition class is a Java interface which extends the CommonFrame interface and represents exactly one frame in the original 4GL compilation unit. The CommonFrame interface provides all the basic 4GL equivalent services such as methods to invoke to VIEW or DISPLAY a frame.

In addition to this common interface, there is a frame-specific interface which provides getters/setters/accessors for each widget. This allows the widget data to be read and written by the business logic. It also allows the business logic to obtain each widget instance by a unique name. Using that instance, the widget configuration and state (outside of the data being contained) can be read or written.

Using this combination of a common and custom interface for frames reduces the amount of boilerplate code that must exist. In addition, a big advantage is that conversion to an interface enables the runtime to completely control the behavior of all frame methods and change it if necessary without needing to reconvert and/or recompile frame classes. At runtime, frame interfaces are backed by a dynamically created Java proxy class which implements all necessary functionality such that the instance is a proper implementation of the interface. The key point here is that the interface methods are not explicitly implemented in the converted code. Instead, the runtime dynamically implements the interface.

The code in the runtime which provides this dynamic proxy feature is implemented in com.goldencode.proxy.ProxyFactory. It is similar in concept to the java.lang.reflect.Proxy (J2SE Dynamic Proxy facility). FWD contains its own implementation which provides some features that are not available in the J2SE version. The idea is that a list of interfaces is inspected at runtime and a class is created on the fly when the instance of a frame is created. That class implements each one of the list of interfaces that are provided. But that implementation is mapped to an “invocation handler”. The invocation handler is what is dispatched to do the actual work of each of the interface methods, when any of those methods are called on that proxy object. The runtime implements its own invocation handler which provides a common implementation of all the interface methods. This is possible because the interfaces are highly regular (see below for details).

Besides the common frame functionality provided by the runtime, each frame contains number of widgets. The application needs some way to access them. This is solved during conversion by declaring in each custom interface a number of methods which can be separated into three large groups - getter methods (or simply getters), setter methods (setters) and widget access methods (accessors). Methods from the first group (getters) are used to get the widget data value (for example, entered by the user during an editing operation). Methods from second group (setters) are used to assign some data value to the widget (for example, to show the value of some expression to the user). Methods from third group are used when access to widget itself (as opposed to the data in the widget) is required. Usually this is necessary when some widget property should be obtained or changed (more information about widget properties provided in Widget Properties Summary section below).

In addition to the method declarations (getters/setters/accessors) each interface declares a nested (inner) static class with frame-specific frame and widget initialization code. This class is used to perform frame-specific initialization when the frame class is instantiated.

General structure of the frame definition class is as follows:

public interface <interface name>
extends CommonFrame
{
   <config class reference>

   <getters/setters/accessors declaration>

   public static class <config class>
   extends WidgetList
   {
      <widget declaration/instantiation>

      public void setup(CommonFrame frame)
      {
         <static frame properties initialization>

         <static widget properties initialization>
      }

      {
         <widget registration>
      }
   }
}

All these parts will be described below in more details.

The config class reference has following form:

   public static final Class configClass = <config class>.class;

The name of the variable is fixed and always is configClass. This variable is accessed by the runtime code to get the static inner class for instantiation.

The naming of getter methods follows the well-known Java Beans naming convention, i.e. name of the getter is built using widget name with capitalized first letter and with a prefix of get or is (for widgets which hold a boolean values). For example, getters generated for character variable account will have name starting with get - getAccount. The same naming scheme is used for setter methods (although the prefix in this case always is set). For example, the setter for the same character variable will be setAccount. Naming of the accessors is slightly different. Like getters and setters, the widget name with a capitalized first letter is used, but the prefix in this case is widget. Following the previous examples, the name of the accessor method will be widgetAccount. The deviation from Java Beans naming convention in this case is caused by the fact that it contains no counterparts for such methods and the prefix get is already used for the getter name.

All frame properties which are changed at run time are converted into dedicated code in business logic classes. Refer to Frame Dynamic Properties and Widget Dynamic Properties sections for more dynamic property conversion details.

It should be noted that for each widget more than one getter, setter or accessor can be generated. Overloading these methods allows accepting different types of input parameters which are automatically converted at runtime. These different versions of methods enable the application to transparently use interchangeable types without need for the business logic to handle the explicit type conversion. For example, a widget generated for a character variable will have three setters: one with a parameter of type java.lang.String, one with a parameter of type com.goldencode.p2j.util.character and one with a parameter of type com.goldencode.p2j.util.BaseDataType.

The following example of a Java frame definition interface illustrates the description provided above. The interface shown below is generated for 4GL code stored in a procedure named block01.p and a frame with the name f0:

package com.goldencode.testcases.ui._book; // package to which frame definition belongs

import com.goldencode.p2j.util.*;          // common classes
import com.goldencode.p2j.ui.*;            // common UI classes

public interface Block01F0                 // refer to section above for naming details
extends CommonFrame                        // this interface holds common methods for all frames
{
   public static final Class configClass = Block01F0Def.class; // reference to static initialization class

   public character getCh1=();              // getter for widget “ch1”

   public void setCh1(character parm);     // setter for widget “ch1”

   public void setCh1(String parm);        // another setter for same widget

   public void setCh1(BaseDataType parm);  // third setter for same widget

   public FillInWidget widgetCh1();        // accessor for widget “ch1”

   public static class Block01F0Def // definition class is named by outer interface + suffix “Def”
   extends WidgetList               // base class for all static initialization classes
   {
      FillInWidget ch1 = new FillInWidget(); // actual widget “ch1” instance created here

      public void setup(CommonFrame frame) // this method is invoked during frame initialization
      {
         frame.setDown(1);                 // frame property
         ch1.setDataType("character");     // widget property, it matches type returned by getter
         ch1.setLabel("ch1");              // widget property
         ch1.setFormat("x(8)");            // another widget property
      }

      {                                    // instance initialization
         addWidget("ch1", "ch1", ch1);     // Java widget name, 4GL widget name, widget instance
      }
   }
}

If the frame contains more than one widget, then all of them are added in static initialization class instance initialization block exactly in the order they are defined in original 4GL sources. The order is important because it defines the frame layout (see Frame Layout section below for more details).

The static frame initialization class basically consists of three main parts - widgets declared as member variables, static initialization block which adds widgets to the widget list (implemented in base class of static initialization class) and setup() method which holds frame and widget properties initialization code. The structure of the static frame initialization class is dictated by required initialization order of widgets and frame: widgets should be created, then they should be added to the frame and finally all widget and frame properties should be initialized. Note that order in which widgets are added to the frame is important, because it defines frame layout (see Frame Layout section below for more details).

To summarize, the Java frame definition provides the following:

  • A custom frame interface that defines methods to get data from widgets, set data into widgets and access each widget. This is directly used by the business logic.
  • It extends a super-interface which defines the common functionality of every frame.
  • A static inner class of the interface which is instantiated once for each frame instance. This inner class inherits from the abstract class com.goldencode.p2j.ui.WidgetList. It contains the following:
  • The set of widget instances. Each widget of the frame is a unique data member of the inner class.
  • A setup() method which provides an explicit way for the runtime to delegate the setting of frame and widget properties. In particular, this method must be called at a very specific time and order during frame initialization. Since frame initialization is implemented in the runtime, this processing must be controlled from the runtime. This method comes from the com.goldencode.p2j.ui.Settable interface and must be implemented by the static inner class since it is missing in WidgetList.
  • An anonymous initialization block in which the data members (widgets) are added to the frame's list of widgets.

Everything else about the frame is implemented in common code in the runtime, including a dynamic implementation of the getters, setters and widget accessors. Since these methods all implement common logic, the only difference is in the data type translations, method names and widgets that are being used. All of those differences can (and are) differentiated at runtime using Java reflection. This means that the implementation of these methods does not need to be explicitly implemented in the frame definition. For more details, see above for the discussion on the dynamic proxy facility.

Frame Declaration

In Progress 4GL, a frame can be declared in three different ways: with the DEFINE FRAME statement, with the FORM statement and by referencing it in the “frame phrase” of statements which may cause a frame to be displayed. A list of these statements that accept a frame phrase is provided below:

1.  ENABLE
2.  VIEW
3.  DISPLAY
4.  DOWN
5.  INSERT
6.  PROMPT-FOR
7.  SET
8.  UNDERLINE
9.  UP
10. UPDATE

Further explanation will refer to the statements in the list above as Displaying Statements. Only the first statement in the 4GL file which references the frame in displaying statements actually declares it (causes it to be created). Subsequent statements that reference that same frame name do not create a new frame but they may change the frame settings/properties or add new widgets to it.

The declaration of the frame after conversion appears in two places: first part is the frame definition interface and the second part is the code which instantiates the frame in the business logic. The business logic part of the code instantiates converted frames as instance variables of the outermost business logic class. This is mandatory because frames have logic which depends on the exit from business logic class execute() procedure. Attempting to create frames at other block nesting levels is not supported and may result in unexpected behavior. Instances of the frames are created with one of the GenericFrame class static methods - regular frames are created with createFrame() method, instances of the new shared frames are created with createSharedFrame() method and instances of shared frames created at some outer scope are obtained using importSharedFrame() method.

DEFINE FRAME Statement

Full DEFINE FRAME syntax is provided below:

DEFINE [[NEW] SHARED] FRAME name [expression ...] [DROP-TARGET] [{HEADER | BACKGROUND} header-expression ...] {[frame-phrase]}

FWD conversion supports only part of this syntax, to be exact, DROP-TARGET and BACKGROUND clauses are not supported.

The name defines the frame's name. It is converted to a Java class name and to a Java variable name for the business logic (see the section above, entitled Frame and Widget Naming in the Converted Code).

The [NEW] SHARED clause declares shared frame (see below, also see Shared Frames section).

The HEADER clause (if present) denotes start of form header widget definitions.

The syntax of expression and header-expression elements are identical and are shown below:

{var-or-field [format-phrase] | literal} [{at-phrase | {TO n}}] [presentation-element …] | {SPACE [(n)]} | {SKIP [(n)]}

The var-or-field is a reference to local variable or a database field. For details on the format-phrase, please see the Format Phrase section below.

The frame-phrase defines frame options. For details on the frame-phrase, please see the Frame Phrase section below.

Following 4GL code shows an example of a how frame can be declared with DEFINE FRAME statement (declaration01.p):

define variable i as integer.
define frame f i with no-labels.

The example above defines frame with the name f with one widget (more details on this provided below) and NO-LABELS option set (again more details about frame options and properties provided below).

Conversion of the code provided above will result to following declaration in the appropriate business logic Java class:

Declaration01F fFrame = GenericFrame.createFrame(Declaration01F.class, "f");

Just like in Progress 4GL, Java code declares frame identifier and invokes static GenericFrame.createFrame() method in order to instantiate frame class. Also, for this frame following Java interface is created during conversion:

public interface Declaration01F
extends CommonFrame
{
   public static final Class configClass = Declaration01FDef.class;

   public integer getI();

   public void setI(integer parm);

   public void setI(int parm);

   public void setI(BaseDataType parm);

   public FillInWidget widgeti();

   public static class Declaration01FDef
   extends WidgetList
   {
      FillInWidget i = new FillInWidget();

      public void setup(CommonFrame frame)
      {
         frame.setDown(1);
         frame.setNoLabels(true);
         i.setDataType("integer");
         i.setLabel("i");
      }

      {
         addWidget("i", "i", i);
      }
   }
}
FORM Statement

The syntax of FORM statement provided below:

FORM [ form-expression ... ] [ { HEADER | BACKGROUND } form-header-expression ... ] [ frame-phrase ]

The syntax of form-expression and form-header-expression is identical to expression and header-expression element described in DEFINE FRAME above, except that in addition to references to a variable or database field it may contain complex expressions. A complex expression is one that does not just convert to a reference to the variable or field. In other words, it is a sub-expression that must be evaluated before it can be displayed. Such complex expressions as well as string literals and pseudo-widgets get generated names (see Frame And Widget Naming In The Converted Code for more details about naming rules). Also, widgets generated for complex expressions and literals are by default generated as TEXT widgets and therefore can't be edited.

The other difference between FORM and DEFINE FRAME is that FORM has an effect on frame scoping. Please see the Frame Scoping and Life Time section below.

The remaining elements of the FORM syntax are identical to those of DEFINE FRAME, as well as conversion limitations. Please see the DEFINE FRAME Statement section above for an example.

Declaring a Frame in Displaying Statements

All displaying statements as part of their syntax have a frame-phrase which is identical to that described below in the Frame Phrase section.

Following example declares frame just like in two examples provided above (declaration03.p):

define variable i as integer.
display i with frame f no-labels.

Note that there is no special declaration of the frame except that the frame name is referenced in the frame phrase of the DISPLAY statement, which is one of the displaying statements.

The converted code does not differ from the samples provided above:

Declaration03F fFrame = GenericFrame.createFrame(Declaration03F.class, "f");

More details about how frame widgets are collected will be provided in next section, but before that one special case should be reviewed in more details (declaration03d.p):

define variable i as integer.
display i.

This frame declaration produces following code after conversion:

Declaration03dFrame0 frame0 = GenericFrame.createFrame(Declaration03dFrame0.class, "");

Java interface created for this frame looks so:

public interface Declaration03F
extends CommonFrame
{
   public static final Class configClass = Declaration03FDef.class;

   public integer getI();

   public void setI(integer parm);

   public void setI(int parm);

   public void setI(BaseDataType parm);

   public FillInWidget widgeti();

   public static class Declaration03FDef
   extends WidgetList
   {
      FillInWidget i = new FillInWidget();

      public void setup(CommonFrame frame)
      {
         frame.setDown(1);
         frame.setNoLabels(true);
         i.setDataType("integer");
         i.setLabel("i");
      }

      {
         addWidget("i", "i", i);
      }
   }
}

These examples demonstrate the conversion of the default (unnamed) frame. As you may notice there is no big difference in comparison to declaration of the named frame. A frame definition was created and got a generated name and the second parameter in call to GenericFrame.createFrame() is an empty literal. This illustrates one important thing in frame conversion - in converted code there is no such thing as unnamed frames, all frame variables have names and all frames have a backing frame definition class.

Collecting Widgets For The Frame

Once a frame is declared, conversion process immediately starts collecting widgets for this frame. It is possible to declare a frame without any widgets. Usually this happens by accident when the frame name is misspelled in a displaying statement like in the example shown below:

define variable i as integer.
display i with frame frame-1 no-labels.
hide frame frame1. /* frame name is slightly different here */

Such frames are dropped during conversion and no code is generated for them.

Widget references can be found in several places - in the DEFINE FRAME statement (expression and header-expression elements), the FORM statement (form-expression and form-header-expression elements) and in any displaying statement (elements identical to form-expression). The application may use any of these methods or a combination of some or all of them to define widgets for the same frame. The order in which widgets are declared is important because it dictates the frame layout and the widgets will be placed in the frame in exactly the same order as they are declared.

Consider following example (widgets01.p):

define variable i as integer.
define variable j as integer.
define variable k as integer.

define frame f i with no-labels.
form j with frame f.
display k with frame f.

This simple example uses all three possible ways to define widgets for single frame f. Widget i defined as part of DEFINE FRAME statement. Widget j is defined as part of FORM statement. And finally widget k is defined by referring it in the DISPLAY statement. For the example above following static initialization class is generated:

public static class Widgets01FDef
extends WidgetList
{
   FillInWidget i = new FillInWidget();

   FillInWidget j = new FillInWidget();

   FillInWidget k = new FillInWidget();

   public void setup(CommonFrame frame)
   {
      frame.setDown(1);
      frame.setNoLabels(true);
      i.setDataType("integer");
      j.setDataType("integer");
      k.setDataType("integer");
      j.setLabel("j");
      k.setLabel("k");
      i.setLabel("i");
   }

   {
      addWidget("i", "i", i);
      addWidget("j", "j", j);
      addWidget("k", "k", k);
   }
}

There are no differences in handling these widgets regardless of the way they are declared. For each widget, appropriate static initialization code is generated, which includes the widget data type and default label. The widget data type is a mandatory part of the widget initialization, so the conversion makes sure to determine it.

In the 4GL it is perfectly possible to leave code completely unreachable (the control flow cannot ever branch through that path). During Progress 4GL compilation (when it designs the static frames), the 4GL compiler does not analyze the reachability of the code which declares widgets. This results in all declared widgets being included in a frame even if some of them can never be used due to execution flow. The following example illustrates this behavior (widgets02.p):

define variable i as integer init 1.
define variable j as integer.
define variable k as integer.

define frame f i with no-labels.
form j with frame f.

if i < 0 then
    display k with frame f.
end

The DISPLAY statement is not reachable, because the condition in the IF statement is always false, but the widget k is still included in the frame.

Array Variables And Fields

Progress 4GL has special handling for variables declared as arrays when they are referenced without a subscript operator in displaying statements. The 4GL automatically expands each such a variable into the full set of identical widgets. When elements of the array are referenced via subscript, they automatically reference a specific element in the array and the resulting is the matching widget in the frame. Variables with subscript are handled like any other single-valued variables. The only exception to this is the use of the subscript with the FOR clause. See below for an example.

In converted code for any automatically expanded array variables, the generated getters, setters and accessors have a special form: a single method is provided that accepts an additional parameter which is the element index to be accessed/set. This allows applications to access individual widgets via a run time generated index. In addition to that special form, the normal getters, setters and accessors are generated for each element of the expanded array. They are used in cases when the index is a constant and is thus known at conversion time. The following example illustrates the automatic expansion and code generation for array variables (widget01a.p):

define variable k as integer extent 2.

display k with frame f.

Complete frame interface generated for this example is provided below:

public interface Widgets01aF
extends CommonFrame
{
   public static final Class configClass = Widgets01aFDef.class;

   public integer getKArray0();                        // getter for element 0

   public integer getKArray(NumberType parm);          // getter for arbitrary element

   public integer getKArray(double parm);              // getter for arbitrary element

   public void setKArray0(integer parm);               // setter for element 0

   public void setKArray0(int parm);                   // setter for element 0

   public void setKArray0(BaseDataType parm);          // setter for element 0

   public FillInWidget widgetkArray0();                // accessor for element 0

   public FillInWidget widgetkArray(NumberType parm);  // accessor for arbitrary element

   public FillInWidget widgetkArray(double parm);      // accessor for arbitrary element

   public integer getKArray1();                        // getter for element 1

   public void setKArray1(integer parm);               // setter for element 1

   public void setKArray1(int parm);                   // setter for element 1

   public void setKArray1(BaseDataType parm);          // setter for element 1

   public FillInWidget widgetkArray1();                // accessor for element 1

   public static class Widgets01aFDef
   extends WidgetList
   {
      FillInWidget kArray0 = new FillInWidget();

      FillInWidget kArray1 = new FillInWidget();

      public void setup(CommonFrame frame)
      {
         frame.setDown(1);
         kArray0.setIndex(1);
         kArray0.setDataType("integer");
         kArray1.setIndex(2);
         kArray1.setDataType("integer");
         kArray0.setLabel("k[1]");
         kArray1.setLabel("k[2]");
      }

      {
         addWidget("kArray0", "k", kArray0);
         addWidget("kArray1", "k", kArray1);
      }
   }
}

As mentioned above, in addition to the regular getters/setters/accessors for each widget created for an expanded array variable, a dedicated set of methods for the array variable is generated as well. These methods are necessary to support access to values and widgets by indexes generated at runtime. For each widget created for the expanded array variable, the setup code invokes the method setIndex(x), so that the widget is aware that it is a specific element of an array of widgets. These index values are necessary for the 4GL FRAME-INDEX feature support.

The following example shows the one form of subscript reference that is really a reference to a range of elements in the array. This subscript FOR syntax is partially supported in the conversion today (so long as the starting index, 1 in the example below, is a literal value).

define variable k as integer extent 5.
display k[1 FOR 3] with frame f.

The converted frame definition follows:

public interface Declaration05F
extends CommonFrame
{
   public static final Class configClass = Declaration05FDef.class;

   public integer getKArray0();

   public integer getKArray(NumberType parm);

   public integer getKArray(double parm);

   public void setKArray0(integer parm);

   public void setKArray0(int parm);

   public void setKArray0(BaseDataType parm);

   public FillInWidget widgetkArray0();

   public FillInWidget widgetkArray(NumberType parm);

   public FillInWidget widgetkArray(double parm);

   public integer getKArray1();

   public void setKArray1(integer parm);

   public void setKArray1(int parm);

   public void setKArray1(BaseDataType parm);

   public FillInWidget widgetkArray1();

   public integer getKArray2();

   public void setKArray2(integer parm);

   public void setKArray2(int parm);

   public void setKArray2(BaseDataType parm);

   public FillInWidget widgetkArray2();

   public static class Declaration05FDef
   extends WidgetList
   {
      FillInWidget kArray0 = new FillInWidget();

      FillInWidget kArray1 = new FillInWidget();

      FillInWidget kArray2 = new FillInWidget();

      public void setup(CommonFrame frame)
      {
         frame.setDown(1);
         kArray0.setIndex(1);
         kArray0.setDataType("integer");
         kArray1.setIndex(2);
         kArray1.setDataType("integer");
         kArray2.setIndex(3);
         kArray2.setDataType("integer");
         kArray0.setLabel("k[1]");
         kArray1.setLabel("k[2]");
         kArray2.setLabel("k[3]");
      }

      {
         addWidget("kArray0", "k", kArray0);
         addWidget("kArray1", "k", kArray1);
         addWidget("kArray2", "k", kArray2);
      }
   }
}

The frame only contains three widgets, while variable k is declared with extent 5. This happens because expression k[1 FOR 3] in original 4GL source refers only three elements.

For more details on array expansion, please see the sections entitled Unsubscripted Array Reference Expansion and Range Subscripting Syntax Expansion in the Data Types chapter in Part 4.

Collecting Widget Properties

Each widget has several properties (see Widget Properties Summary section below). These properties include: data type, display format, widget appearance, size, location, among others. Widget properties are collected from the Format Phrase for appropriate widget, from the declaration of the variable or database field for which the widget is created and/or from a widget declaration (for BROWSE and BUTTON widgets).

The example provided below shows how properties are collected from a Format Phrase of the variable declaration (widgets07.p):

define variable i as integer format ">>>>>>9".

define frame f i.

Static frame initialization class looks so (excerpt):

...
public void setup(CommonFrame frame)
{
   frame.setDown(1);
   i.setDataType("integer");
   i.setFormat(">>>>>>9");
   i.setLabel("i");
}
...

The format string is passed without changes to the widget. In other words, the Java runtime code is aware of and directly implements support for 4GL format strings.

The following example defines the same format string but in this case it is specified directly in the frame definition (widgets08.p):

define variable i as integer.

define frame f i format ">>>>>>9".

The converted code in this case looks identical to the one obtained by converting the previous example. In other words, regardless of the location where the property is collected, the property will be set in a uniform way, either in the static frame initialization class or in the business logic (for dynamic properties).

Since there are so many different places where a format phrase which holds widget properties (see Format Phrase section below for more details) can be present, it is common for format phrases to be specified multiple times for a specific widget. For this reason, there must be a precedence hierarchy used to determine which conflicting/overlapping format phrase option is honored.

During conversion, all Format Phrases for each widget are processed, but for each property the latest found value takes precedence and in the vast majority of cases only the last value will be assigned to the property in static frame initialization class. In some rare cases conversion may insert more than one initialization for particular property, but order is still preserved and last value is effective.

Following example illustrates conversion of repeated property definition (widgets11.p):

define variable i as integer.

define frame f i format ">>>>>>>>>>9".
display i format "999" with frame f.

Note that FORMAT property is defined twice, in DEFINE FRAME statement and in DISPLAY statement. But only last one is used (excerpt):

...
public void setup(CommonFrame frame)
{
   frame.setDown(1);
   i.setDataType("integer");
   i.setFormat("999");
   i.setLabel("i");
}
...

If a widget is defined from a database field, any properties specified in the field's schema definition are honored as the defaults. If not specified in the schema or otherwise explicitly specified in a format phrase, the default properties for a value of the given data type will be used.

For widgets that are defined from a variable, properties can come from a LIKE clause (e.g. DEFINE VARIABLE x LIKE db-field) or from the variable definition itself. If not specified via a LIKE clause or otherwise explicitly specified in the variable definition or a format phrase, the default properties for a value of the given data type will be used.

When the 4GL schema and code is parsed, each field and variable's properties are gathered and stored with the definition of that field or variable. These properties become the first values of the properties for any widgets based on those fields or variables. Format phrases specified in displaying statements or in FORM or DEFINE FRAME statements will then override the first properties, with the last specified property value being honored as the actual value.

Widget Properties Conversion

Some properties can be changed at runtime, while some remain unchanged during the entire widget lifetime. There are different conversion approaches for different properties. Some properties are converted into calls to setters in the static frame initialization class, some into various statements in the business logic class, some into different classes. Some properties have default values while some must be explicitly defined.

Format Phrase

A format phrase is the way that widget-specific properties are encoded. The format-phrase can appear in variable definitions, in database field definitions (DEFINE TEMP-TABLE or in a non-temp database schema) or with a widget defined in a DEFINE FRAME, FORM or Displaying Statement.

The syntax of the format-phrase is following:

[ ACCELERATOR ]
[ at-phrase ]
[ as-clause | like-clause ]
[ ATTR-SPACE | NO-ATTR-SPACE ]
[ AUTO-RETURN ]
[ BLANK ]
[ COLON n]
[ CONTEXT_HELP_ID ]
[ COLUMN-BGCOLOR ]
[ COLUMN-DCOLOR ]
[ COLUMN-FGCOLOR ]
[ COLUMN-FONT ]
[ COLUMN-LABEL label ]
[ COLUMN-PFCOLOR ]
[ DEBLANK ]
[ DISABLE-AUTO-ZAP ]
[ FORMAT expression ]
[ HELP string ]
[ LABEL label [ , label ] ... | NO-LABELS ]
[ LABEL-BGCOLOR ]
[ LABEL-DCOLOR ]
[ LABEL-FGCOLOR ]
[ LABEL-FONT ]
[ LABEL-PFCOLOR ]
[ MOUSE-POINTER ]
[ NO-TAB-STOP ]
[ PASSWORD-FIELD ]
[ presentation-element ]
[ TO n ]
[ VALIDATE ( condition , msg-expression ) ]
[ VIEW-AS type]
[ WIDGET-ID ]

Here is the cross-reference table to find the detailed descriptions of options have been documented in other places of this chapter or other ones.

Keyword/clause Description Where to Find Details If Supported
ACCELERATOR Keyboard accelerator of the menu. Not supported
at-phrase Defining the row and column or X and Y coordinates the displaying should be started. Widget Properties section
as-clause | like-clause New variable definition clause Data Types chapter of this book
ATTR-SPACE | NO-ATTR-SPACE Backward compatibility, placeholder only. Widget Properties section
AUTO-RETURN Automatically leaving field when pressing ENTER key. Widget Properties section
BLANK Do not display entering value. Can be used to type the password for example. Widget Properties section
COLON n The exact coordinate of the colon char. Affects the widget location and final frame layout. Widget Properties section
CONTEXT-HELP-ID The help topic ID. Not supported
COLUMN-BGCOLOR Background color of the columns Not supported
COLUMN-DCOLOR Color used to display the column in character mode interface. Widget Properties section
COLUMN-FGCOLOR Foreground color of the columns DEFINE BROWSE Statement section
COLUMN-FONT The fonts for column in GUI Not supported
COLUMN-LABEL label Label to be displayed above the widget. Widget Properties section
COLUMN-PFCOLOR Color used to display the column of the focused widget in character mode interface. Widget Properties section
DEBLANK Leading blank spaces removing when typing new value inside the widget. Widget Properties section
DISABLE-AUTO-ZAP Overrides the AUTO-ZAP attribute assuming it is turned off not depending on the attribute value. Widget Properties section
FORMAT expression Widget attribute definition DEFINE BROWSE Statement section and Data Types and Displaying Data Using Frames chapters of this book
HELP string Defines the string to be displayed when the focus is moved to the specified widget. Widget Properties section
LABEL label [. label] ... | NO-LABELS The label of the widget. Or defining the widget to not to display the label at all. Widget Properties section
LABEL-BGCOLOR The label background color. Not supported
LABEL-DCOLOR The label color of the widget in character mode interface. Widget Properties section
LABEL-FGCOLOR The label foreground color. DEFINE BROWSE Statement section
LABEL-FONT The font of the label in GUI. Not supported
LABEL-PFCOLOR The label color of the widget having the input focus in character mode interface. Widget Properties section
MOUSE-POINTER The name of the user defined mouse pointer loaded dynamically. Not supported
NO-TAB-STOP Turn off the ability of the widget to receive the input focus by TAB key event. Widget Properties section
PASSWORD-FIELD The field is used to type the password. The password is displayed as blank. Supported only during front conversion phase. Acts the same as a blank field, see the widget representation sections
presentation-element Visual representation of the data. Frame Properties section
TO n Another way to specify the widget layout by specifying the right edge coordinate to displaying the widget. It must be rendered before column n. Widget Properties section
VALIDATE (condition, msg-element ) Data consistency level approach used to verify the transactions. Validation chapter of this book
VIEW-AS type Defining static widget representation on the screen. Widget Class section
WIDGET-ID Widget identifier Not supported

Many of the above options are undocumented but have been seen used in 4GL applications.

The at-phrase has following syntax:

  AT { n }
 | {
     { {COLUMN n | COLUMN-OF ref} {ROW n | ROW-OF ref} } | { {X n | X-OF ref} {Y n | Y-OF ref} }
     [COLON-ALIGNED | LEFT-ALIGNED | RIGHT-ALIGNED]
  }

The conversion does not support relative versions of coordinate specification (so COLUMN-OF, ROW-OF, X-OF and Y-OF are not yet supported).

The syntax of presentation-element is provided below:

[ BGCOLOR expression ]
[ DCOLOR expression ]
[ FGCOLOR expression ]
[ PFCOLOR expression ]
[ FONT expression ]

Most options listed above are widget properties. For details on how each one converts, please see the Widget Properties section below.

The as-clause and like-clause are used to define a new variable inside with the statement. These are an exception to the widget property rule. For details on how they convert, see the Data Types chapter.

Widget Class

The main property which is mandatory for all widgets is the widget class. Once the widget class is defined, it cannot be changed later. The widget class is determined from widget type (regular, form header, etc.) and any explicitly defined VIEW-AS clause in the variable definition, database field definition or a format phrase.

If no VIEW-AS clause is specified, all widgets created for regular variables and database fields are converted into the FillInWidget class. The VIEW-AS clause overrides that default with a specific mapping to a Java class for each widget type (see below).

All widgets created for literals and expressions are converted into ControlTextWidget class, SKIP and SPACE converted into the SkipEntity class, button and browse widgets are converted into ButtonWidget and BrowseWidget classes respectively. None of these widget types are affected by the VIEW-AS clause.

Following example illustrates conversion of regular widgets, literals, SKIP, SPACE and header widgets (widgets05.p):

define variable i as integer init 1.
define variable j as integer.
define variable k as integer.
define variable l as integer.

define frame f "Hello!" i SKIP j SPACE k HEADER l "Another header" with no-labels.

Frame static initialization class looks so (excerpt):

...
public static class Widgets05FDef
extends WidgetList
{
   ControlTextWidget expr1 = new ControlTextWidget();

   FillInWidget i = new FillInWidget();

   SkipEntity expr3 = new SkipEntity();

   FillInWidget j = new FillInWidget();

   SkipEntity expr5 = new SkipEntity();

   FillInWidget k = new FillInWidget();

   FillInWidget l = new FillInWidget();

   ControlTextWidget expr8 = new ControlTextWidget();

   public void setup(CommonFrame frame)
   {
      frame.setDown(1);
      frame.setNoLabels(true);
      expr1.setStatic(true);
      expr1.setDataType("character");
      expr1.setFormat("x(6)");
      ((Widgets05F) frame).setExpr1(new character("Hello!"));
      i.setDataType("integer");
      expr3.setVertical(true);
      expr3.setHeight(0);
      j.setDataType("integer");
      expr5.setVertical(false);
      expr5.setWidth(1);
      k.setDataType("integer");
      l.setHeader(true);
      l.setDataType("integer");
      expr8.setHeader(true);
      expr8.setStatic(true);
      expr8.setDataType("character");
      expr8.setFormat("x(14)");
      ((Widgets05F) frame).setExpr8(new character("Another header"));
      l.setLabel("l");
      j.setLabel("j");
      k.setLabel("k");
      i.setLabel("i");
   }
...

The default class can be changed for regular and header widgets with the VIEW-AS clause. The following example illustrates one possible case (widgets06.p):

define variable i as integer view-as text.

define frame f i.

Converted code looks so (excerpt):

...
public static class Widgets06FDef
extends WidgetList
{
   ControlTextWidget i = new ControlTextWidget();
...

The widget i is generated as a ControlTextWidget instance instead of FillInWidget.

Below is provided table which summarizes mapping between the Progress 4GL widget type specified in VIEW-AS clause and the resulting widget class:

Widget type Widget class Notes
COMBO-BOX ComboBoxWidget  
EDITOR EditorWidget  
FILL-IN FillInWidget  
RADIO-SET RadioSetWidget  
SELECTION-LIST SelectionListWidget  
TEXT ControlTextWidget  
TOGGLE-BOX ToggleBoxWidget  
SLIDER SliderWidget  
Widget Properties

The following table provides a summary for all widget properties. All properties belong to a particular widget instance. This means that each method is called as an instance method on the specifically instantiated widget in the frame definition. Since these must be invoked before the frame is used, these property setter methods are called in the setup() method of the static frame initialization class:

Progress 4GL Format Phrase Element Widget Method Notes
ATTR-SPACE ControlEntity.setAttrSpace() ControlEntity is base class for ButtonWidget, ControlSetEntity (parent of ComboBoxWidget, RadioSetWidget and SelectionListWidget), ControlTextWidget (parent of EditorWidget and FillInWidget)
AUTO-ENDKEY ButtonWidget.setAutoEndKey() Part of the BUTTON definition
AUTO-GO ButtonWidget.setAutoGo() Part of the BUTTON definition
AUTO-RETURN GenericWidget.setAutoReturn() GenericWidget is the base class for root of widget class hierarchy
BGCOLOR BaseEntity.setBgcolor(color)  
BLANK GenericWidget.setBlank()  
BUFFER-CHARS EditorWidget.setBufferChars() Part of the VIEW-AS phrase
BUFFER-FIELD fieldFromName() Part of BROWSE definition. This method is generated by conversion but not supported by runtime (won't compile).
BUFFER-LINES EditorWidget.serBufferLines() Part of the VIEW-AS phrase
BUFFER-VALUE getValue()
setValue()
Part of the BROWSE definition. This method is generated by conversion but not supported by runtime (it won't compile).
CENTERED BrowseWidget.setCentered() Part of the BROWSE definition.
COLON ControlEntity.setColon()  
COLON-ALIGNED ControlEntity.setAlign(ALIGN_COLON)  
COLUMN GenericWidget.setColumn() Part of the AT phrase
COLUMN-DCOLOR setColumnDcolor() Part of the BROWSE definition. This method is generated by conversion but not supported by runtime (won't compile).
COLUMN-LABEL ControlEntity.setColumnLabel(label) Label can be split into multiple rows by inserting ! (exclamation mark) inside label text
COLUMN-OF   Not supported
COLUMN-PFCOLOR setColumnPfcolor Part of the BROWSE definition. This method is generated by conversion but not supported by runtime (it won't compile).
CONTEXT-HELP-ID   Not supported
DCOLOR GenericWidget.setDcolor()  
DEBLANK GenericWidget.setDeblank()  
DEFAULT ButtonWidget.setDefault() Part of the BUTTON definition
DISABLE-AUTO-ZAP GenericWidget.setDisableAutoZap()  
DROP-TARGET   Not supported
EXPANDABLE   Not supported
FGCOLOR BaseEntity.setFgcolor()  
FIT-LAST-COLUMN   Not supported
FLAT-BUTTON   Not supported
FONT   Not supported
FORMAT GenericWidget.setFormat()  
FREQUENCY setFrequency() Part of the VIEW-AS phrase. This method is generated by conversion but not supported by runtime (it won't compile).
HELP GenericWidget.setHelp()  
IMAGE ButtonWidget.setImage() Property is converted but has no effect
IMAGE-DOWN ButtonWidget.setImageDown() Property is converted but has no effect
IMAGE-INSENSITIVE ButtonWidget.setImageIns() Property is converted but has no effect
IMAGE-UP ButtonWidget.setImageUp() Property is converted but has no effect
INNER-CHARS GenericWidget.setInnerChars() Part of the VIEW-AS phrase
INNER-LINES GenericWidget.setInnerLines() Part of the VIEW-AS phrase
KEEP-TAB-ORDER BrowseWidget.setKeepTabOrder() Applies to the BROWSE widget. It is emitted by the conversion rules, but not supported by the runtime (the code fails to compile).
LABEL GenericWidget.setLabel() If not specified, then conversion tries to determine default label from the name of the bound variable.
Label can be split into multiple rows by inserting ! (exclamation mark) inside label text. When used for buttons, navigation mnemonic character can be set by preceding it with & (ampersand). This character will be underlined. Literal ampersand can be inserted by repeating it twice: &&.
LABEL-DCOLOR setLabelDcolor Part of the BROWSE definition. This method is generated by conversion but not supported by the runtime (it won't compile).
LABEL-PFCOLOR setLabelPfcolor Part of the BROWSE definition. This method is generated by conversion but not supported by the runtime (it won't compile).
LEFT-ALIGNED ControlEntity.setAlign(ALIGN_LEFT)  
LIST-ITEM-PAIRS ControlSetEntity.setItems() Part of the COMBO-BOX phrase
LIST-ITEMS ControlSetEntity.setItems() Part of the COMBO-BOX phrase
MAX-CHARS EditorWidget.setMaxChars() Part of the VIEW-AS phrase
MAX-VALUE setMaxValue() Part of the VIEW-AS phrase. This method is generated by conversion but not supported by the runtime (it won't compile).
MIN-VALUE setMinValue() Part of the VIEW-AS phrase. This method is generated by conversion but not supported by the runtime (it won't compile).
MULTIPLE GenericWidget.setMultiple() Part of BROWSE definition, also applicable to SELECTION-LIST
NO-ASSIGN setNoAssign() Part of the BROWSE definition. This method is generated by conversion but not supported by the runtime (it won't compile).
NO-AUTO-VALIDATE setNoAutoValidate() Part of the BROWSE definition. This method is generated by conversion but not supported by the runtime (it won't compile).
NO-BOX BrowseWidget.setNoBox()
EditorWidget.setNoBox()
Part of the BROWSE definition, also applicable to EDITOR
NO-COLUMN-SCROLLING   Part of the BROWSE definition, passes conversion but no code is emitted for it.
NO-CONVERT-3D-COLORS ButtonWidget.setNoConvert3D() Property is converted but has no effect
NO-EMPTY-SPACE   Not supported
NO-FOCUS ButtonWidget.setNoFocus()  
NO-LABELS ControlEntity.setNoLabels(true)
BrowseWidget.setNoLabels(true)
BrowseColumnWidget.setNoLabels(true)
If specified then the widget will have no label.
NO-ROW-MARKERS BrowseWidget.setNoRowMarkers() Part of the BROWSE definition
NO-SCROLLBAR-VERTICAL setNoScrollbarVertical() Part of the BROWSE definition. This method is generated by conversion but not supported by the runtime (it won't compile).
NO-SEPARATORS BrowseWidget.setNoSeparators() Part of the BROWSE definition.
NO-TAB-STOP GenericWidget.setTabStop(false) For BROWSE widgets; it is not supported by the conversion rules.
NO-VALIDATE setNoValidate() Part of the BROWSE definition. This method is generated by conversion but not supported by the runtime (it won't compile).
PASSWORD-FIELD   Supported only by the front end conversion phase. No code is emitted for it.
PFCOLOR GenericWidget.setPfcolor()  
RADIO-BUTTONS ControlSetEntity.setItems() Part of the RADIO-SET phrase.
RIGHT-ALIGNED ControlEntity.setAlign(ALIGN_RIGHT)  
ROW GenericWidget.setRow() Part of the AT phrase.
ROW-HEIGHT-CHARS   Not supported
ROW-HEIGHT-PIXELS   Not supported
ROW-OF   Not supported
SCROLLABLE BrowseWidget.setScrollable() Part of the BROWSE definition.
SCROLLBAR-HORIZONTAL BrowseWidget.setScrollBarHorizontal()
EditorWidget.setScrollBarHorizontal()
Part of the BROWSE definition, also applicable to EDITOR.
SCROLLBAR-VERTICAL BrowseWidget.setScrollBarVertical()
EditorWidget.setScrollBarVertical()
Part of the BROWSE definition, also applicable to EDITOR.
SEPARATORS BrowseWidget.setSeparators() Part of the BROWSE definition.
SINGLE BrowseWidget.setSingle()
SelectionListWidget.setSingle()
Part of the BROWSE definition, also applicable to SELECTION-LIST.
TO ControlEntity.setTo()  
TOOLTIP GenericWidget.setTooltip()  
WIDTH BaseEntity.setWidth() Part of the BROWSE definition. BaseEntity is the base class for BrowseWidget and ControlEntity.
X GenericWidget.setRow() Part of the AT phrase.
X-OF   Not supported
Y GenericWidget.setColumn() Part of the AT phrase.
Y-OF   Not supported
Mandatory Properties

All regular widgets and all form header widgets have a mandatory property: data type. This means that property must always be specified before the widget can be used. In the examples provided above, the data type is setfor each regular widget by invoking the setDataType() method in the setup() method of static frame initialization class.

SKIP and SPACE pseudo-widgets have two mandatory options: direction and size. The direction property is necessary because at run time both widgets are handled by the same class. Following example illustrates conversion of the SKIP and SPACE (widgets03.p):

define variable i as integer init 1.
define variable j as integer.
define variable k as integer.

define frame f i SKIP j SPACE k with no-labels.

The resulting static frame initialization class:

public static class Widgets04FDef
extends WidgetList
{
   FillInWidget i = new FillInWidget();

   SkipEntity expr2 = new SkipEntity();

   FillInWidget k = new FillInWidget();

   SkipEntity expr4 = new SkipEntity();

   public void setup(CommonFrame frame)
   {
      frame.setDown(1);
      frame.setNoLabels(true);
      i.setDataType("integer");
      expr2.setVertical(true);
      expr2.setHeight(0);
      k.setDataType("integer");
      expr4.setVertical(false);
      expr4.setWidth(1);
      k.setLabel("k");
      i.setLabel("i");
   }

   {
      addWidget("i", "i", i);
      addWidget("expr2", "", expr2);
      addWidget("k", "k", k);
      addWidget("expr4", "", expr4);
   }
}

Widgets expr2 and expr4 are created for SKIP and SPACE respectively. The expr2 widget is initialized as vertical (setVertical(true) is called) and with size set to 0 (setHeight(0)). The expr4 widget initialized as horizontal (notice setVertical(false) call) and of size 1 (setWidth(1)). Note that these calls illustrate the default size values for SKIP and SPACE when Progress 4GL source does not set explicit values for these widgets. If these statements have explicit values (i.e. statements have form SKIP(x) or SPACE(x)) then these values are used to initialize appropriate widget properties. The default SKIP vertical size 0 has special meaning during generation of the frame layout. See more details in the Frame Layout section.

A mandatory property for frame header widgets is the header flag. It is set by calling setHeader(true) in the static frame initialization class. See the Widget Class section for an example.

Widgets generated for literals have two mandatory properties: the static flag and the format string. The format string is generated from the actual literal content and always has form “x(ZZZ)”, where ZZZ is the literal string length. The static flag is set by calling setStatic(true) in the static frame initialization class. The example provided in the Widget Class section illustrates this case, see the code generated for the expr1 widget.

At this time, mandatory properties are always generated in the body of the setup() method of static frame initialization class. In the future, these properties will be passed in the widget-specific constructor to reduce lines of code.

CREATE BROWSE Statement

Not supported.

CREATE Widget Statement

Not supported.

DEFINE BROWSE Statement

The BROWSE widget is not a single widget. Rather, it is a container for several column widgets, a title, common decorations and a dynamically created and destroyed FILL-IN widget which is used to edit column values. Most of this complexity is hidden from the end user, but an application has access to each column as a widget and this dictates conversion of the BROWSE widget as a main BROWSE widget and several browse column widgets. The application has no access to the dynamically created FILL-IN widget.

Browse widgets are defined using DEFINE BROWSE statement. Its syntax is provided below:

DEFINE [ [ NEW ] SHARED ] BROWSE name [ QUERY query-name ]
   [ SHARE-LOCK | EXCLUSIVE-LOCK | NO-LOCK ]
   [ NO-WAIT ]
   [ DISPLAY { column-list | record [ EXCEPT field ... ] } ]
   [ browse-enable-phrase ]
   { browse-options-phrase }
   [ CONTEXT-HELP-ID expression ]
   [ DROP-TARGET ]
   [ TOOLTIP tooltip ]

The [NEW] SHARED option for the browse is not supported. The QUERY name option binds the query to the browse widget. Options SHARE-LOCK, EXCLUSIVE-LOCK, NO-LOCK, NO-WAIT are converted to the appropriate query flags as necessary (see more details in the Queries chapter of Part 5).

In contrast with the official DEFINE BROWSE syntax, testing showed that the entire QUERY and DISPLAY columns clauses are optional, with an important note: in the 4GL, if the QUERY clause is missing, then the DISPLAY columns clause must be missing too. The conversion rules do not enforce this restriction, so it is possible to convert a DEFINE BROWSE statement with a DISPLAY columns clause but with no QUERY clause.

The syntax of the column-list phrase is provided below:

expression [column-format] [ @ base-field] ...

In the above, the expression is a reference to a database field, a variable, a literal or an expression which will be displayed in the corresponding browse column.

The syntax of the column-format follows:

[ FORMAT expression ]
[ LABEL label | NO-LABELS ]
[ WIDTH n ]
[ COLUMN-LABEL label ]
[ column-presentation-element ... ]
[ label-presentation-element ... ]

The syntax of column-presentation-element is provided below:

[ COLUMN-FONT expression ]
[ COLUMN-DCOLOR expression ]
[ COLUMN-BGCOLOR expression ]
[ COLUMN-FGCOLOR expression ]
[ COLUMN-PFCOLOR expression ]

The syntax of label-presentation-element follows:

[ LABEL-FONT constant ]
[ LABEL-DCOLOR expression ]
[ LABEL-BGCOLOR expression ]
[ LABEL-FGCOLOR expression ]

All these options are converted to appropriate column widget properties except COLUMN-FONT, LABEL-FONT, COLUMN-BGCOLOR and LABEL-BGCOLOR, which are not supported.

The record refers to the database table being queried and is expanded into full list of table fields during conversion (see Part 5 for more details on implicit record expansion). The EXCEPT clause lists fields which should be excluded from the expanded list of columns.

The browse-enable-phrase has the following syntax:

ENABLE { { field [ HELP string ]
                 [ VALIDATE (condition , message) ]
                 [ AUTO-RETURN ]
                 [ DISABLE-AUTO-ZAP ] } ...  |
         ALL [ EXCEPT field ] }

This option defines columns which can be edited by the user at run time and the remaining options are actually properties of the dynamically created FILL-IN widget. Just like the record phrase described above, ALL [ EXCEPT field ]} option enables for editing all fields except listed in EXCEPT clause.

The browse-options-phrase syntax is provided below:

WITH { [ constant ] DOWN [ WIDTH width ] | [ size-phrase ] }
     [ DROP-TARGET ]
     [ EXPANDABLE ]
     [ FIT-LAST-COLUMN ]
     [ label-presentation-element ... ]
     [ MULTIPLE | SINGLE ]
     [ NO-ASSIGN ]
     [ NO-AUTO-VALIDATE ]
     [ NO-BOX ]
     [ NO-EMPTY-SPACE ]
     [ NO-LABELS ]
     [ NO-ROW-MARKERS ]
     [ NO-SCROLLBAR-VERTICAL | SCROLLBAR-VERTICAL ]
     [ NO-VALIDATE ]
     [ NO-TAB-STOP ]         /* undocumented option */
     [ KEEP-TAB-ORDER ]      /* undocumented option */
     [ CENTERED ]            /* undocumented option */
     [ SCROLLABLE ]          /* undocumented option */
     [ NO-COLUMN-SCROLLING ] /* undocumented option */
     [ presentation-element ... ]
     [ ROW-HEIGHT-CHARS | ROW-HEIGHT-PIXELS ] row-height
     [ SEPARATORS | NO-SEPARATORS ]
     [ title-phrase ]

All supported options are converted into browse widget properties. The following options are not supported: ROW-HEIGHT-CHARS, ROW-HEIGHT-PIXELS, FIT-LAST-COLUMN, EXPANDABLE, NO-EMPTY-SPACE, DROP-TARGET. The title-phrase in browse widget has following syntax:

TITLE expression

The expression can be simple string literal or more complex expression. In case of complex expression browse title is an dynamic browse property and can be changed at runtime. Detailed description of this case provided in separate section Dynamic BROWSE Title below.

Following example illustrates conversion of the BROWSE widget (widgets13.p):

def temp-table test no-undo field tot-hrs as int field proj-mgr  as int.

create test.
assign tot-hrs = 50 proj-mgr = 1.

def query q for test.
def browse brws query q no-lock no-wait
    display test with single 5 down centered no-separators.

open query q for each test no-lock.

form brws with frame f-frame.
enable all with frame f-frame.
wait-for close of current-window.

This excerpt from the business logic class shows how binding between the browse and the query is converted:

...
// bind browse columns to table fields
FrameElement[] elementList0 = new FrameElement[]

   new Element(new FieldReference(test, "totHrs"), fFrameFrame.widgetBrwsTotHrsColumn()),
   new Element(new FieldReference(test, "projMgr"), fFrameFrame.widgetBrwsProjMgrColumn())
};

// register query with the browse widget
fFrameFrame.widgetBrws().registerQuery(query0, elementList0);

// assign particular query implementation to query wrapper
query0.assign(new AdaptiveQuery(test, (String) null, null, "test.id asc", LockType.NONE));

// init the query and load required records
query0.open();
...

The static frame initialization class is shown below:

public static class Widgets13FFrameDef
extends WidgetList

   BrowseWidget brws = new BrowseWidget();

   BrowseColumnWidget brwsTotHrsColumn = new BrowseColumnWidget();

   BrowseColumnWidget brwsProjMgrColumn = new BrowseColumnWidget();

   public void setup(CommonFrame frame)
   {
      frame.setDown(1);
      brws.setSingle(true);
      brws.setDown(5);
      brws.setCentered(true);
      brws.setNoSeparators(true);
      brws.addColumn(brwsTotHrsColumn);
      brwsTotHrsColumn.setDataType("integer");
      brwsTotHrsColumn.setLabel("tot-hrs");
      brws.addColumn(brwsProjMgrColumn);
      brwsProjMgrColumn.setDataType("integer");
      brwsProjMgrColumn.setLabel("proj-mgr");
   }

   {
      addWidget("brws", "", brws);
      addWidget("brwsTotHrsColumn", "tot-hrs", brwsTotHrsColumn);
      addWidget("brwsProjMgrColumn", "proj-mgr", brwsProjMgrColumn);
   }
}

Each column is converted into a separate widget just as if it was separately declared. Then each column is added to the BROWSE widget.

DEFINE BUTTON Statement

Button widgets are defined using DEFINE BUTTON statement:

DEFINE BUTTON name [ AUTO-GO | AUTO-ENDKEY ]
                   [ CONTEXT-HELP-ID expression ]
                   [ DEFAULT ]
                   [ DROP-TARGET ] [presentation-element ...]
                   [ IMAGE-DOWN image-phrase ]
                   [ { IMAGE | IMAGE-UP } image-phrase ]
                   [ IMAGE-INSENSITIVE image-phrase ]
                   [ MOUSE-POINTER name ]
                   [ LABEL label ]
                   [ LIKE button ]
                   [ size-phrase ]
                   [ NO-FOCUS [ FLAT-BUTTON ] ]
                   [ NO-CONVERT-3D-COLORS ]
                   [ TOOLTIP tooltip ]
                   { [ trigger-phrase ] }

The presentation-element syntax described in the DEFINE FRAME Syntax section. All supported (see table above) options are converted into button widget properties. The trigger-phrase converted as part of business logic. Conversion of trigger-phrase is not supported.

Following example illustrates conversion of DEFINE BUTTON statement:

define button button1 LABEL "Hello".

on choose of button1
do:
    message "Button 1".
end.

define frame f button1.

Static frame initialization class for this class looks so:

public static class Widgets17FDef
extends WidgetList
{
   ButtonWidget button1 = new ButtonWidget();

   public void setup(CommonFrame frame)
   {
      frame.setDown(1);
      button1.setLabel("Hello");
   }

   {
      addWidget("button1", "", button1);
   }
}

Excerpt from the business logic code provided below:

...
public void execute()
{
   externalProcedure(new Block()
   {
      public void body()
      {
         fFrame.openScope();
         EventList list0 = new EventList();
         list0.addEvent("choose", frame0.widgetButton1());
         registerTrigger(list0, TriggerBlock0.class, Widgets17.this);
      }
   });
}

public class TriggerBlock0
extends Trigger
{
   public void body()
   {
      message("Button 1");
   }
}
...

DEFINE IMAGE Statement

Not supported.

DEFINE MENU Statement

Not supported.

DEFINE RECTANGLE Statement

Not supported.

DEFINE SUB-MENU Statement

Not supported.

COMBO-BOX, SELECTION-LIST and RADIO-SET Conversion

These widgets are converted in similar way, despite different look and feel. COMBO-BOX is converted to ComboBoxWidget class, SELECTION-LIST - to SelectionListWidget class and RADIO-SET - to RadioSetWidget class. All these classes are inherited from ControlSetEntity class. The commonality of these widgets is that all of them have a list of options displayed to the user. Following example illustrates conversion of all three types of widgets (widgets12.p):

def var i0 as int init 3
   view-as combo-box list-items 100,300,3,7,9 drop-down-list inner-lines 3.

def var i1 as int init 0
   view-as combo-box list-item-pairs "Zero", 0, "One", 1, "Two", 2 simple sort inner-lines 7.

def var i2 as int init 5
   view-as radio-set radio-buttons "Five", 5, "Ten", 10 horizontal size 16 by 3.

def var i3 as char init "3" 
   view-as selection-list list-items "100","300","3","7","9" inner-lines 3 inner-chars 10.

form i0 skip(1) i1 skip(1) i2 with frame f0.

prompt-for i0 i1 i2 i3 with frame f0 side-labels.

This Progress 4GL code after conversion produces following initialization code (excerpt):

...
public static class Widgets12F0Def
extends WidgetList
{
   ComboBoxWidget i0 = new ComboBoxWidget();

   SkipEntity expr2 = new SkipEntity();

   ComboBoxWidget i1 = new ComboBoxWidget();

   SkipEntity expr4 = new SkipEntity();

   RadioSetWidget i2 = new RadioSetWidget();

   SelectionListWidget i3 = new SelectionListWidget();

   public void setup(CommonFrame frame)
   {
      frame.setDown(1);
      frame.setSideLabels(true);
      i0.setDataType("integer");
      expr2.setVertical(true);
      expr2.setHeight(1);
      i1.setDataType("integer");
      expr4.setVertical(true);
      expr4.setHeight(1);
      i2.setDataType("integer");
      i3.setDataType("character");
      i0.setLabel("i0");
      i0.setItems(new ControlSetItem[]
      {
         new ControlSetItem(new character("100"), new integer(100)),
         new ControlSetItem(new character("300"), new integer(300)),
         new ControlSetItem(new character("3"), new integer(3)),
         new ControlSetItem(new character("7"), new integer(7)),
         new ControlSetItem(new character("9"), new integer(9))
      });
      i0.setModeDropDownList(true);
      i0.setInnerLines(3);
      i3.setLabel("i3");
      i3.setItems(new ControlSetItem[]
      {
         new ControlSetItem(new character("100"), new character("100")),
         new ControlSetItem(new character("300"), new character("300")),
         new ControlSetItem(new character("3"), new character("3")),
         new ControlSetItem(new character("7"), new character("7")),
         new ControlSetItem(new character("9"), new character("9"))
      });
      i3.setInnerLines(3);
      i3.setInnerChars(10);
      i2.setLabel("i2");
      i2.setItems(new ControlSetItem[]
      {
         new ControlSetItem(new character("Five"), new integer(5)),
         new ControlSetItem(new character("Ten"), new integer(10))
      });
      i2.setHorizontal(true);
      i2.setWidthChars(16);
      i2.setHeightChars(3);
      i1.setLabel("i1");
      i1.setItems(new ControlSetItem[]
      {
         new ControlSetItem(new character("Zero"), new integer(0)),
         new ControlSetItem(new character("One"), new integer(1)),
         new ControlSetItem(new character("Two"), new integer(2))
      });
      i1.setHonorFormat(false);
      i1.setModeSimple(true);
      i1.setSort(true);
      i1.setInnerLines(7);
   }
...

For all items very similar code is generated and regardless of the item type for each item generated instance of ControlSetItem class which represents single item. The first parameter passed to the ControlSetItem constructor is the character representation of the item. The second parameter is the actual value which will be returned from the widget.

@ Field Conversion

In Progress 4GL it is possible to mark some field in DISPLAY statement with character and refer some other widget. Such a fields switch referred FILL-IN widget into a special mode. Unlike a regular FILL-IN widget it does not display data itself. Instead it notifies the runtime that the data must be inserted into the named widget instead of creating a new widget as would normally occur. Type of the data may not match the data type specified for the widget.

The actual width of the widget is set to widest of the base field format width and widths of “overriding formats” of all expressions displayed in this widget. An overriding format is determined using the following precedence order:

  1. An explicit format string in a format phrase for the displayed expression (if should appear before the @ character).
  2. If the expression is a string literal, its length is used.
  3. If the data type of the expression is the same as that of the base field, then the expression format is ignored (no further rules will be processed).
  4. The explicit format string from the variable or database field declaration.
  5. The default format string for that data type (for literals, variables, database fields or complex expressions).

Regardless from the case the width of the @ field is set explicitly during conversion. An example:

define variable i as integer.
define variable j as integer.
define variable str as char format "x(15)".
define variable base-str as char format "x(5)".

define frame f base-str.

display i @ base-str with frame f.
display j format "999999" @ base-str with frame f.
display str @ base-str with frame f.

Format of the base field (base-str) is “x(5)”, overriding formats are “->,>>>,>>9” (default format for integer i), “999999” (explicit format for displaying j), “x(15)” (format of str). Format of str is ignored because this variable is of the same data type as the base field. The widest among other formats is “->,>>>,>>9”, so the widget width should be set to 10. The static frame initialization code reflects this:

...
frame.setDown(1);
baseStr.setDataType("character");
baseStr.setAt(true);
baseStr.setWidthChars(10);
baseStr.setFormat("x(5)");
baseStr.setLabel("base-str");
...

Additionally, the widget is marked as an field ( setAt(true)call). Also, note that there are no widgets i , j or str.

When format string is specified explicitly for @ field (in one or several statements) then the rules are a bit different: the width of the widget is set to the widest of the width of the last explicit base field format and widths of overriding formats of all expressions displayed in this widget (the rules which define overriding formats are listed before). Example provided below illustrates this behavior (widgets09b.p)::

def var txt as char init "abc" format "x(20)".
def var override as int init 0.

form txt with frame f1.

display override format "9999999999999" @ txt with frame f1.
display override @ txt with frame f1.
display txt with frame f1.

(widgets09c.p):

def var txt as char init "abc" format "x(20)".
def var override as int init 0.

form txt format "x(2)" with frame f1.

display override format "9999999999999" @ txt with frame f1.
display override @ txt with frame f1.
display txt with frame f1.

The difference between these cases only in explicitly set format in the FORM statement for txt widget. For the first case, explicitly defined format for the txt variable defines width of the widget (excerpt from the static frame initialization class):

...
public void setup(CommonFrame frame)
{
   frame.setDown(1);
   txt.setDataType("character");
   txt.setAt(true);
   txt.setWidthChars(20);
   txt.setFormat("x(20)");
   txt.setLabel("txt");
}
...

Second example produces following static frame initialization code (excerpt):

...
public void setup(CommonFrame frame)
{
   frame.setDown(1);
   txt.setDataType("character");
   txt.setFormat("x(2)");
   txt.setAt(true);
   txt.setWidthChars(13);
   txt.setLabel("txt");
}
...

The resulting width of the @ field (13) in this case is set to the widest of explicit base field format (“x(2)”) and overriding formats "9999999999999" (first DISPLAY statement) and “->,>>>,>>9” (second DISPLAY statement).

Use of Callback Expressions in Widgets

Some Progress 4GL language constructions assume that the runtime has access to the current value of some expression far from the point where this expression is defined. In such cases during conversion additional code called a callback expression is generated in the business logic class. The callback expression is a Java inner class which is non-static. This means it has access to the compilation unit scope (including all variables defined in the scope). This class has a method which can be invoked at any time to evaluate the expression using the current values of variables or other business logic state (e.g. database fields) referenced in the expression. In FWD all callback expressions implement the com.goldencode.p2j.util.Resolvable interface. Evaluation may happen deep into the runtime, far outside the scope of the compilation unit in which the expression is defined. It can also occur far away from the point in the control flow where expression is defined.

The inner class is then instantiated in the business logic and registered with the runtime for the specific purpose for which it was used in the original 4GL code. When the runtime determines that the latest value of that expression is needed, it calls the resolve() method and obtains the current value.

In some cases (when the expression consists of a single variable), the FWD runtime uses a simple reference to this variable and generates an instance of an anonymous Java class automatically (without the business logic needing to have an extra inner class just to expose this variable).

In cases when the simple expression consists of a single database field, the database field is passed as an instance of the com.goldencode.p2j.persist.@FieldReference@ class. This class implements the com.goldencode.p2j.util.@Accessor@ interface. The implementation of the FieldReference allows delegated read/write of the database field, without any need for the delegate code to be database aware.

The following example illustrates the generation of a complex callback expression:

4GL source code:

define variable i as integer init 1.
define variable j as integer init 12.
define variable k as integer init 123.
define variable l as integer init 1234.

form "Hello!" i j k header string(l) "Another header" with frame f with no-labels.
display i j k l with frame f.

The first header string(l) is a complex expression.

Business logic class (excerpt):

...
public void body()
{
   fFrame.openScope();

   FrameElement[] elementList0 = new FrameElement[]
   {
      new HeaderElement(new HeaderExpr0(), fFrame.widgetExpr5()), // uses callback expression
      new HeaderElement("Another header", fFrame.widgetExpr6())
   };
   fFrame.registerHeader(elementList0); // registration of the headers with associated callbacks

   ...

   fFrame.display(elementList1); // evaluation of callback happens during processing this call
}
...
private class HeaderExpr0
extends GenericExpression
{
   public BaseDataType resolve()
   {
      return valueOf(l);
   }
}
...

The next example illustrates the conversion of a simple expression which uses an anonymous callback expression class generated by runtime.

4GL source code:

define variable i as integer init 1.
define variable j as integer init 12.
define variable k as integer init 123.
define variable l as integer init 1234.

form "Hello!" i j k header l "Another header" with frame f with no-labels.
display i j k l with frame f.

Business logic class (excerpt):

public void body()
{
   fFrame.openScope();

   // array of callback expressions generated for headers
   FrameElement[] elementList0 = new FrameElement[]
   {
      new HeaderElement(l, fFrame.widgetl()), // reference to the variable 'l' is passed
      new HeaderElement("Another header", fFrame.widgetExpr6())
   };

   fFrame.registerHeader(elementList0); // registration of the headers with associated callbacks
   ...
   fFrame.display(elementList0); // evaluation of callback happens during processing this call
}

Next example shows conversion of database field reference:

4GL source code:

define variable i as integer init 1.
def temp-table test no-undo
    field frm-title as char.

form "Hello!" i with frame f with no-labels title frm-title.
display i with frame f.

Business logic class (excerpt):

...
public void body()
{
   fFrame.openScope();
   fFrame.setDynamicTitle(new FieldReference(test, "frmTitle")); // reference to field is wrapped
   RecordBuffer.openScope(test);

   FrameElement[] elementList0 = new FrameElement[]
   {
      new Element(i, fFrame.widgeti())
   };

   fFrame.display(elementList0);
}
...

All cases in which a callback expression can be generated are described in following sub-sections.

HEADER Widgets

The widgets declared after HEADER clause in FORM and DEFINE FRAME statements (see appropriate sections above) which are not literals may refer variables and even complex expressions (e.g. operators, function calls...). In these cases the converted code uses callback expressions (provided by the runtime for single variable expressions or generated in business logic class for complex expressions). The section Use of Callback Expressions in Widgets describes callback expressions in more detail and contains examples of handling HEADER widgets for both (simple and complex) cases.

Dynamic BROWSE Title

The BROWSE widget supports dynamic change of title with callback expressions described above. In case of browse title the callback expression is registered with BrowseWidget instance method setDynamicTitle().

Following example illustrates conversion of dynamic browse title:

4GL source code:
def var dt as char init "First title".
def temp-table t field f as int field g as int.

def query q for t.

def browse brws query q no-lock no-wait display t with single 5 down title dt.
open query q for each t no-lock.
form brws with frame f.
view frame f.

Converted business logic code (excerpt):

...
public void body()
{
   fFrame.openScope();
   RecordBuffer.openScope(t);
   fFrame.widgetBrws().setDynamicTitle(dt);  // registering dynamic browse title
...

   fFrame.view();                            // browse title is evaluated here
}
...
Embedded Assignments

In SET, UPDATE and PROMPT-FOR statements, the Progress 4GL supports a special syntax for widgets called embedded assignment. The embedded assignment does not create a new frame widget. Instead it provides an expression which must be evaluated at the moment that the editing operation is complete and all widget values are assigned back to the variables in the calling code.

The approach encodes an embedded assignment expression such that evaluation can be delegated to the user interface processing at a specific point in the normal assignment processing, as defined by the order of the FrameElement array passed to the helper.

Each embedded assignment is converted into a EmbeddedAssignment instance in the FrameElement array in the business logic class. These elements provide enough data for the runtime to detect that an embedded assignment must be processed and the elements provide the variable/field (the assignee) and the expression whose evaluated value must be assigned. The assignment expression is converted into a dedicated class derived from GenericExpression class. This allows the expression to be encoded for deferred evaluation (by the runtime).

The code for the expression class is emitted as an inner class in the business logic class code. An instance of this dedicated class then is passed as a parameter to new EmbeddedAssignment instance. Order in which instances of EmbeddedAssignment are created in the array is exactly the same order as they are mentioned in the source code. This maintains the proper order of execution. Note that if an assignment uses a variable/field declared in the same SET, UPDATE or PROMPT-FOR statement before the assignment, then the value set by user is used, otherwise the old value is used. Example:

 def temp-table tt field f1 as integer field f2 as integer.
 def var str as char.
 create tt. f1 = 1. f2 = 2.

 update f1
        str = "f1 is " + string(f1) + ", f2 is " + string(f2)
        f2
        with frame f2.
 message str.

Consider user has entered “3” as f1 and “4” as f2. At the embedded assignment execution point f1 has the new value “3” assigned, while the new value of f2 has not been assigned yet and it is still “2”, so the message output is:

f1 is 3, f2 is 2

The EmbeddedAssignment frame element is otherwise completely ignored for normal user interface processing. In particular, it is not associated with any widget nor does it change the state, behavior or appearance of the user interface in any way.

Typically, this element is part of a larger array of FrameElement objects, most of which do affect the state, behavior and appearance of the user interface. In some cases a user interface helper method handles assignment of data modified by the user back into the original object. This operation generally occurs at the end of the helper's processing.

An embedded assignment is one where the assignee and new value are defined in calling code but the actual assignment is done at a later time in the user interface helper. This delegation approach is used where it is necessary to intermix or insert this assignment in a specific order with other embedded assignments and/or with the frame element assignments that may occur.

It is also important to note that the evaluation of the given expression occurs at assignment time and thus honors the current state of the calling class. This current state includes any of the variables or fields which have been modified by having had edited widget values assigned back by FrameElement objects in the array. Since all of this assignment processing is done by the user-interface runtime code AND the evaluation of the embedded assignment expressions must be deferred to that time, inserted in the proper order, this class is needed to support that behavior.

The syntax is documented for SET and UPDATE but working code has been found that shows that the syntax is valid for use in PROMPT-FOR as well. This undocumented but valid usage is supported, however since PROMPT-FOR does not assign back its values automatically, the benefit of this is yet to be determined.

Following example illustrates conversion of simple embedded assignment expression.

4GL source code:

def var i as int.
def var k as int.
set i
    k = 3.

Converted business logic class (excerpt):

...
public void body()
{
   frame0.openScope();

   FrameElement[] elementList0 = new FrameElement[] // array of FrameElement instances
   {
      new Element(i, frame0.widgeti()),
      new EmbeddedAssignment(k, new EmbeddedAssignmentExpr0()) // instance of expression class
   };

   frame0.set(elementList0); // expression is evaluated here, before displaying widgets to user
}
...
private class EmbeddedAssignmentExpr0 // dedicated embedded assignment expression calculation class
extends GenericExpression
{
   public BaseDataType resolve()
   {
      return new integer(3);
   }
}
...

This example shows the assignment of a constant (3) to the variable k, but the evaluated result of more complex expressions can also be assigned via this mechanism. The converted code handles all those cases the same way (as a new instance of an inner class that is of type Resolvable). While the array of FrameElement instances passed to the set() method contains two elements, the user will only be editing the widget i. Interestingly, there actually is no widget k in the frame in this case since the second element in this array is used only for the deferred assignment.

At this point embedded assignments in FWD have the following limitations: assignee can only be a variable (local or shared), and the new value can only be represented by an expression with constants and database fields (but not a standalone database field). This will be fixed in a future release.

Changing Widget Properties At Runtime

Widget properties specified in format phrase are assigned in static frame initialization class. But application can change many widget properties at runtime, using colon (“:”) to obtain current property value or assign new value. Actual operation (retrieving or assigning value) depends on the location of the reference to widget property in the assignment statement relatively to “=” sign. If property appears at the left side of the equals sign, then statement assigns property value, if property is at the right side of equals sign then statement retrieves the property value. The following example illustrates both, retrieving and assignment of the property value (widgets10.p):

define variable i as integer.
define variable r as integer.

define frame f i format ">>>>>>>>>>9".
display i with frame f.
r = i:row. /* property value is retrieved */
i:row = r + 1. /* property value is assigned */
display i with frame f.

Note that unlike specifying property in format phrase, dynamic property appears as regular variable in regular statements. During conversion these statements including access to dynamic widget properties are converted as part of the business logic classes:

...
public void body()
{
   fFrame.openScope();

   FrameElement[] elementList0 = new FrameElement[]
   {
      new Element(i, fFrame.widgeti())
   };

   fFrame.display(elementList0);

   r.assign(fFrame.widgeti().getRow()); // get widget property value
   fFrame.widgeti().setRow(plus(r, 1)); // assign new value to widget property

   FrameElement[] elementList1 = new FrameElement[]
   {
      new Element(i, fFrame.widgeti())
   };

   fFrame.display(elementList1);
}
...

Two statements right after first fFrame.display() call set new value to ROW property.

Following table lists supported widget properties which can be retrieved/assigned at runtime:

Widget property Notes
BGCOLOR -
COLUMN -
DATA-TYPE -
DCOLOR -
FGCOLOR -
FORMAT -
FRAME -
HEIGHT -
HEIGHT-PIXELS converted, but not supported by runtime (won't compile)
HELP -
HIDDEN -
KEEP-Z-ORDER converted, but not supported by runtime (won't compile)
LABEL -
LIST-ITEM-PAIRS -
MAX-HEIGHT converted, but not supported by runtime (won't compile)
MAX-WIDTH converted, but not supported by runtime (won't compile)
MESSAGE-AREA converted, but not supported by runtime
MULTIPLE -
NAME -
NEXT-TAB-ITEM converted, but not supported by runtime
NUM-SELECTED-ROWS converted, but not supported by runtime
PARENT converted, but not supported by runtime
PFCOLOR -
PRIVATE-DATA -
READ-ONLY -
REPOSITIONED-ROW -
RESIZE converted, but not supported by runtime
ROW -
SCREEN-VALUE -
SCROLLABLE -
SCROLL-BARS converted, but not supported by runtime
SELECTED -
SENSITIVE -
STATUS-AREA converted, but not supported by runtime
THREE-D converted, but not supported by runtime
TITLE -
TOOLTIP -
VIRTUAL-HEIGHT converted, but not supported by runtime
VIRTUAL-WIDTH converted, but not supported by runtime
VISIBLE -
WIDTH -
WIDTH-PIXELS converted, but not supported by runtime (won't compile)
X  
Y  

Frame Properties

Frame properties are collected from all frame phrases related to a particular frame. Each element of the frame phrase can be either static (fully defined at compile time) or dynamic (only resolvable when the program is run). All static frame properties are removed from the business logic and are set in the frame definition's static inner class. Dynamic properties are converted into the appropriate code in the business logic class (see Frame Dynamic Properties section below).

When the same option is specified multiple times, the last frame property value to be specified (from top to bottom of the source file) takes precedence. This is the same way things work with widgets options.

Frame Phrase

The syntax of frame-phrase is as follows:

WITH [ ACCUM [ max-length ] ]
     [ frame-at-phrase ]
     [ ATTR-SPACE | NO-ATTR-SPACE ]
     [ CANCEL-BUTTON button-name ]
     [ CENTERED ]
     [ frame-presentation-element ...]
     [ COLUMN expression ]
     [ n COLUMNS ]
     [ CONTEXT-HELP ]
     [ CONTEXT-HELP-FILE help-file-name ]
     [ DEFAULT-BUTTON button-name ]
     [ [ expression ] DOWN ]
     [ EXPORT ]
     [ FRAME frame ]
     [ KEEP-TAB-ORDER ]
     [ NO-BOX ]
     [ NO-HIDE ]
     [ NO-LABELS ]
     [ USE-DICT-EXPS ]
     [ NO-VALIDATE ]
     [ NO-AUTO-VALIDATE ]
     [ NO-HELP ]
     [ NO-UNDERLINE ]
     [ OVERLAY ]
     [ PAGE-BOTTOM | PAGE-TOP ]
     [ RETAIN n ]
     [ ROW expression ]
     [ SCREEN-IO | STREAM-IO ]
     [ SCROLL n ]
     [ SCROLLABLE ]
     [ SIDE-LABELS ]
     [ size-phrase ]
     [ STREAM stream ]
     [ THREE-D ]
     [ title-phrase ]
     [ TOP-ONLY ]
     [ USE-TEXT ]
     [ V6FRAME [ USE-REVVIDEO | USE-UNDERLINE ] ]
     [ VIEW-AS DIALOG-BOX ]
     [ WIDTH n ]
     [ IN WINDOW window ]

The ACCUM option converted into business logic code. More details can be found in the Accumulators chapter.

The frame-at-phrase is a shorter version of at-phrase shown above:

AT { COLUMN n ROW n } | { X n Y n }

The frame-presentation-element has following syntax:

presentation-element | { COLOR [DISPLAY] color-specification [PROMPT color-specification] }

The syntax of the presentation-element is provided above.

The size-phrase has following syntax:

{ SIZE | SIZE-CHARS | SIZE-PIXELS } n BY n

The syntax of title-phrase provided below:

TITLE [ presentation-element ... ] text

Following options are not supported by FWD conversion: CONTEXT-HELP, CONTEXT-HELP-FILE, EXPORT, USE-DICT-EXPS, SCREEN-IO, STREAM-IO, SIZE-PIXELS version of size-phrase, V6FRAME, USE-REVVIDEO, IN WINDOW clause.

All options of the frame-phrase except ACCUM and STREAM are converted as frame properties. The STREAM option is converted special version of appropriate CommonFrame class method. For example, DISPLAY statement with STREAM option is converted as call to special version of CommonFrame.display() method which accepts reference to stream as additional parameter. Following example illustrates such a conversion:

def var i as int.
def var j as char.
def stream str.

display stream str i j with frame f0.

For the DISPLAY statement following code is generated in business logic:

...
Stream strStream = new StreamWrapper("str");
...
public void body()
{
   f0Frame.openScope();
   TransactionManager.registerTopLevelFinalizable(strStream, true);

   FrameElement[] elementList0 = new FrameElement[]
   {
      new Element(i, f0Frame.widgeti()),
      new Element(j, f0Frame.widgetj())
   };

   f0Frame.display(strStream, elementList0);
}
...

The first parameter of the f0Frame.display() call is a reference to stream str. Please see the Redirected Terminal chapter for more details on streams usage in the user interface.

Following example illustrates conversion of some often used properties which appear in the frame phrase:

define variable i as integer.
define frame f i with overlay row 5 column 4 title "Frame title" attr-space no-labels.

All these properties are converted as calls to appropriate frame methods in the static frame initialization class:

...
public static class Declaration01zFDef
extends WidgetList
{
   FillInWidget i = new FillInWidget();

   public void setup(CommonFrame frame)
   {
      frame.setDown(1);
      frame.setOverlay(true);         // result of OVERLAY option in frame phrase
      frame.setRow(5);                // result of ROW option in frame phrase
      frame.setColumn(4);             // result of COLUMN option in frame phrase
      frame.setTitle("Frame title");  // result of TITLE option in frame phrase
      frame.setAttrSpace(true);       // result of ATTR-SPACE option in frame phrase
      frame.setNoLabels(true);        // result of NO-LABELS option in frame phrase
      i.setDataType("integer");
      i.setLabel("i");
   }

   {
      addWidget("i", "i", i);
   }
}
...

Frame Dynamic Properties

Frames support changing of properties at runtime just like regular widgets, but beside that frames also support “dynamic properties”. Dynamic properties are callback expressions which are evaluated every time the frame is displayed (except DOWN, which is evaluated, but frame appearance changes only under specific circumstances, see below). At the present, FWD only supports dynamic tracking of the following properties: TITLE, ROW, COLUMN,and DOWN. The DOWN property, in addition to changing dynamically, can be handled automatically by FWD runtime in some cases. See the Automatic DOWN section of this chapter.

When a Progress 4GL application needs to change ROW, COLUMN, TITLE or DOWN property automatically, it uses an expression instead of a constant in declaring the appropriate frame property. The main distinction of such cases is that the application does not need to evaluate these expressions explicitly when frame needs these properties. Instead these expressions are evaluated “under the hood” every time the runtime needs the associated value. When the application changes the value of any state (e.g. variable or database field) involved in the dynamic property expression, the computed property will now have a different value. For all properties, except DOWN, this results to change of frame appearance every time when frame is displayed. DOWN property is a bit special - frame appearance is changed only when frame is hidden and then displayed again. The important point here is that the Java runtime (as in the 4GL) only evaluates the expression at certain points in the processing. When the expression is evaluated the latest value will be honored.

In order to duplicate this behavior, FWD handles dynamic properties in a special way. Since dynamic property expression evaluation happens far from the point where this expression is declared and expression may refer variables (or other state) which are visible only in a particular scope and are not known nor directly accessible by the UI runtime code, conversion generates a callback expression in the business logic class (see more details on callback expressions in Use of Callback Expressions In Widgets section) and registers this expression using the frame instance method dedicated to the associated property. Then the runtime uses these callbacks to obtain current values of the properties as necessary and as many times as necessary. Following example illustrates conversion of dynamic TITLE property, but conversion of other dynamic frame properties is identical (except use of different methods for callback registration):

4GL source code:

def var txt1      as char init "First".
def var txt2      as char init "Title".
def var i         as int.

form i with title txt1 + " " + txt2 width 20 frame fr.

display i with frame fr.
pause.
hide frame fr.

do i = 1 to 5:
   display i with title "Dynamic" + " " + "Title #" + string(i) + "!" frame fr.
   pause.
end.

The converted business logic code contains the following statements:

...
frFrame.openScope();
frFrame.setDynamicTitle(new CharacterExpression()
{
   public character execute()
   {
      return concat("Dynamic", " ", "Title #", valueOf(i), "!");
   }
});
...

In the example above, the title contains a complex expression which is converted into anonymous inner class which evaluates this expression. When the expression consists of a single variable instead of generation of (rather verbose) anonymous class, conversion passes the reference to the variable directly and the runtime wraps it into an internal class which implements the Resolvable interface:

4GL source code:

def var txt1      as char init "First".
def var txt2      as char init "Title".
def var i         as int.

form i with title txt1 width 20 frame fr.

display i with frame fr.

The title expression consists of single variable.

Converted business logic code (excerpt):

...
public void body()
{
   frFrame.openScope();
   frFrame.setDynamicTitle(txt1); // callback registration
   ...

   frFrame.display(elementList0); // callback evaluation happens here
}

In this case, the reference to the variable is passed directly. This does not include the case when the expression is a simple reference to a database field. Cases when the expression consists of single database field are converted by additional wrapping of the field into a new instance of the FieldReference class, just like this is done for other callback expressions:

define variable i as integer init 1.
def temp-table test no-undo field frm-title as char.

form "Hello!" i with frame f with no-labels title frm-title.
display i with frame f.

Converted business logic code (excerpt):

...
public void body()
{
   fFrame.openScope();
   fFrame.setDynamicTitle(new FieldReference(test, "frmTitle")); // callback registration
   ...

   fFrame.display(elementList0); // DB field value is requested here
}
...

As mentioned above, conversion of all dynamic properties is very similar except for the registration method used to register the callback with the frame instance. The following table summarizes these differences:

Property Callback Registration Method Notes
COLOR   Not supported at this time.
COLUMN CommonFrame.setColumn  
DOWN CommonFrame.setDown Appearance changes only after hiding and redisplaying frame.
ROW CommonFrame.setRow  
TITLE CommonFrame.setDynamicTitle  
Automatic DOWN

The DOWN property (beside explicit definition and dynamic change) can be calculated automatically. In this case runtime calculates DOWN value depending on the frame location, available screen space and iteration size. In order to enable automatic down calculation application keeps DOWN clause in frame declaration “open” (i.e. not specified explicitly). This can be done either by using DOWN clause without parameter or by omitting DOWN clause completely. Latter case works only if frame is automatically detected as DOWN frame during conversion (see more details about frame types in DOWN And Regular Frames section).

Consider following Progress 4GL code:

def var i as int.

do i = 1 to 8 with frame my-frame:
   display i with down no-labels frame my-frame.
end.

Note that DOWN property is not set explicitly, but left “open” instead. Conversion of this example produces no special code in business logic class. In static frame initialization class it is converted so:

...
public void setup(CommonFrame frame)
{
   frame.setDown(0);
   frame.setNoLabels(true);
   i.setDataType("integer");
   i.setLabel("i");
}
...

Note that DOWN property is set to 0. The actual DOWN value is determined automatically when frame is displayed at the screen. When frame is displayed its actual DOWN value can be obtained as any other regular frame or widget property.

Frame Properties Summary Table

All frame properties are accessible via appropriate CommonFrame interface methods. The setup(CommonFrame frame) method of the static frame initialization class calls frame instance methods listed below. These methods are all called on the single parameter of this setup() method, which is how each instance of the given frame class is initialized.

Progress 4GL Frame Phrase Element Frame Method Notes
ATTR-SPACE CommonFrame.setAttrSpace()  
BGCOLOR CommonFrame.setBgcolor()  
CANCEL-BUTTON CommonFrame.setCancelButton()  
CENTERED CommonFrame.setCentered()  
COLOR CommonFrame.setColor()  
COLUMN CommonFrame.setColumn()  
COLUMNS CommonFrame.setColumns()  
DCOLOR CommonFrame.setDcolor()  
DEFAULT-BUTTON CommonFrame.setDefaultButton()  
DOWN CommonFrame.setDown()  
FGCOLOR CommonFrame.setFgcolor()  
KEEP-TAB-ORDER CommonFrame.setKeepTabOrder()  
NO-ATTR-SPACE CommonFrame.setNoAttrSpace()  
NO-AUTO-VALIDATE CommonFrame.setNoAutoValidate()  
NO-BOX CommonFrame.setNoBox()  
NO-HELP CommonFrame.setNoHelp()  
NO-HIDE CommonFrame.setNoHide()  
NO-LABELS CommonFrame.setNoLabels()  
NO-UNDERLINE CommonFrame.setNoUnderline()  
NO-VALIDATE CommonFrame.setNoValidate()  
OVERLAY CommonFrame.setOverlay()  
PAGE-BOTTOM CommonFrame.setPageBottom()  
PAGE-TOP CommonFrame.setPageTop()  
PFCOLOR CommonFrame.setPfcolor()  
RETAIN CommonFrame.setRetain()  
ROW CommonFrame.setRow()  
SCROLL CommonFrame.setScroll()  
SCROLLABLE CommonFrame.setScrollable()  
SIDE-LABELS CommonFrame.setSideLabels()  
SIZE x BY y
SIZE-CHARS x BY y
CommonFrame.setWidth() + CommonFrame.setHeight()  
THREE-D CommonFrame.setThreeD  
TITLE CommonFrame.setTitle()  
TOP-ONLY CommonFrame.setTopOnly  
USE-TEXT - This property forces use of ControlTextWidget() instead of FillInWidget() by default
VIEW-AS DIALOG-BOX CommonFrame.setDialog()  
WIDTH CommonFrame.setWidth()  
X CommonFrame.setRow()  
Y CommonFrame.setColumn()  

Frame Runtime Support

At runtime frames are created at server side via call to static GenericFrame.createFrame() method with interface class as a parameter (see Complete Frame Conversion Sample below). This method dynamically creates a class that implements the frame definition interface and which has properly initialized the widget list, its widgets and frame options. This class is called a “dynamic proxy” in Java terms. It doesn't actually have a real “implementation”. Instead, it has an invocation handler method that is called whenever a method is invoked on that instance. That handler method then analyzes the method name/signature that is called and dispatches the call to backing code in the runtime. The returned proxy class handles all the common (CommonFrame) and interface-specific (the frame definition) methods. This class is also a sub-class of GenericFrame. The common methods are redirected to appropriate GenericFrame class methods, while methods specific to particular frame are redirected to GenericFrame.invoke() method. Note that these methods (along with the common property setters and getters) provide access to the server side widgets (including the frame widget itself) and their properties. Any changes in these properties are transparently passed to the client. Likewise, any changes done at the client during interaction with the user are transparently passed back to server.

Since frames and other widgets are entities known to both client and server sides of the application, FWD uses an automatically generated frame ID to identify frames. Each frame is allocated a block of 1000 IDs for itself and its widgets. The frame's own ID is always the first ID of the block (so it is always evenly divisible by 1000). The frame ID identifies the frame itself (since it also a widget), while remaining widgets get an ID counted as the frame ID plus the ordinal number of widget in the frame definition. The frame allocates a unique ID when it is instantiated first time and this ID remains unchanged until after the frame is removed from the frame registry at the server. Once the frame ID is released, it can be assigned to another frame. More details on this process are provided in Frame Scope And Life Time section.

Once the frame is created at the server (by the business logic), its definition (which consists of definition of all widgets including frame itself) is pushed to the client. When the frame is created from this definition at the client, the client and server may freely perform operations on the frame and its widgets. Note that frame at the server and at the client are completely different things and represented by different classes because they handle different parts of the frame behavior.

It should be noted, that the frame name has no hard binding between widgets and appropriate variables for which these widgets are created. During editing operations a temporary binding between widgets and variables is established and all valid edits are transparently passed back to appropriate variables.

Shared Frames

Like regular variables frames can be shared between procedures/code files. Note that when shared frame properties are changed in one procedure/code file, these changes survive when frame leaves its scope.

In Progress 4GL shared frames are defined using special form of DEFINE FRAME statement. In fact, the DEFINE FRAME statement is the only way to declare a shared frame.:

DEFINE [NEW] SHARED FRAME name ...(remaining part is identical to regular DEFINE FRAME statement)

The conversion of a shared frame mostly identical to a regular frame except, depending on the presence of NEW clause in business logic class frame instance is created using GenericFrame.createSharedFrame() (if NEW clause is present) or with GenericFrame.importSharedFrame().

The frame definition is created the same way, no matter whether the frame is shared or not.

At runtime a frame created with the NEW clause causes a new shared variable to be placed in the current scope. Then such a frame is processed like any other regular (non-shared) frame. Processing of frames without the NEW clause is more complicated: when the application requests importing of the shared frame, the instance of previously declared shared frame is looked up in the containing scopes using the frame name as it appears in original Progress 4GL source. If there is no such frame, then an ERROR condition is raised. If such a named frame is present, then the properties of found shared frame and visibility state of its widgets are copied into the imported shared frame. When the imported shared frame goes out of scope, its properties are copied back into the shared frame instance. This allows to mimic Progress 4GL behavior in shared frame handling.

In order to make this scheme function correctly it is necessary that definition of shared frames (new and imported) should match each other. Since properties of individual widgets (except visibility state) are not copied, there is no requirement that two frames must be identical by the means of the widgets and their types, but an attempt to use two completely unrelated frame definitions as new and imported frames may result to unexpected frame behavior. In order to avoid unexpected behavior it is suggested to at least have the same number of form header and other widgets between the two frame definitions. In other words, to be safe it is important to preserve the total number of widgets and proportion between form header and other widgets across new and imported shared frame definitions.

The following two examples illustrate conversion of the shared frames. First example illustrates definition of new shared frame (declaration01s.p):

define variable i as integer.
define new shared frame f i with no-labels.

run declaration01sc.p.

The frame interface and static frame initialization class contains nothing special, but business logic class is more interesting:

...
public class Declaration01s
{
   Declaration01sF fFrame = GenericFrame.createSharedFrame(Declaration01sF.class, "f");
...

Since frame is defined as NEW SHARED, new shared frame instance is created in this case. Following code illustrates how this shared frame can be used (declaration01sc.p):

define variable i as integer.
define shared frame f i with no-labels.

Attempt to execute this code directly will fail, because shared frame instance is undefined. But this code can be safely invoked as shown in previous example. In this case shared frame instance will be present at the moment when import of frame f will be requested. Business logic class for last example looks so (excerpt):

...
   Declaration01scF fFrame = GenericFrame.importSharedFrame(Declaration01scF.class, "f");
...

Note that definitions of both frames identical, including names of widgets. In practice, the names of the widgets do not have to match, but it is essential to have the number of widgets identical for reasons mentioned above.

Overlay Frames

Regular frames are displayed as tile frames (non-overlapping), so frames can't cover other frames. When a currently non-visible frame is displayed and there is not enough room on the screen to display it, some frames are hidden and new frame is displayed in the freed space. In cases when the frame should be displayed over other frames without hiding them, OVERLAY frames are used. These frames don't follow regular rules for frame placement and displaying an overlay frame does not trigger hiding of other frames. OVERLAY frame property is orthogonal to DOWN/1-DOWN so overlay frame can be DOWN and 1-DOWN just like usual tile frames.

DOWN And Regular Frames

As mentioned above, a frame can be one of two types - DOWN and 1-DOWN. The key difference between these types lies in the number of records (or instances of the variable) displayed inside frame a 1-DOWN frame is used to display a single record (or set of variables) while DOWN frames display several records. In other words DOWN frame can be considered a logical table which contains several identical rows. This difference between 1-DOWN and DOWN frames dictates many other differences, from layout handling to hiding/pausing behavior. The number of records which can be displayed in a DOWN frame can be set explicitly (e.g. 5 DOWN for a 5 record frame) or determined dynamically (e.g. just the option DOWN in the frame phrase with no qualifier) when frame is displayed on the screen the first time. The frame definition contains only one set of fields/variables regardless of its type.

The type of frame is determined during conversion implicitly from the type of the block to which the frame is scoped, unless the frame type is declared explicitly. Explicit declaration is done by the DOWN clause of the Frame Phrase.

A DOWN frame is usually scoped to some kind of iterating (loop) block, either implicitly or explicitly. This association is based on important key concepts of the DOWN frame - iteration and cursor.

Cursor and Iteration

An “iteration” represents one set of fields in a DOWN frame. The name “iteration” is ambiguous, but usually it is clear from context what kind of iteration (loop iteration or frame iteration) is meant.

The cursor points to the active iteration and can be moved up and down. The cursor can be moved down implicitly or explicitly, using DOWN statement. Implicit cursor movement happens when the block to which frame is scoped iterates (loops). A RETRY does not qualify as an iteration. The cursor can be moved up using the UP statement.

Therefore iteration and cursors provide a navigation mechanism which allows to reach each set of fields/variables displayed in the DOWN frame. When cursor reaches first or last iteration following attempt to move cursor in same direction as before causes wrapping - the cursor is moved to the last or first iteration respectively. Wrapping of the cursor when it is moved down in most cases triggers pausing when an attempt to display new content or clear content of the frame is made.

In converted code both explicit and implicit cursor movement methods are still present and it works just like in Progress 4GL the cursor can be moved explicitly using frame's up() and down() methods and implicitly called by the runtime during iterating block processing.

Frame Scope And Life Time

Frame scoping is a mechanism which binds an instance of a frame to a particular block. This binding then controls implicit behavior such as cursor movement. Usually a frame is scoped to the closest containing block of the following types: external or internal procedure, function, FOR, REPEAT or DO WITH FRAME in which the frame is displayed the first time using displaying statements or mentioned in a FORM statement. Simply mentioning a frame in other statements (for example, HIDE) or a frame declaration via DEFINE FRAME statement does not affect frame scoping. The only way to explicitly control frame scoping is the FORM statement (which only defines the frame, it does not cause the frame to be displayed).

In converted code, the block to which a frame is scoped is always explicitly defined. The business logic calls method GenericFrame.openScope() to register the frame in the current block. The scope lasts to the end of the execution of the Block instance in which this method is invoked. It is important to call GenericFrame.openScope() only once. In all cases when block execution can be repeated (any iterating blocks, for example) it is necessary to call this method in the init() method of the block in the business logic. The only exception is for any frame defined directly in the procedure's or function's body. In this case, conversion generates the call to Frame.openScope() at the beginning of the body() method. This approach is not recommended for manually written code because it may cause problems during further code modifications. Manually written code should always place the Frame.openScope() calls in the init() method. In the Progress 4GL, the frame can be scoped only once inside the compilation unit (Progress 4GL source file).

Example of frame scoping:

def var i as integer.
def var j as integer.

form j with frame f2.

repeat i = 1 to 10:
   display i with frame f1.

   j = i + 1.
   display j with frame f2.
end.

hide frame f1.

Frame f1 is scoped to the REPEAT block; frame f2 is scoped to the top level procedure because it is referenced in the FORM statement. Note that the HIDE statement references frame f1 outside of its scope. Converted code (excerpt):

externalProcedure(new Block()
{
   ...
   public void body()
   {
      f2Frame.openScope();

      ToClause toClause0 = new ToClause(i, 1, 10);
      repeatTo("loopLabel0", toClause0, new Block()
      {
         public void init()
         {
            f1Frame.openScope();
         }
         ...
      });
      f1Frame.hide();
   }
});

If a frame is referenced in an internal procedure or function as well as in the top level procedure or another internal procedure or function, Progress 4GL creates a separate frame for each procedure or function. Following source illustrates this process (scoping01.p):

procedure p0:
    display "Text 2" with frame f1.
end.

display "Text 1" with frame f1.
run p0.

As one can see, frame f1 should be scoped to procedure p0 and to top level procedure. Instead of doing so, Progress 4GL treats both frames as separate. Converted code for this procedure, provided below, shows resulting frame scoping (excerpt):

public class ProcFrame2
{
   ProcFrame2F1 f1Frame = GenericFrame.createFrame(ProcFrame2F1.class, "f1");

   ProcFrame2P0F1 p0F1Frame = GenericFrame.createFrame(ProcFrame2P0F1.class, "p0-f1");
...
   public void execute()
   {
      externalProcedure(new Block()
      {
         public void body()
         {
            f1Frame.openScope();
...
         }
      });
   }
...
   public void p0()
   {
      internalProcedure(new Block()
      {
         public void body()
         {
            p0F1Frame.openScope();
...
         }
      });
   }
}

The frames will get different names in the converted code to make frames distinct. Moreover, these frames are handled as two separate frames in all other regards too, i.e. they have independent definitions and may have completely distinct layout and type (DOWN or regular).

WARNING! Manually written or changed converted code does not set single scope limitation described above and it is possible to scope frame to more than one block. Although sometimes this might work as expected, this is not tested and behavior of the runtime is unpredictable.

When the displayed frame reaches the end of its scope it remains visible at the screen until it is hidden by explicit HIDE FRAME statement, implicitly by frame placement logic or by special frame hiding logic triggered by block scope processing. Just because a frame has exited its scope does not mean that frame is disposed. The frames properties can even still be changed so long as the frame is visible.

Referencing a frame in a HIDE as the first statement which references the frame, does not make the frame scoped to the appropriate block, although no errors/warnings are produced in this case. In other words, HIDE may reference a frame before the actual scope is opened and this is silently ignored.

Shared frame life time lasts from the point where it is declared with the NEW option (see more details in Shared Frames section) through all calls to other procedures and code files and up to the end of the procedure where it is declared. Scoping of a shared frame is the same as for a regular frame.

In converted code regular (non-shared) frame life time in the vast majority of cases is the same as life time of the class where this frame is declared. Nevertheless, since the frame may remain visible at the screen after the end of life time, run-time library contains dedicated mechanism which holds frame ID in a frame registry as long as necessary until the frame is actually hidden. When such a frame is actually hidden, then the server is notified that it may release frame and frame is actually removed from the registry.

Hiding/Pausing Rules

When frame is scoped to the iterating (i.e. looping) block (for example FOR EACH or counting DO block), it is subject of automatic hiding process. Basically algorithm of automatic hiding looks so: at the end of each iteration block all frames scoped to current and nested scopes are marked for hiding unless they have NO-HIDE property set. When new iteration starts at current or outer scope, first displayed frame hides all frames marked for hiding. If any of these frames has changed content and there were no pause or interactive event processing loop after content change, then automatic pause is triggered. After end of the pause (usually after pressing some key by user) all frames marked for hiding are actually removed from the screen.

This algorithm has numerous exclusions and special cases. In particular, if block iterates due to RETRY processing, pausing is suppressed for frames at current scope (but regular pausing processing is performed for frames marked for hiding at nested scopes); not every frame displayed in current scope is allowed to hide other frames; hiding is processed as described if less than two frames displayed in inner scopes and so on and so forth. In other words, hiding algorithm in some cases may not work as described above and not always produces results application writer expects. In such a cases it is suggested to use explicit frame visibility management with HIDE statement. Note that pausing still works as described above even for explicit HIDE. If this is not what is required, then NO-PAUSE option will suppress default pause processing.

More details about pausing caused by DOWN frame iteration processing can be found in the Cursor And Iteration section above.

Frame Layout

The layout of each frame in the Progress 4GL is handled automatically and dynamically. It is based on the order in which widgets are added to the frame, as well as the widget type and properties for each widget. In addition, the SKIP and SPACE pseudo-widgets can be used to control layout processing. As mentioned earlier in this chapter, conversion collects widgets for each frame in all statements where references to widgets may appear. This process retains the order in which widgets are added to a frame.

The frame layout generation basic algorithm is rather simple: widgets are placed from left to right, one by one until the right frame edge is reached or there are no more widgets to place. Placed widgets form a stripe which has the height of the tallest widget in that stripe. If there are more widgets left unplaced then a new stripe is started right below the previous one. The SKIP and SPACE pseudo-widget, widgets with an explicitly set column and widgets with alignment flags may interrupt preparing the current stripe and force the start of new stripe. The general rule: the start of a new stripe is forced when the calculated column for the currently processed widget is less than the rightmost column occupied by the previous widget or the right edge of the currently processed widget crosses the right edge of the frame. In some cases widgets with explicitly specified ROW also may force start of the new stripe. This happens when explicitly specified row does not match current stripe row. Depending on the frame properties, the algorithm described above can be applied in different ways. Different cases are described below.

1-DOWN, One Column Frame Layout

This is the simplest case which completely follows basic algorithm. The example provided below shows a simple form with top (default) labels.

The first example presents the simplest possible case, when all layout is generated automatically (layout00.p):

define variable i as integer label "Integer variable" init 123.
define variable s as char    label "String"           init "text".

display i s.

The frame layout produced by this example is provided below:

 ┌─────────────────────────┐
 │Integer variable String  │
 │──────────────── ────────│
 │             123 text    │
 └─────────────────────────┘

As one can see, each widget is split into two parts - the widget label at the top and the widget itself at the bottom - separated by a horizontal line. Total width of space occupied by the widget is determined as the largest of the label width and widget itself. Different widgets are sized in similar way - widget size is determined as follows:

Width is set to width calculated from format string and width of additional decorations. Exceptions from this rule are EDITOR widget, which must have width set explicitly in the VIEW-AS phrase; BROWSE widget, which has width set as width of included columns and decorations; RADIO-SET widget, which, depending on the direction, can be either sum of widths of all items (+ item decorations) for horizontal RADIO-SET or width of widest item (+item decorations) for vertical RADIO-SET. Width of decorations also depends on type of the widget: COMBO-BOX widget uses 4 additional characters for “ [V]” string; RADIO-SET uses 3 additional spaces for “( )” (not selected option) or “(X)” (selected option) string for each item; BROWSE uses 2 additional drawing positions for border (if no NO-BOX property is set) and numberOfColumns-1 additional drawing positions for column separators (unless NO-SEPARATORS property is set). Note that if frame has no NO-LABELS nor SIDE-LABELS properties set, then horizontal space occupied by widget in frame layout is determined as the maximum of the label width and widget width. Also, note that label width does not affect actual widget width (i.e. how many characters can be displayed inside the widget).

Widget height is set to 1 for all widgets except following ones: EDITOR widget must have its height set explicitly in the VIEW-AS phrase, RADIO-SET widget has height set to 1 for horizontal and number of items for vertical mode; BROWSE widget has height 1 + decorations (border, column labels).

The rules above are used only if widget width and/or height are not set explicitly.

The next example illustrates two things: how layout generation can be controlled with the SKIP pseudo-widget as well as the so called stacked layout mode (discussed in more details in the Stacked Layout Mode section below).

define variable i as integer label "Integer variable" init 123.
define variable s as char    label "String"           init "text".

display i skip s.

The frame layout produced by this example is provided below:

 ┌─────────────────┐
 │Integer variable │
 │String           │
 │─────────────────│
 │             123 │
 │text             │
 └─────────────────┘

The SKIP pseudo-widget breaks the stripe and the second widget is placed below the first one. Due to the stacked mode, all widget labels are grouped at the top and all widgets are grouped at the bottom with a single continuous separator between labels and widgets.

The next example shows effect of use of SKIP(n).

define variable i as integer label "Integer variable" init 123.
define variable s as char    label "String"           init "text".

display i skip(2) s.

Basically, the layout is very similar to that shown above, but now there are two empty rows between widgets. Note that there are no empty rows between labels:

 ┌─────────────────┐
 │Integer variable │
 │String           │
 │─────────────────│
 │             123 │
 │                 │
 │                 │
 │text             │
 └─────────────────┘

The next example demonstrates the use of the SIDE-LABELS frame property:

define variable i as integer label "Integer variable" init 12345678.
define variable s as char    label "String"           init "--text--".

display i s with side-labels.

Labels are now displayed at the left side of the widget and are separated from widgets with colon and space characters:

 ┌─────────────────────────────────────────────┐
 │Integer variable: 12,345,678 String: --text--│
 └─────────────────────────────────────────────┘

Now, the space occupied by the widget is calculated as the sum of the widget width and label length (plus some spacing between the label and widget). Label alignment options may also affect the calculations (see Label Alignment section below).

The next example shows effect of using SKIP when the SIDE-LABELS property is enabled:

define variable i as integer label "Integer variable" init 12345678.
define variable s as char    label "String"           init "--text--".

display i skip s with side-labels.

The resulting layout is shown below:

 ┌────────────────────────────┐
 │Integer variable: 12,345,678│
 │String: --text--            │
 └────────────────────────────┘

Stacked layout mode can't be triggered when SIDE-LABELS is enabled, so each widget is still placed with its label.

Stacked Layout Mode

Usually when no SIDE-LABELS property is set, each widget gets its own label separated from the widget itself dedicated separator line. In stacked mode all widget labels are collected together in the top part of the frame and placed above common separator. In other words, in stacked mode frame is split into two parts by the separator - all labels are placed in the top part and all widgets are place in the bottom part.

The complete set of rules which trigger stacked layout mode is rather complex, but in general it can be narrowed to two rules: a) presence of top widget labels and b) presence of some kind of “wrapping” (i.e. more than one stripe in layout). More than one stripe can be either caused by presence of SKIP in layout or when number and size of widgets just don't fit into one stripe.

Frame headers do not affect triggering to stacked layout mode, however they may trigger the mode which looks similar to stacked layout mode. You can read about it in the “Frame Headers” section.

define variable i as integer label "Integer variable" init 123.
define variable s as char    label "String"           init "text".

display i skip s.

The frame layout produced by this example is provided below:

 ┌─────────────────┐
 │Integer variable │
 │String           │
 │─────────────────│ ← note common separator
 │             123 │
 │text             │
 └─────────────────┘

All labels now are above the widgets itself and horizontal separator between widgets and labels is contiguous.

Frame Headers

Frame headers are set using HEADER clause in the frame definition and consist of common headers for the frame. Frame headers are placed above all labels or instead of them if NO-LABELS property is set. In latter case entire frame layout is similar to stacked layout mode (i.e. single continuous separator between frame headers and widgets). Frame headers can be simple literals or more complex expressions. Formatting and placement of the widgets created for the form headers are very similar to widgets created for regular widgets with two differences: header widgets have no their own labels and in some cases there is no default space between header widgets (see below).

Following example illustrates simple case of frame headers:

define variable i as integer init 1.
define variable j as integer init 12.
define variable k as integer init 123.
define variable l as integer init 1234.
form "Hello!" i j k header l "Another header" with frame f.
display i j k l with frame f.

This resulting frame layout is shown below:

 ┌───────────────────────────────────────┐ ← frame headers are above all labels
 │     1,234Another header               │ ← no spacing between header widgets
 │Hello!          i          j          k│ ← remaining layout generated as usual
 │       ────────── ────────── ──────────│
 │                1         12        123│
 └───────────────────────────────────────┘

Note that when frame headers and regular labels are used together, then frame headers are placed without spaces.

Next example illustrates use of frame headers with NO-LABELS property set:

define variable i as integer init 1.
define variable j as integer init 12.
define variable k as integer init 123.
define variable l as integer init 1234.
form "Hello!" i j k header l "Another header" with frame f with no-labels.
display i j k l with frame f.

Resulting layout looks so:

 ┌───────────────────────────────────────┐
 │     1,234 Another header              │ ← spacing between widgets is present
 │───────────────────────────────────────│ ← single continuous separator
 │Hello!          1         12        123│
 └───────────────────────────────────────┘

As one can see, resulting layout is very similar to stacked mode layouts.

Label Sizing and Placement

Widget labels are included into layout if no NO-LABELS property is set (either for the entire frame or for particular widget). There is an exception from this rule - explicitly set label displayed even if NO-LABELS property is set for the frame.

Label is placed above widget unless SIDE-LABELS property is set for the frame. If this property is set, then label is displayed at the left side of the widget and between label and widget inserted “: ” string.

In previous and following section provided several examples which demonstrate widget placement, so following example illustrates only case which is not covered by these examples - displaying of the explicitly set label when frame has NO-LABELS property set:

define variable i as integer init 1.
define variable j as integer init 12.

display i j label "Widget label" with no-labels.

Resulting layout looks so:

 ┌───────────────────────┐
 │           Widget label│ ← note that labels is present
 │           ────────────│
 │         1           12│
 └───────────────────────┘
Label Alignment

Progress 4GL has several kinds of syntax for label alignment options, but in the end they represent only three possible kinds of label alignment - left alignment, right alignment and colon alignment. The following example shows how labels can be aligned:

def var i as int init "12345678" label "One".
def var j as int init "87654321" label "Two".
def var k as int init "12345678" label "Three".
def var l as int init "87654321" label "Four".
def var m as int init "12345678" label "Five".

display i at row 1 column 7 colon-aligned
        j at row 2 column 7 colon-aligned
        k at row 3 column 7 left-aligned
        l at row 4 column 7 right-aligned
        m colon 7
        with side-labels.

Widget m uses an alternative syntax for colon alignment. The resulting layout is provided below:

 ┌───────────────────────┐
 │   One: 12,345,678     │
 │   Two: 87,654,321     │
 │      Three: 12,345,678│
 │Four: 87,654,321       │
 │  Five: 12,345,678     │
 └───────────────────────┘

The first two widgets and the last widget have the same type of alignment and the colon character between the label and the widget is located at the same column for all these widgets. The third and fourth widgets show alignment to the left and to the right edges of the field respectively. The specified column for all five widgets are the same.

1-DOWN, Multiple Columns Frame Layout

When the COLUMNS property is specified, the frame uses the special multiple column layout even if the number of columns is set to 1. Although this layout has several differences, basically it follows the same general layout algorithm described at the beginning of the Frame Layout section. There are two main differences. The first is that in this mode the SIDE-LABELS property is implicitly set to true. The second difference is that all widgets are placed at “tab stops” dictated by the COLUMNS property value, screen width and the actual number of widgets. The following example shows some possible combinations of these parameters and the resulting layouts:

def var a as int init 12345678 label "12345678901234567890".
def var b as char init "12345678" label "12345678901234567890".
def var c as char init "12345678" label "12345678901234567890".
def var a1 as int init 12345678 label "12345678901234567890".
def var b1 as char init "12345678" label "12345678901234567890".
def var c1 as char init "12345678" label "12345678901234567890".
def var a2 as int init 12345678 label "12345678901234567890".
def var b2 as char init "12345678" label "12345678901234567890".
def var c2 as char init "12345678" label "12345678901234567890".

display a   with frame f11 1 columns.

display a b c a1 b1 c1  with frame f12 1 columns.

display a   with frame f21 2 columns.
display a b with frame f22 2 columns.

display a b   with frame f31 3 columns.
display a b c with frame f32 3 columns.

The example above produces following frame layouts:

 ┌────────────────────────────┐ ← single widget in single column
 │1234567890123456: 12,345,678│
 └────────────────────────────┘
 ┌────────────────────────────┐ ← multiple widgets in single column, note that they are wrapped
 │1234567890123456: 12,345,678│
 │1234567890123456: 12345678  │
 │1234567890123456: 12345678  │
 │1234567890123456: 12,345,678│
 │1234567890123456: 12345678  │
 │1234567890123456: 12345678  │
 └────────────────────────────┘
 ┌──────────────────────────┐ ← single widget in 2 columns
 │12345678901234: 12,345,678│
 └──────────────────────────┘
 ┌───────────────────────────────────────────────────────────────┐ ← 2 widgets in 2 columns
 │12345678901234: 12,345,678             12345678901234: 12345678│
 └───────────────────────────────────────────────────────────────┘
 ┌────────────────────────────────────────────────┐ ← 2 widgets in 3 columns frame
 │123456789012: 12,345,678  123456789012: 12345678│
 └────────────────────────────────────────────────┘
 ┌─────────────────────────────────────────────────────────────────────────┐ ← 3 widgets in 3 columns
 │123456789012: 12,345,678  123456789012: 12345678   123456789012: 12345678│
 └─────────────────────────────────────────────────────────────────────────┘

The third and fourth (as well as the fifth and sixth) frames have the same number of columns specified in frame properties, but the actual number of widgets is less than the number of columns and this results in a different layout. The frame with 2 widgets and 3 columns has different layout from frame with 2 widgets and 2 columns.

DOWN Frame Layout

DOWN frame layout basically can be considered as 1-DOWN frame layout repeated multiple (the number of iterations) times. By default, the repeated part of the layout is where widgets are placed (the body of the frame) while the labels remain common for all iterations. But for SIDE-LABELS frames labels are repeated as well.

Following example produces one of the simplest cases of DOWN frame layout:

def var i as int.
def var j as int init 2.

repeat i = 1 to 4:
   display i j.
end.

Resulting layout looks so:

 ┌─────────────────────┐
 │         i          j│
 │────────── ──────────│
 │         1          2│
 │         2          2│
 │         3          2│
 │         4          2│
 │                     │
 │                     │
 │                     │
 │                     │
 │                     │
 │                     │
 │                     │
 │                     │
 │                     │
 │                     │
 │                     │
 │                     │
 │                     │
 └─────────────────────┘

The provided layout depends on screen height because the DOWN property is calculated dynamically depending on the frame location and available space. Layout shown above is produced for standard 80x24 terminal window. For more details refer to Automatic DOWN Calculation section below.

Next example uses SIDE-LABELS in frame, otherwise it is identical to example provided above:

def var i as int.
def var j as int init 2.

repeat i = 1 to 4:
   display i j with side-labels.
end.

Frame layout produced for this example provided below:

 ┌───────────────────────────┐
 │i: 1          j: 2         │
 │i: 2          j: 2         │
 │i: 3          j: 2         │
 │i: 4          j: 2         │
 │                           │
 │                           │
 │                           │
 │                           │
 │                           │
 │                           │
 │                           │
 │                           │
 │                           │
 │                           │
 │                           │
 │                           │
 │                           │
 │                           │
 │                           │
 └───────────────────────────┘

Procedure complete. Press space bar to continue.

In the example shown above one iteration uses exactly one row, but there is no such restriction and the iteration may have any height as dictated by layout. The following example illustrates this case:

def var i as int label "first".
def var j as char init "text" label "second".

repeat i = 1 to 4:
   display i skip j.
end.

The resulting layout is shown below:

 ┌──────────┐
 │     first│
 │second    │
 │──────────│
 │         1│
 │text      │
 │         2│
 │text      │
 │         3│
 │text      │
 │         4│
 │text      │
 │          │
 │          │
 │          │
 │          │
 │          │
 │          │
 │          │
 │          │
 └──────────┘
Frame Layout And Terminal Width

As mentioned above, in frame layout handling, the terminal (screen) width has a significant role. When the frame width is not specified explicitly, the maximum width of a frame can't exceed the terminal width. By default for the interactive terminal, frame width is set to the actual width of the terminal window and for redirected output the frame width defaults to 128.

ATTR-SPACE Handling

Frames and widgets may have the ATTR-SPACE property which, in general, adds some extra spacing between widgets and to the right and left edges of the frame.

If a frame with top labels has a single stripe per iteration (non-stacked layout mode) or it is a frame with NO-LABELS property, then the widgets with ATTR-SPACE property have the following spacing: the leftmost widget in the frame has one extra space before and one after it, other widgets have an extra space after them. Enabling ATTR-SPACE property for such frame has an effect similar to enabling it for all widgets. Example:

def var i as int label "first".
def var j as int label "second".
def var k as int label "third".

display i attr-space j k with frame f1.
display i j attr-space k with frame f2.
display i j k with frame f3 attr-space.

The frame layout produced by this example is provided below:

 ┌──────────────────────────────────┐
 │      first      second      third│
 │ ──────────  ────────── ──────────│ ← extra spaces for the first widget
 │          0           0          0│
 └──────────────────────────────────┘
 ┌─────────────────────────────────┐
 │     first     second       third│
 │────────── ──────────  ──────────│ ← extra space for the second widget
 │         0          0           0│
 └─────────────────────────────────┘
 ┌────────────────────────────────────┐
 │      first      second       third │
 │ ──────────  ──────────  ────────── │ ← extra spaces caused by ATTR-SPACE applied to the frame
 │          0           0           0 │
 └────────────────────────────────────┘

If a frame with top labels has multiple stripes per iteration (stacked layout mode), then the widgets with ATTR-SPACE property have the following spacing: the right-edge widgets are not affected, other widgets have an extra space after them. Enabling ATTR-SPACE property for such frame has an effect similar to enabling it for all widgets and adding extra spaces to the right and left edges of the frame. Example:

def var i as int label "first".
def var j as int label "second".
def var k as int label "third".
def var m as int label "fourth".

display i attr-space j attr-space skip k m with frame f1.
display i j skip k m with frame f2 attr-space.

The frame layout produced by this example is provided below:

 ┌──────────────────────┐
 │     first      second│ ← extra spaces for the first widget, second widget has no extra spaces
 │     third     fourth │   despite that it has ATTR-SPACE property
 |──────────────────────│
 │         0           0│
 │         0          0 │
 └──────────────────────┘
 ┌────────────────────────┐
 │      first      second │ ← extra spaces caused by ATTR-SPACE applied to the frame
 │      third      fourth │
 │────────────────────────│
 │          0           0 │
 │          0           0 │
 └────────────────────────┘

If a frame has side labels, then the widgets with ATTR-SPACE property have the following spacing: the right-edge widgets are not affected, other widgets have an extra space after them. Enabling ATTR-SPACE property for a such frame has an effect similar to enabling it for all widgets and adding an extra space to the right edge of the frame. Example:

def var i as int label "first".
def var j as int label "second".
def var k as int label "third".

display i attr-space j k attr-space with frame f1 side-labels.
display i j k with frame f2 side-labels attr-space.

The frame layout produced by this example is provided below:

 ┌─────────────────────────────────┐
 │first: 12345678  second: 12345678│ ← extra spaces for the first widget, second widget has no extra
 └─────────────────────────────────┘   spaces despite that it has ATTR-SPACE property
 ┌──────────────────────────────────┐
 │first: 12345678  second: 12345678 │  ← extra spaces caused by ATTR-SPACE applied to the frame
 └──────────────────────────────────┘
SKIP Pseudo-widget

The SKIP pseudo-widget is important for layout handling. Usually it is used to break the current stripe and start the new one. The optional SKIP argument defines number of new lines should be inserted after widget.

Following example illustrates use of SKIP widget:

define variable i as integer init 12.
define variable j as integer init 34.

display i skip j with side-labels frame f.

Resulting layout consists of two “strips”:

 ┌─────────────┐
 │i: 12        │
 │j: 34        │
 └─────────────┘

With no explicitly set value SKIP has no effect if it is the last widget in the stripe:

def var i as int label "first".
def var j as char init "text" label "second".

display i j skip.

The resulting layout looks as if there were no SKIP in the frame layout:

 ┌───────────────────┐
 │     first second  │
 │────────── ────────│
 │         0 text    │
 └───────────────────┘

This is not the case when the SKIP has a non-default value:

def var i as int label "first".
def var j as char init "text" label "second".

display i j skip(1).

The resulting layout has an extra empty row at the bottom:

 ┌───────────────────┐
 │     first second  │
 │────────── ────────│
 │         0 text    │
 │                   │
 └───────────────────┘

In other words, it should be kept in mind that SKIP without parameters in some cases behaves differently than SKIP(1). It might be somewhat counter-intuitive, because one may expect that SKIP without parameters is equivalent to SKIP(1).

SPACE Pseudo-widget

This widget is used to insert arbitrary number of spaces between widgets. The following simple example illustrates use of SPACE pseudo-widget:

define variable i as integer label "Integer variable" init 123.
define variable s as char    label "String"           init "text".

display i space(2) s.

Widgets are laid out with exactly two spaces between them:

 ┌──────────────────────────┐
 │Integer variable  String  │
 │────────────────  ────────│
 │             123  text    │
 └──────────────────────────┘

In this case, the default spacing is ignored, otherwise there would be only one space between widgets. The SPACE without parameters is equivalent to SPACE(1) and produces exactly one space between widgets, identical to default spacing.

Although SPACE is a pseudo-widget because it has no visible representation, the resulting space is not just empty space and this space can't be used to place another widget. Following example illustrates this:

define variable i as integer label "Integer variable" init 123.
define variable j as integer label "Integer variable" init 456.
define variable s as char    label "String"           init "text".

display i skip j space(2) s at 18.

Without space(2) expression, the widget s should be placed at column 18, but there is a SPACE widget and this triggers start of new stripe:

 ┌─────────────────────────┐
 │Integer variable         │
 │Integer variable         │
 │                 String  │
 │─────────────────────────│
 │             123         │
 │             456         │
 │                 text    │
 └─────────────────────────┘
Column Labels

In addition to a regular label, a widget may have a column label. Column labels are handled like regular labels, but if a widget has both, the column label takes precedence unless SIDE-LABELS property is set for the frame. The following example illustrates this:

def var i as int label "first".
def var j as char init "text" label "second".

display i j column-label "hello!my!world".

Note that the column label text has an embedded exclamation mark. It is used to define a label which will have more than one row. The frame layout created by this example follows:

 ┌───────────────────┐
 │           hello   │
 │           my      │
 │     first world   │
 │────────── ────────│
 │         0 text    │
 └───────────────────┘

Widget j has both, label and column label, but the column label is displayed.

If it is necessary to include an exclamation mark into label text, then it should be included twice:

def var i as int label "first".
def var j as char init "text" label "second".

display i j column-label "Extra!!Label".

Resulting layout provided below:

 ┌──────────────────────┐
 │     first Extra!Label│
 │────────── ───────────│
 │         0 text       │
 └──────────────────────┘

The exclamation mark now is part of the label.

NO-BOX Frames

Sometimes it is necessary to have frames without a border around them. This is especially necessary for report generation. This can be achieved by specifying the NO-BOX property for the frame:

def var i as int label "first".
def var j as char init "text" label "second".

display i j with no-box.

In this case the frame has the following layout:

     first second
────────── ────────
         0 text
Automatic DOWN Calculation

As mentioned above, a DOWN frame which has no explicitly or dynamically set DOWN property value calculates its DOWN value automatically when the frame is displayed or redisplayed (i.e. value is calculated when frame is displayed first time and when displayed frame has become hidden and then displayed again). The actual DOWN value for such frames depends on the row at which the frame is placed (either, explicitly specified or chosen dynamically during frame placement), the size of decoration elements (border, labels, delimiter between labels and widgets) and the height of the layout of the widgets of one iteration (“stripe” above). The following example demonstrates this behavior:

def var i as int label "first".
def var j as char init "text" label "second".

repeat i = 1 to 4 with frame f0 title "Automatic DOWN":
   display i j.
end.

hide frame f0.

display i j with frame f1 title "Another frame".

view frame f0.

The hide frame f0 statement triggers pausing logic so, before the frame will be actually hidden, a pause prompt is displayed:

 ┌──Automatic DOWN───┐
 │     first second  │
 │────────── ────────│
 │         1 text    │
 │         2 text    │
 │         3 text    │
 │         4 text    │
 │                   │
 │                   │
 │                   │
 │                   │
 │                   │
 │                   │
 │                   │
 │                   │
 │                   │
 │                   │
 │                   │
 │                   │
 │                   │
 └───────────────────┘

Press space bar to continue.

After pressing space bar screen looks so:

 ┌───Another frame───┐
 │     first second  │
 │────────── ────────│
 │         5 text    │
 └───────────────────┘
 ┌──Automatic DOWN───┐
 │     first second  │
 │────────── ────────│
 │                   │
 │                   │
 │                   │
 │                   │
 │                   │
 │                   │
 │                   │
 │                   │
 │                   │
 │                   │
 │                   │
 │                   │
 └───────────────────┘

Press space bar to continue.

Since frame f1 consumed some space on the screen (leaving some space below it), frame f0 is displayed below it and now has smaller height (and therefore DOWN property value).

Complete Frame Conversion Sample

Following example illustrates conversion of small Progress 4GL program (complete01.p):

def var i as int.
def var j as int.
def var k as int.

repeat k = 1 to 2:
    form with frame f0.

    repeat i = 1 to 2:

        display "F0" i j k with side-labels frame f0.
        pause.

        repeat j = 1 to 2 with side-labels frame f1:
            display "F1" i j k.
        end.
    end.
end.

Java code obtained by converting example provided above contains three files - business logic Complete01.java and two frames Complete01F0.java and Complete01F1.java.

Complete01F0.java:

import com.goldencode.p2j.util.*;
import com.goldencode.p2j.ui.*;

public interface Complete01F0
extends CommonFrame
{
   public static final Class configClass = Complete01F0Def.class;

   public void setExpr1(character parm);

   public void setExpr1(String parm);

   public void setExpr1(BaseDataType parm);

   public ControlTextWidget widgetExpr1();

   public integer getI();

   public void setI(integer parm);

   public void setI(int parm);

   public void setI(BaseDataType parm);

   public FillInWidget widgeti();

   public integer getJ();

   public void setJ(integer parm);

   public void setJ(int parm);

   public void setJ(BaseDataType parm);

   public FillInWidget widgetj();

   public integer getK();

   public void setK(integer parm);

   public void setK(int parm);

   public void setK(BaseDataType parm);

   public FillInWidget widgetk();

   public static class Complete01F0Def
   extends WidgetList
   {
      ControlTextWidget expr1 = new ControlTextWidget();

      FillInWidget i = new FillInWidget();

      FillInWidget j = new FillInWidget();

      FillInWidget k = new FillInWidget();

      public void setup(CommonFrame frame)
      {
         frame.setDown(1);
         frame.setSideLabels(true);
         expr1.setDataType("character");
         expr1.setFormat("x(2)");
         i.setDataType("integer");
         j.setDataType("integer");
         k.setDataType("integer");
         j.setLabel("j");
         k.setLabel("k");
         i.setLabel("i");
      }

      {
         addWidget("expr1", "", expr1);
         addWidget("i", "i", i);
         addWidget("j", "j", j);
         addWidget("k", "k", k);
      }
   }
}

For each widget, a getter, setters and accessor are generated. For convenience several forms of setter are generated for each widget created for integer variable: one which accepts int argument, one which accepts integer argument and one which accepts BaseDataType argument. At run-time these methods transparently handle conversion of passed argument to widget data type.

Complete01F1.java:

import com.goldencode.p2j.util.*;
import com.goldencode.p2j.ui.*;

public interface Complete01F1
extends CommonFrame
{
   public static final Class configClass = Complete01F1Def.class;

   public void setExpr5(character parm);

   public void setExpr5(String parm);

   public void setExpr5(BaseDataType parm);

   public ControlTextWidget widgetExpr5();

   public integer getI();

   public void setI(integer parm);

   public void setI(int parm);

   public void setI(BaseDataType parm);

   public FillInWidget widgeti();

   public integer getJ();

   public void setJ(integer parm);

   public void setJ(int parm);

   public void setJ(BaseDataType parm);

   public FillInWidget widgetj();

   public integer getK();

   public void setK(integer parm);

   public void setK(int parm);

   public void setK(BaseDataType parm);

   public FillInWidget widgetk();

   public static class Complete01F1Def
   extends WidgetList
   {
      ControlTextWidget expr5 = new ControlTextWidget();

      FillInWidget i = new FillInWidget();

      FillInWidget j = new FillInWidget();

      FillInWidget k = new FillInWidget();

      public void setup(CommonFrame frame)
      {
         frame.setDown(0);
         frame.setSideLabels(true);
         expr5.setDataType("character");
         expr5.setFormat("x(2)");
         i.setDataType("integer");
         j.setDataType("integer");
         k.setDataType("integer");
         j.setLabel("j");
         k.setLabel("k");
         i.setLabel("i");
      }

      {
         addWidget("expr5", "", expr5);
         addWidget("i", "i", i);
         addWidget("j", "j", j);
         addWidget("k", "k", k);
      }
   }
}

Beside obvious differences in naming it is worth to note that this frame has an “open” DOWN property value. That means the down value will be implicitly calculated by the runtime, at runtime.

Complete01.java:

import com.goldencode.p2j.util.*;
import com.goldencode.p2j.ui.*;

import static com.goldencode.p2j.util.BlockManager.*;
import static com.goldencode.p2j.ui.LogicalTerminal.*;

/**
 * Business logic (converted to Java from the 4GL source code
 * in _book/complete01.p).
 */
public class Complete01
{
   Complete01F1 f1Frame = GenericFrame.createFrame(Complete01F1.class, "f1");

   Complete01F0 f0Frame = GenericFrame.createFrame(Complete01F0.class, "f0");

   /**
    * External procedure (converted to Java from the 4GL source code
    * in _book/complete01.p).
    */
   public void execute()
   {
      externalProcedure(new Block()
      {
         integer i = new integer(0);

         integer j = new integer(0);

         integer k = new integer(0);

         public void init()
         {
            TransactionManager.register(i, j, k);
         }

         public void body()
         {
            ToClause toClause0 = new ToClause(k, 1, 2);

            repeatTo("loopLabel0", toClause0, new Block()
            {
               public void init()
               {
                  f0Frame.openScope();
               }

               public void body()
               {
                  ToClause toClause1 = new ToClause(i, 1, 2);

                  repeatTo("loopLabel1", toClause1, new Block()
                  {
                     public void body()
                     {
                        FrameElement[] elementList0 = new FrameElement[]
                        {
                           new Element("F0", f0Frame.widgetExpr1()),
                           new Element(i, f0Frame.widgeti()),
                           new Element(j, f0Frame.widgetj()),
                           new Element(k, f0Frame.widgetk())
                        };

                        f0Frame.display(elementList0);

                        pause();

                        ToClause toClause2 = new ToClause(j, 1, 2);

                        repeatTo("loopLabel2", toClause2, new Block()
                        {
                           public void init()
                           {
                              f1Frame.openScope();
                           }

                           public void body()
                           {
                              FrameElement[] elementList1 = new FrameElement[]
                              {
                                 new Element("F1", f1Frame.widgetExpr5()),
                                 new Element(i, f1Frame.widgeti()),
                                 new Element(j, f1Frame.widgetj()),
                                 new Element(k, f1Frame.widgetk())
                              };

                              f1Frame.display(elementList1);
                           }
                        });
                     }
                  });
               }
            });
         }
      });
   }
}

Note that frame f0 is scoped to outermost REPEAT loop block by using explicit FORM statement. Frame f1 illustrated implicit scoping behavior - frame is scoped to the closest REPEAT loop block. In the Java code, both scopes are explicitly specified using openScope() on the frame.


© 2004-2017 Golden Code Development Corporation. ALL RIGHTS RESERVED.