A Dual-UI Constraint Equation Solver in C++

Larry creates a pair of constraint-equation solvers, one based on the InterViews GUI toolkit for the X Window System and the other driven by a tty interface. Both UIs are connected to a common equation-solving back end. Mark Linton adds an overview of the upcoming Fresco toolkit--the successor to InterViews.


June 01, 1994
URL:http://www.drdobbs.com/cpp/a-dual-ui-constraint-equation-solver-in/184409252

Figure 1


Copyright © 1994, Dr. Dobb's Journal

Figure 2


Copyright © 1994, Dr. Dobb's Journal

Figure 1


Copyright © 1994, Dr. Dobb's Journal

Figure 2


Copyright © 1994, Dr. Dobb's Journal

JUN94: A Dual-UI Constraint Equation Solver in C++

A Dual-UI Constraint Equation Solver in C++

When two UIs are better than one

Larry Medwin

Larry is the director of engineering at Advanced NMR Systems Inc., in Wilmington, MA. He is currently responsible for the development of an MRI (Magnetic Resonance Imaging) scanner dedicated to mammography. He can be reached on the Internet at !uunet!thehulk!medwin!larry.

Irecently implemented a set of C++ programs to solve constraint equations, including a program that uses the InterViews GUI toolkit for the X Window System. Designing a constraint-equation solver turned out to be an interesting problem, both algorithmically and in terms of its user interface.

Algorithmically, it's interesting because equations are normally programmed to be solved in one direction--one is always solving for a particular unknown, and the rest of the variables are always given. But many systems are modeled in terms of mathematical relationships among many quantities. In such systems, any one value can be found if all the rest are known. You can represent the relationships among the variables and operators of a linear algebraic equation by using what's known as a "constraint network." With this structure (and appropriate algorithms), you can compute the value of any one variable automatically as soon as the values of all other variables are given.

In terms of the user interface, I found it a design challenge to present a clear and simple model of program functionality to the user via a GUI. I also used a dual-mode architecture, implementing two versions: one using a GUI and the other driven by a tty interface (simple commands read from stdio). Both are connected to a common, equation-solving back end.

Although the constraint solver is a relatively small system (about 1500 lines of code), the design approach used should allow scaling up to more powerful systems. This article describes the implementation of the program, starting with the dual-mode architecture.

A Dual-UI Architecture

The two versions of constraint solver are known as calc and icalc and represent alternate front ends to the equation solver. The functionality of the two programs is almost identical, but calc is accessed via a stdio-driven interface, while icalc is a GUI program built with the InterViews toolkit for X Windows. The application's ability to access the constraint-solving "engine" via calc's stdio interface allows it to be used in batch-style regression testing: calc receives input from test files, and the resulting output can be compared automatically with known good output. Internally, user-visible functionality is implemented via virtual functions. In calc, these become tty commands for input, output, and control; in the GUI version, these functions have overloaded equivalents.

To use calc, you launch the program and type some simple commands--for example, the keyword equation followed by an algebraic equation. An equation is defined as two algebraic expressions joined by an equal sign. Other commands include the set command, to force a value on a variable, and unset, which leaves the value unspecified. Note that a variable must be unset before it can be given a new value with set. Finally, you can simply type the name of a variable to obtain its solved value. At any time during the session, you can use the equation command to switch to another problem.

The GUI version of the constraint solver is similar to the command-line version in that there are three basic operations: editing an equation, specifying whether a variable is forcing its value, and editing that value. In designing the user interface to icalc, I made sure the user does not have to know about things such as equation parsing or be aware of internal entities. All the user needs to know is that he or she can type in an equation and specify some, but not all, of the variables. The user cannot specify a variable without first indicating that its value will be "forced;" the program does not allow forcing if all remaining variables have already been forced. When icalc is launched, a window similar to Figure 1 is displayed. An equation is preloaded into the equation-editing area, and the user can immediately set values of variables. To set the value of a variable, you must first click on the corresponding Force Value pushbutton to change the variable's state so that it can force a value. Then you can enter a value, which is a floating-point number. This value can be changed at any time. Clicking on the Force Value pushbutton again changes the variable's state so that it will no longer accept values.

The Constraint Solver

The constraint-solving engine is in the constraint.c module, available electronically (see "Availability," page 3). My implementation is based on a system described in Abelson and Sussman's The Structure and Interpretation of Computer Programs (MIT Press, 1985), except that their's is built using the Lisp-like language Scheme.

I chose C++ because I found that the components of the constraint network can be naturally modeled as objects that encapsulate functions and state. Inheritance allows a "constraint protocol" to be defined for a virtual base class, Constraint. The type and connectivity of objects of classes derived from Constraint is known only to the equation parser and the objects themselves. Polymorphism allows me to treat objects of classes derived from Constraint as objects of class Constraint itself. Constrained values, from Constants or user controlled Syms, are propagated around the network by Connectors according to a message protocol.

The bidirectional properties of the constraint network are the result of the behavior of the primitive constraints--operators which enforce relationships like x+y=z. In this case, knowing any two variables allows computation of the third. The other primitive constraints are Constants and Syms. A Constant always forces its value, but a Sym is a UI object for input (user sets value) or output (value is calculated from other known values). In the InterViews version, I added a class derived from Sym, called IVSym, which inherits from both Sym and the MonoGlyph classes in InterViews.

With the addition of Connector objects, primitive constraint objects can be combined to express more complex relationships. Connectors connect pairs of constraints. They can hold and propagate values. When a connector gets a new value from one constraint, it sends the value to its other one. A constraint can also "unconstrain" a connector. In this case, the Connector will tell its other constraint to forget the value previously forced by that Connector.

A Binop (BINary OPerator) is an operator that computes a result from two operands. It is a primitive constraint attached to three connectors. It represents the symmetrical relationship between two operands and a result. The symmetry results from the fact that knowing any two values, the third can be computed. For example, assume x+y=z. If x and y are known, z can be calculated. Alternatively, if z and y are known, x can be calculated as z--y. The same Binop represents both addition and subtraction by assigning the proper connectors to its operands and results. In normal operation of the network, a Binop is told every time one of its connectors gets or loses a value.

The root of the inheritance hierarchy is the Constraint class. A Constraint object must be able to determine if it has enough information to calculate a new value, and to tell attached Connectors about that new value. Similarly, it can withdraw a value lost when one of its inputs loses a value. These behaviors are implemented by the member functions process_new_value() and process_forget_value(), defined as pure virtual member functions in Constraint, implemented only its subclasses. This interface protocol allows client classes, such as Connector, to invoke process_new_value() and process_forget_value() on attached objects, knowing only that the objects are derived from class Constraint.

The other common behavior of all Constraints has to do with the construction of the constraint network. The member function replace_connector() asks the Constraint to disconnect itself from one specified connector and replace it with another. This is used when the equation parser sees an equal sign. All Constraints are attached to one or more Connectors, but the connectivity is specialized by the derived classes.

The process_delete() member function is similar to a destructor, except that it requires a single argument to identify the caller. The network is destroyed by traversing it and passing process_delete() messages recursively to each network object, except the caller. If a conventional destructor were called, the recursion step would pass a delete message to all attached Connectors, including the caller, resulting in an infinite loop.

The Binop class encapsulates the behavior of a generalized undirected operator taking two arguments. Binop has no state or value; it only responds to changes in its inputs. Binop is also a virtual class, and although it has a constructor, Binop objects themselves cannot be instantiated.

Propagation of Constraints

Now that you know what comprises a constraint network, I'll present an example that illustrates how constraints propagate. This particular network expresses the relationship between the force exerted on a spring, F, and the length to which it is stretched, L; see Figure 2. The spring constant K gives the "stiffness" of the spring, while the constant L0 gives the length of the spring with no forces acting on it. As you can see from the figure, the two circles represent Binops: a Multiplier and Adder. The four rectangles represent the four Syms. Connectors (labeled c1 through c5) attach the Binops and Syms to each other.

Because the network is initially created by the parser, the Syms do not have values to propagate. Assume the user starts off by setting the value of the spring constant K. As soon as the value of the K Sym is set, it sets the value of Connector c4; c4 tells the Multiplier that it has acquired a value, but the Multiplier determines that it only has one value and cannot calculate anything.

Now the user sets L0, the unstretched length of the spring. Again, the L0 Sym sets the value of Connector c2, but the Adder doesn't have enough information to perform any computations.

Say the user assigns a value to the Sym L that tells Connector c1 to perform a computation; this, in turn, tells Adder to do the same. Adder finds that two of its connectors have values, so it computes a third value (by subtracting the value on Connector c2 from the value on Connector c1) and passes this value to the Connector c3. Connector c3 passes its value to the Multiplier, which finds that it has two values, so it multiplies the values on Connectors c3 and c4 and passes the product to Connector c5. Finally, Connector c5 tells the Sym to display its value.

Before you can use the network to compute the length L from a known force F, you must first stop forcing the value of L. The user tells the Sym L to "unforce" its value, and the Sym passes this message to the Connector c1. C1 sees that the value has been retracted by the Constraint that set it in the first place, so it forgets its value, and informs the Adder that it no longer has one. The Adder sees that one of its Connectors that had been forcing its value has withdrawn that value, and tells Connectors c2 and c3 to forget their values. C2 knows that its value was set by the Sym L0, so it retains its value, but Connector c3 was forced by the Adder. So c3 forgets its value and passes a "forget value" message along to the Multiplier. The process repeats until the value of Sym F is no longer forced. At this point, the user can have the Sym F force its own value on the network.

The User Interface

Listing One (page 93) is calc.c. This is the straightforward, stdio-driven version of the user interface, which simply invokes a yacc-generated parser to process input from stdio. The electronic version of the source code contains the grammar and lexer source files (gram.y and lex.l).

The second version of the program is the GUI implemention in icalc.h and icalc.c (Listings Two and Three, page 93). This UI is built using InterViews, a public-domain GUI toolkit in C++ created by Mark Linton at Stanford University. It contains class libraries for user-interface objects and for drawing and interacting with users, as well as general-purpose classes such as lists and strings. A central part of the design of InterViews is the glyph, a lightweight screen object. Characters are implemented as glyphs, as are the various layout objects: hbox, vbox, hglue, and vglue. These objects are all constructed by calls to a LayoutKit instance, essentially a "factory" of layout objects.

The FieldEditor, used in the equation editor and the IVSym object, is built by an instance of DialogKit, another factory, this one for interactive objects. The FieldEditor allows a line of text to be edited and displayed; a callback function gets invoked when a line is entered. Another widget factory, WidgetKit, is used to get instances of buttons. InterViews allows the look-and-feel of these objects to be Motif-like or OpenLook-like, depending on a command-line argument used when invoking the application.

The instance hierarchy of UI objects basically consists of two levels. An instance of class App is at the root. The areas of the user interface that are not background are the eq_editor_ (the equation editor), and sym_box_ (the vbox holding the IVSym). The sym_box_ is built by traversing the symbol table and appending each IVSym inside the sym_box_.

The instance of class IVSym is the most complex user-interface object in this program. Nested inside its hbox are a Label, Patch, and Button, each of which is separated from the other and the edges of the hbox by hglue. The Label shows an IVSym's name, and the Button allows the user to force or unforce an IVSym's value. The Patch causes a portion of the screen to be redisplayed when it changes.

Underneath the Patch is a Deck, an unusual interface object which contains a set of Glyphs but only displays one. Inside the Deck are two FieldEditors: ed_, which represents the IVSym value when it can be edited; and uned_, which shows the value but does not allow the user to change it. The Deck has a member function, flip_to(), which gives the index of the glyph to display. When the IVSym is built, the Deck is given a list of pointers to ed_ and uned_, as in the following statement: deck_=layout.deck( uned_, uned_, ed_, ed_);. Due to an InterViews bug, the symbol area is not redisplayed with the new variable values when you delete the equation in the equation area and type in a new one. Resizing the window (using the window manager) forces the symbol area to be redrawn.

Future Directions

This program can be enhanced in several ways. One idea is to extend the underlying constraint mechanism to solve simultaneous equations. This might involve the propagation of partially constrained values around the network, and algorithms to reduce sets of partially constrained values to known values. This would be an interesting test of the generality and reusability of the class design used in this program.

Figure 1: Constraint solver with graphical-user interface.

Figure 2: An example constraint network; F=K*(L--L0).

Fresco: The Next-Generation InterViews

Mark Linton

Mark, known as the father of the InterViews toolkit, is the principle architect of Fresco. Portions of this article first appeared in X Resources, published by O'Reilly & Associates. Mark can be reached at [email protected].


The X Consortium's officially supported successor to InterViews is Fresco, a technology that supports "graphical embedding" by combining structured graphics and application embedding into a single architectural framework. With graphical embedding, you can compose both graphical objects (circles, polygons, and the like) and application-level editors (word processors, spreadsheets, and so on) with a uniform mechanism. Embedded application objects can appear within graphical scenes and will be appropriately transformed.

The Fresco API in X11R6 covers Xlib and Xt functionality, with additional features for graphical embedding. The Fresco API is defined using the Interface Definition Language (IDL) that's part of the Common Object Request Broker Architecture (CORBA) from the Object Management Group (OMG). Among IDL's advantages are:

Being able to distribute IDL-specified objects across address spaces or machines is particularly important for application embedding. For instance, you often might prefer relatively large applications to run as independent processes. Distribution also increases the likelihood of concurrency because of the desire to take advantage of available processing power. Even in the absence of distribution, there are certain applications that are simpler when implemented using multiple threads. The Fresco sample implementation (Fresco 1.0) supports multithreaded applications by locking access to shared data and spawning an independent thread to handle screen redraw.

As part of the support for graphical objects, Fresco operations are screen-resolution independent--coordinates are automatically translated by the implementation from application-defined units to pixels. Consequently, an application's appearance is the same on different screens or printers, without special coding by the programmer. Fresco includes a type that provides stencil/paint operations for rendering.

Fresco 1.0 in X11R6, written entirely in C++, includes an IDL-to-C++ translator, C++ include files, and a library that implements the Fresco classes. This implementation does not completely support distribution, though extensions to the run-time library would make that possible.

The sample implementation also includes an application called "Dish" that allows invocation of Fresco operations through the Tcl scripting language (developed at the University of California, Berkeley). Fresco does not include a Tcl implementation, but if you have the Tcl library installed, then Dish uses it along with CORBA dynamic invocation to create and manipulate Fresco objects from a script.

To summarize, the distinguishing features of Fresco are a standard object model (OMG CORBA) with a C++ implementation, resolution-independence, and graphical embedding. The use of CORBA means Fresco objects can be distributed across a network. Fresco also supports multithreaded applications in a single address space.

[LISTING ONE]



/***** CALC.C --- simple tty interface to constraint solver.
#include <stdio.h>
#include "constraints.h"

extern int yyparse();

main()
{
    Phase = INIT;
    yyparse();    // Parse input
    exit(0);
}


[LISTING TWO]



// *** icalc.h -- Larry Medwin 4/5/93    (Abridged version)
// *** InterViews user interface for the constraint system

extern class IVSym;
extern class Lexio;
extern class App;
extern Style* global_style;
extern App* Myapp;

declareFieldEditorCallback(App)
declareFieldEditorCallback(IVSym)
declareActionCallback(IVSym)

//---------------------------------------------------------------------
class IVSym : public Sym , public MonoGlyph
{
  public:
    IVSym(Sym*);
    ~IVSym() {};
    void accept_editor(FieldEditor*);    // FieldEditor callbacks
    void cancel_editor(FieldEditor*);
    void click_me();                     // Pushbutton callback
    static Style* sym_style;             // set by App::App()
  private:
    void IVSym_init();
                                         // UI stuff
    FieldEditor* ed_;                    // User enters values here
    FieldEditor* uned_;                  // Replaces ed_ when not forced
    Deck* deck_;
    Patch* patch_;
    Button* pbutton_;                    // Displays name_

    virtual void show_val();             // Display value and state
    virtual void show_no_val();
    virtual void show_state();
};
// Replaces lex I/O handlers to read from char arrays instead of stdio
class Lexio
{
  private:
    char* equation_;            // char string for lex
    char* current_;             // cursor for lex
  public:
    Lexio(String);
    ~Lexio();
    // Undefine lex I/O macros
#   undef lex_input()
#   undef unput(char)
    // Lex I/O handlers
    int lex_input();
    void unput(char);
};
//----------------------------------------------------------------------

class App                  // Canvas with equation editor and ActiveSyms
{
  private:
    String equation_;                    // FieldEditor String
    IVSym* network_;                     // Access to constraint network
  public:
    App(Style*);
    Lexio* lex_handler;
    FieldEditor* eq_editor_;             // InterViews components
    Glyph* sym_box_;                     // vbox to hold sym_area_
    void accept_editor(FieldEditor*);    // FieldEditor callbacks
    void cancel_editor(FieldEditor*);
                                  // Start off App with initial equation
    void kick_start(char *);      // (shouldn't need this)
};


[LISTING THREE]



//*** icalc.c --  user interface for InterViews-based version of
//    the constraint equation solver.        By Larry Medwin, 1993

#include <stream.h>
#include "icalc.h"

Style* IVSym::sym_style;     // Set by App::App(), read by IVSym::IVSym()
App*    Myapp;               // Global I/O handlers for lex
implementFieldEditorCallback(IVSym)
implementActionCallback(IVSym)

//*** class IVSym ***
IVSym::IVSym( Sym* source_sym) : Sym(source_sym->name())
{
    state_ = source_sym->state();    // Copy state_ and value_
    value_ = source_sym->value();
    IVSym_init();
}
void IVSym::IVSym_init()
{
    WidgetKit&        kit   = *WidgetKit::instance();
    const LayoutKit& layout = *LayoutKit::instance();
    //--- Init field editors (one for editing, one for uneditable display)
    kit.begin_style("ed");
    ed_ = DialogKit::instance()->field_editor(
        "       ", sym_style,
        new FieldEditorCallback(IVSym)(
            this, &IVSym::accept_editor, &IVSym::cancel_editor)
    );
    kit.end_style();
    //-------------------
    kit.begin_style("uned");
    uned_ = DialogKit::instance()->field_editor(
                     "       ", sym_style,
                     new FieldEditorCallback(IVSym)(
                         this, &IVSym::cancel_editor, &IVSym::cancel_editor)
    );
    kit.end_style();
    //--- Put ed and uned in deck arrange the glyphs in the deck in same
    //  order as enum state_ values, so that flip_to can index the proper
    //  glyph by state
    deck_ = layout.deck( uned_, uned_, ed_, ed_);
    deck_->flip_to( state());

    patch_ = new Patch( deck_);
    //--- The Pushbutton
    pbutton_ = kit.push_button("Force value",
                   new ActionCallback(IVSym)(this, &IVSym::click_me));
    //--- Put all this stuff in an hbox
    body(
        layout.vbox(
            layout.vglue(),
            layout.hbox(
                layout.hglue(), kit.label(name()),  // Sym name
//              layout.hglue(), kit.label("  "),    // spaces
                layout.hglue(), patch_,             // deck
                layout.hglue(), pbutton_,
                layout.hglue()
            ),
            layout.vglue()
        )
    );
    //-------------------
    ed_->field("");    // Start these guys up
    uned_->field("");

    return;
};
void IVSym::click_me()                   // Implement state transition
{
    switch(state()) {
        case UNCONSTRAINED:
            state(ACTIVE_NO_VALUE);
            break;
        case PASSIVE:       // Can't make transition if Sym is constrained
            break;
        case ACTIVE_WITH_VALUE:
            state(UNCONSTRAINED);
            con_->forget_value( this );
            break;
        case ACTIVE_NO_VALUE:
            state(UNCONSTRAINED);
            break;
        default:

            assert(True);
    }
    // see /home/iv/src/examples/zoomer/main.c
    patch_->redraw();
    return;
//------------------------------------------------------------------
void IVSym::accept_editor(FieldEditor* ed)
{
    float ed_val;
    assert( state() != PASSIVE);            // Shouldn't try to set this
                                            // value if it is being forced
    const String& valueStr = *ed->text();   // Get value from field editor
    if (!valueStr.convert(ed_val)) {        // Illegal number?
        cancel_editor(ed);
    }
    else {
        con_->forget_value(this);           // Remove last constraint
        state(ACTIVE_WITH_VALUE);           // Update state & propagate value
        set_value(ed_val);
    }
    return;
};
void IVSym::cancel_editor(FieldEditor* ed)
{   switch(state())                         // Restore old value
    {   case PASSIVE:
        case ACTIVE_WITH_VALUE:
            show_val();
            break;
        case UNCONSTRAINED:
        case ACTIVE_NO_VALUE:
            show_no_val();
            break;
        default:
            assert(True);
    }
    return;
};
void IVSym::show_val()
{
    char tmp[100];
    assert( state() != ACTIVE_NO_VALUE);
    assert( state() != UNCONSTRAINED);
    switch(state())
    {
        case ACTIVE_WITH_VALUE:
            sprintf( tmp, "%6.2f", value());
            ed_->field(tmp);
            break;
        case PASSIVE:
            sprintf( tmp, "%6.2f", con_->get_value());
            uned_->field(tmp);
            break;

        default:
            assert(True);
    }
    return;
}
//----------------------------------------------------------------------
{
    assert( state() != ACTIVE_WITH_VALUE);
    assert( state() != PASSIVE);
    switch(state())
    {
        case ACTIVE_NO_VALUE:
            ed_->field("");
            break;
        case UNCONSTRAINED:
            uned_->field("");
            break;
        default:
            assert(True);
    }
    return;
}
void IVSym::show_state()
{
    deck_->flip_to(state());
    patch_->redraw();
    return;
}
//*** class Lexio ***
Lexio::Lexio( String equation)
{
    // Copy into lex input string
    // Format: "equation " %s '\0'
    equation_ = new char[equation.length()+strlen("equation ")+1];
    sprintf( equation_, "equation %s", equation.string());
    current_ = equation_;
}
Lexio::~Lexio()
{
    delete[] equation_;
}
int Lexio::lex_input()
{
    if (current_ < equation_)     // Don't step back over start of string...
        return 0;                 // EOF
    else                          // Return char, or 0 at end of string
        return *(current_++);
//---------------------------------------------------------------------
void Lexio::unput(char c)
{

    if (current_ > equation_)    // If not before beginning
        *(--current_) = c;       // Push back pointer and put in char
    return;
}
//*** class App ***
implementFieldEditorCallback(App)
App::App(Style* style) : network_(0)
{
    const LayoutKit& layout = *LayoutKit::instance();
    IVSym::sym_style = new Style(style);    // Save copy of style
    // Init field editor
    eq_editor_ = DialogKit::instance()->field_editor(
                      "                    ", IVSym::sym_style,
                      new FieldEditorCallback(App)(
                              this, &App::accept_editor, &App::cancel_editor
                              )
                      );
    sym_box_ = layout.vbox();    // Init CurSymArea & its enclosing box
}
void App::accept_editor(FieldEditor* ed)
{
    IVSym* tmp_sym;
    WidgetKit& kit = *WidgetKit::instance();

    // Get rid of old constraint network
    //  if( network_ != 0) {
    //  network_->process_delete((Connector*)0);
    //  delete network_;
    //}
    for (GlyphIndex g = sym_box_->count(); g > 0; g--)  // Clean up glyphs
    {
        sym_box_->remove(g-1);
    }
    lex_handler = new Lexio( *ed->text());    // Get FieldEditor string to lex
    delete Symtab;                            // Create and empty symbol table
    Symtab = new SymbolList;
    // Parse the equation, generate constraint network and symbol table
    Phase = PARSE;
    yyparse();
    // Build up sym_box area
    for (ListUpdater(SymList) i(*Symtab); i.more(); i.next())
    {
        Sym* cur_sym = i.cur();
        tmp_sym = new IVSym( cur_sym);        // "Promote" Sym to IVSym
        // Replace Sym with IVSym in the constraint network
        Connector* tmp_con = cur_sym->con_;
        tmp_con->disconnect(cur_sym);
        tmp_sym->connect(tmp_con);
        // delete cur_sym;
        // Put the IVSym in the sym_box_ area
        sym_box_->append(tmp_sym);
    }
    // Keep a hook into the network so we can clean up later

    // network_ = tmp_sym;
    // UI gives us access to symbols, not the symbol table
    // delete Symtab;
    // Send expose event (How??)
//---------------------------------------------------------------------
void App::cancel_editor(FieldEditor* ed)
{
    ed->field(equation_);                 // Restore old equation
    return;
//---------------------------------------------------------------------
void App::kick_start(char* s)             // Start it off
{
    eq_editor_->field( s );               // Set up initial string
    accept_editor( eq_editor_);           // Get started
    return;
}
//*** main() ***
int main(int argc, char** argv)
{
    Session* session = new Session("FieldEditorTest", argc, argv);
    WidgetKit& kit = *WidgetKit::instance();
    const LayoutKit& layout = *LayoutKit::instance();
    Myapp = new App(session->style());
    Myapp->kick_start("a+b+c+d+e=0");      // Start it off
    return session->run_window(            // Set up app window
        new ApplicationWindow(
            new Background(
                layout.hbox(
                    layout.hglue(),
                    layout.vbox(
                        layout.vglue(), Myapp->eq_editor_,
                        layout.vglue(), Myapp->sym_box_,
                        layout.vglue()
                    ),
                    layout.hglue()
                    ),
                kit.background()
            )
        )
    );
    exit(0);
}


Copyright © 1994, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.