Home page  

X-Designer C++ Code Generation


Table of Contents

Historical Perspective
C++ and the Customer
Summary of commonly-asked questions
Answers to commonly-asked questions
An Example speaks a thousand words...
Installing the Example code

Historical Perspective

IST's X-Designer release 3.0 (launched in early 1993) was the first major Motif Graphical User Interface (GUI) builder to offer developers the option of generating their designs into C++ source code. Since 1993 IST has been consistently adding to X-Designer's C++ code generation functionality, ensuring that today it is still the most powerful and comprehensive environment for developing C++-based GUI applications.

February 1993: IST launches X-Designer 3.0, its most important new feature being the ability to generate C++ code to realise and manipulate the user interface. This first release of C++ code generation includes intermediate superclassing and subclassing (see later for a full description), advanced features only recently released in competing products.

May 1994: IST launches X-Designer 3.2, allowing users to package high-level user interface components into reusable definitions available from X-Designer's palette. C++ code generation is extended to take advantage of these reusable definitions, making subclassing even more powerful and providing unparalleled support for "programming-in-the-large".

February 1995: IST launches X-Designer 4.0, offering users the ability to generate C++, from a single design, that will build on both UNIX/Motif and MS-Windows platforms. By targeting the industry-standard Microsoft Foundation Class (MFC) class library IST continues its commitment to generating native code with no proprietary extensions.

Note: The availability of the MFC cross-platform development environment for the Macintosh means that X-Designer-generated C++ code can also be readily ported to the Macintosh.

Go to top of this page



C++ and the Customer

Our experience to date in helping prospective customers evaluate X-Designer has been threefold:

  1. Increasing numbers of GUI application developers are choosing C++ as their primary development language. We estimate that approaching 35% of our customer base are now using XDesigner's C++ code generation features. The object-oriented (OO) features of C++ are naturally suited to GUI development and the concomitant reusability of GUI interface elements implemented in C++ has long been held up as one of the best examples of reuse in the OO community.

  2. One of the chief differences between C and C++ development environments (if C++ is being used correctly and not just as an extended C) is that there is a dramatic shift of effort to up-front design. Choosing which real-world and implementation objects should be represented as C++ classes and specifying their relationships is a difficult and confusing business, especially for developers more accustomed to traditional procedural languages such as C, PASCAL, etc. Many of our prospective customers are caught in this learning curve and find it difficult to evaluate the C++ capabilities of X-Designer when they have never previously conducted a large-scale development in C++.

  3. It is difficult to demonstrate the full breadth of X-Designer's C++ code generation features in a simple "Hello World" tutorial. It tends to be only later, in the course of a real development project, that X-Designer users start to fully appreciate the ability to easily subclass existing GUI classes, add member functions and member data, build reusable class-structured definitions, etc.

The goal of this white paper is to guide the reader through X-Designer's C++ code generation capabilities by answering a series of technical questions actually posed by customers who were evaluating X-Designer for use in a C++ environment. In this way we hope to address real-world concerns about our product and provide the best possible basis for a fair comparison with our competitors.

C++ code examples, generated from two sample X-Designer designs, are available for inspection as you read this white paper.

Go to top of this page



Summary of commonly-asked questions

  1. What does "X-Designer's C++ code generation is based upon the Doug Young model" mean?
  2. Does X-Designer generate `typical' C++?
  3. How are constructors and destructors handled?
  4. Why are widgets created in a separate create() member function and not in constructors?
  5. How does one add member functions and member data to C++ classes defined within XDesigner?
  6. How does the C++ code generation work with X-Designer's reusable palette definitions?
  7. What is the `Children Only' option on the Code Generation panel of the Core Resources dialog?
  8. What is `Motif MFC'?
  9. Will X-Designer support ANSI C++?


Go to top of this page



Answers to commonly-asked questions

  1. What does "X-Designer's C++ code generation is based upon the Doug Young model" mean?

    To understand what IST means by quoting Doug Young and his famous model we must take a step back in time to when C++ first became readily available. A good C++ programmer, faced with the hideous complexity of the Xm/Xt/X toolkit APIs would naturally tend to start simplifying it by encapsulating each Motif widget within a class interface, providing each class with related sets of member functions like Hide() and Show() that would conceal the many different ways of doing the same thing offered by Motif. This approach is known as the `thin wrapper' approach and is NOT the approach advocated by X-Designer!

    Doug Young noted that what C++ developers really want is a higher-level encapsulation of GUI functionality. C++ developers want to deal with reusable `chunks' of GUI functionality, like dialogs and scrolling lists, rather than with individual widgets. After all, most GUI builders will already generate the reams of mundane C code needed to implement GUI `chunks'. What is really needed is a tool that will allow developers to easily hide this code within reusable C++ classes.

    So, in X-Designer, if you have designed (for example) your own specialised Password Entry Dialog, and you wish your application developers to use it from C++, you simply specify the widget at the top of your GUI `chunk' to be structured as a C++ class. In the case of a Password Entry Dialog this widget would probably be the Dialog Shell at the top of the design hierarchy, but ANY widget (in non-Windows mode) can be made a C++ class.

    Now, if you generate C++ externs, you obtain a definition of a C++ class that encapsulates the Dialog Shell and all of its children, i.e. an independent `chunk' of GUI functionality. This class is said to be the enclosing class for all of its non-class-designated widget children. If you have not specified that any of the Dialog Shell's children be structured as C++ classes all of the children widgets will simply appear as a long list of member variables, each of which contains the widget ID of a child.

    
    /*
    ** Generated by X-Designer
    */
    	:
    #include <xdclass.h>
    	:
    class top_shell_c: public xd_ApplicationShell_c {
    public:
          virtual void create (Display *display, char *app_name,
    	     int app_argc, char **app_argv,
    	     char *app_class_name = NULL);
    protected:
          Widget top_shell;
          Widget my_form;
          Widget button_1;
          Widget button_2;
          Widget button_3;
          Widget button_4;
    };
    typedef top_shell_c *top_shell_p;
    extern top_shell_p top_shell;
    	:
    

    Code Example A Example C++ class definition (Externs file)

    (Note: At the risk of confusing the issue X-Designer provides the developer with complete control over how the generated code is structured. For example, children widgets can be specified as global or file static variables; public, private or protected member variables of their enclosing class or local variables defined in the enclosing class's create() member function. The default for a named widget is to be placed as a protected member variable of the enclosing class. The default for an unnamed widget is to be placed as an automatic variable of the enclosing class's create() member function and not as a member variable at all.)

    It is to these higher-level classes, encapsulating whole hierarchies of Motif widgets, that we will later add member data and functions until we have a clean C++ interface between the underlying application and a set of cooperating GUI objects. The seething complexity of Motif, the Xt and X toolkits will remain where they belong, hidden behind a simple, well-designed class interface. That is what is meant by adhering to the Doug Young model of C++ code generation. Of course, nothing stops you from specifying `thin' class wrappers around a hierarchy of one widget, if that is what you need.


    Go to top of this page, summary of questions


  2. Does X-Designer generate `typical' C++?

    We tend to be asked this question by more experienced C++ programmers who have not used either Motif or a Motif GUI builder before. It appears to arise from the fact that, for small evaluation-type designs, the code generated by default can look more like C than C++. There are two reasons for this:

    • The Motif API is, of course, a C API - so the majority of the worker code for creating the interface will always consist of endless calls to XmThis(), XtSetThat(), etc.
    • Because X-Designer adheres to the Doug Young model of binding C++ to the Motif toolkit (see above and below).

    In a relatively simple GUI design, with only one C++ class, most of the generated C++ code will consist of a large create() member function stuffed with hundreds of calls to the C Motif API. But that does not mean it is not C++. It just is not the way one would typically sit down to write C++; because placing such large amounts of code inside a single member functions is frowned upon as poor programming practice. Why? Because it is cryptic and difficult to maintain. But, remember, this is generated code, output by a graphical tool that makes laying out complex interfaces a relatively straightforward task. Developers no longer maintain this Motif code by editing it directly, they make changes to the design in X-Designer and then regenerate the code. The complexity of Motif is all taken care of by X-Designer and safely hidden behind the class interface.

    The code begins to look more like `typical' C++ when you generate C++ externs and start to add method callbacks and your own member functions. For example, to continue our Password Entry Dialog example, you may wish to add an OK callback and a member function to check the entered password. Adding a callback is simple from within X-Designer. Callbacks can either appear in the generated code as C-like global functions or member functions of the enclosing class. An empty stub function is generated into the C++ Stubs files, into which you can insert your application code. There are many ways to add member data and member functions to your class using either code preludes, intermediate base classes or subclassing (see below).


    Go to top of this page, summary of questions,


  3. How are constructors and destructors handled?
  4. Why are widgets created in a separate create() member function and not in constructors?

    X-Designer comes bundled with a simple class library that provides sensible base classes from which you can derive your new UI classes. Nothing stops you either replacing this class library with your own or completely rewriting it. The supplied class library does provide some useful member functions for simple Motif operations, e.g. showing and hiding widget hierarchies.

    Fundamental to the design of this base class library (and most leading GUI class libraries, e.g. Microsoft's Foundation Classes) is the separation of class instantiation from widget creation, i.e. the underlying widgets are not created at the same time as the C++ class is instantiated (e.g. with the C++ new operator). Instead a separate create() member function is provided that is responsible for creating the underlying widgets. There are several good reasons for this separation:

    • Widget creation is expensive and you may wish to create your UI objects all at once (so you can refer to them at will), but only create their widgets as needed.
    • If you created widgets in a constructor you would logically wish to destroy them in a destructor. Unfortunately this is fraught with difficulties, because XtDestroyWidget() deletes a given widget and all of its children. Keeping track of which objects' widgets are valid and which have been deleted is therefore difficult when one object going out of scope can delete the widgets of several other `children'.
    • Constructors cannot have virtual functions (the base function is always called).

    So the current base class library only provides the most basic constructors and leaves it to the user to provide destructors and more complex constructors.

    In addition, the only time you will see X-Designer itself outputting constructors and destructors into the generated code is when a class-designated widget encloses another class-designated widget, e.g. a Form designated as a class contains a Button also designated as a class. If the Button class appears as a member object of the enclosing Form class (or as a global or file static object) then a constructor and virtual destructor are declared for the enclosing Form class that are respectively responsible for the creation and destruction of the enclosed Button object.

    While the constructors and destructors provided by X-Designer as default are therefore quite lightweight the standard approach to adding functionality is either to insert code preludes into the create() member functions (called at widget-creation time) or to use subclassing to provide more specialised constructors and destructors (see below).


    Go to top of this page, summary of questions,


  5. How does one add member functions and member data to C++ classes defined within XDesigner?

    This is, of course, perhaps the most important question to ask when evaluating a C++ Motif GUI builder. Now that you have developed a set of nice clean C++ class wrappers around the GUI elements of your application, how do you add member functions and data to allow them to do useful work?

    The answer to this question highlights the fundamental design philosophy of X-Designer, namely that there is rarely just one way of doing anything. It is a particular strength of X-Designer, and the reason it is particularly popular with engineers, that we never coerce the developer into following a particular approach. Anything developers wish to do (even if we think it a little strange) should be possible using X-Designer... somehow. We offer an unparalleled control over the generated code, so that YOU decide how your application is structured.

    There are four ways of defining new member functions and data in your classes: intermediate base classes, "instantiate as" subclasses, code preludes and method callbacks.

    Basically, X-Designer assumes a class hierarchy like this:

         BaseClass                 |
    	    [SuperClass]       | inheritance
    	       YourClass       |
    		[SubClass]     V
    

    The classes in [] are optional and are created by you in a text editor. In the Code Generation page of the Core resources panel, you can specify the name of your class YourClass, the name of its base class [BaseClass] and the actual class to instantiate as [SubClass]. If you fill in all these fields, XDesigner will generate:

      class YourClass : public SuperClass {     ....   };
    

    and actually instantiate the object as:

       widgetn = new SubClass;
    

    This allows you to put member functions and data of your own in either a sub- or super- class of YourClass if you wish. Of course, [SuperClass] will not have access to the widgets of YourClass and is generally less useful than [SubClass]. These sub- and super- classes are not created by XDesigner, you must create them in a text editor and include the relevant include files in the Type Declaration field on the Code Generation panel (or using the Module Preludes).

    The Code Preludes accessed from the Widget menu can also be used to insert arbitrary text into the generated C++ at several pre-defined locations. For example, a `Public methods' code prelude can be used to insert public member data or functions.

    Method callbacks are easy to understand and simply consist of X-Designer-generated code that routes Motif/Xt callbacks to member functions defined against the enclosing class. While these member functions are usually called by the Motif/Xt toolkit in response to user interactions with the GUI interface (e.g. pressing a Button) they may also be called from application code. Empty stubs are generated for these member functions into a C++ stubs file, ready for the developer to insert code.

    Given the degree of flexibility offered by X-Designer for adding functionality to its generated C++ classes it can be difficult for the user to decide which method to use. While there are no absolutes, we can provide a few guidelines:

    1. Module and code preludes are extremely useful for exerting precise control over the generated code and for rapid prototyping.
      
      /*
      ** Generated by X-Designer
      */
      	:
      #include <xdclass.h>
      class myRowColumn_c: public myRowColumnSuperclass {
      public:
            // Example of public methods added using
            // `Public methods' code prelude
            void set_label (char *new_label)
            {
      	    strcpy (label, new_label);
      	    XtVaSetValues  (myPushButton, XmNlabelString,
      		     XmStringCreateLtoR (new_label,
      		     XmFONTLIST_DEFAULT_TAG), NULL);
            }
            // Pure virtual declaration of say_hello() so
            // that it may be called from within a method
            // callback
            virtual void say_hello () = 0;
            virtual void create (Widget parent,
      	   char *widget_name = NULL);
      protected:
            Widget myRowColumn;
            Widget myPushButton;
      
      private:
            // Example of private data member added using
            // `Private Method' code prelude
            char label[64];
      public:
            // Example of member callback declarations
            static void initialise( Widget, XtPointer,
      		  XtPointer );
            virtual void initialise( Widget, XtPointer );
      };
      	:
      

      Code Example B Example of code preludes in C++ class definition (C++ Externs)

    2. Superclassing is useful when the functionality to be added does not need to know about the data members of YourClass. It has the advantage that method callbacks can directly refer to member data or functions added in this manner (without the need for corresponding declarations in YourClass).
      
      #include <xdclass.h>
      //
      // Example of adding member functions and data by
      // inserting a class between the base class (from
      // X-Designer's default class library) and the
      // generated class.
      //
      class myRowColumnSuperclass: public xd_XmRowColumn_c  {
      protected:
      	int a;
      public:
      	int get_a ()  {return a;}
      	void set_a (int value)  {a = value;}
      };
      

      Code Example C Example intermediate base class definition (superclass.h)

    3. Subclassing seems to be the preferred method for adding member data and functions (especially overridden constructors and destructors) and works best as the size of the application grows. It is the `classical' way of adding functionality in an OO development environment. Note that member functions added in the subclass can only be accessed from method callbacks if corresponding virtual functions (usually pure virtual functions) are inserted into the class YourClass, because method callbacks are defined as member functions of YourClass, not the subclass [SubClass].
      
      #include "superclass.h"
      #include "demo.h"
      //
      // Example of adding member functions and data by
      // inserting a class below the class generated by
      // X-Designer (and using the `instantiate as' code
      // generation option.
      //
      class myRowColumnSubclass: public myRowColumn_c  {
      public:
      	void say_hello ()
      	     {set_label ("Hello world !\n");}
      };
      

      Code Example D Example sub-class definition (subclass.h)

    4. Method callbacks will obviously be used primarily for responding to user interface events, e.g. a set of Buttons may use an OnPress() method callback defined on an enclosing Form to handle Activate events. They may all share the same method callback because one of the arguments to a method callback is the ID of the widget reporting the event. This scheme is often used to implement an event dispatcher type of mechanism, to avoid the proliferation of hundreds of over-specialised method callbacks and allow multiple classes to potentially respond to the same event.
      
      /*
      ** Generated by X-Designer
      */
      	:
      #include <subclass.h>
      	:
      /*
      ** X-Designer Stub myRowColumn_c::initialise
      */
      void 
      myRowColumn_c::initialise (Widget w,
      	       XtPointer xt_call_data )
      {
            XmAnyCallbackStruct *call_data =
      	       (XmAnyCallbackStruct *) xt_call_data;
            say_hello ();
      }
      

      Code Example E Example method callback (C++ Stubs)




    Go to top of this page, summary of questions,


  6. How does the C++ code generation work with X-Designer's reusable palette definitions?
  7. What is the `Children Only' option on the Code Generation panel of the Core Resources dialog?

    Since X-Designer release 3.2 users have been able to specify that a high-level widget hierarchy be saved away as a reusable definition. This definition appears as a new icon on the widget palette, much like a Motif compound object (e.g. a FileSelectionBox). There is an obvious synergy between these reusable definitions and X-Designer's C++ code generation capabilities, which also pertain to encapsulating high-level widget hierarchies. Definitions extend the C++ code generation capabilities by providing an intuitive graphical interface to code reuse.

    To exploit this synergy the user must first structure the top-level widget in the definition to be a C++ class (before making it a definition). Generating code for this definition design will generate a C++ wrapper for this reusable definition. Instantiating this definition in subsequent designs (by simply pressing on the icon in the widget palette) will automatically load the definition's widget hierarchy. The code generated in the new design will simply contain references to the class interface defined by the C++-structured definition.

    Recommendations:

    • In general, definitions and their instances should be kept in separate design files (the .xd files used by X-Designer as save files). While this is not necessary it results in much cleaner code, with separate C++ source and header files for definitions and instances.
    • The `Children Only' option on the Code Generation panel of the Core Resources dialog is very useful in the context of definitions. If the top-level widget in a definition's hierarchy is not a Shell then it must have one or more parents simply used as placeholders in the definition's design. For example a pulldown menu definition must have at least a Shell, MenuBar and a Cascade Button as placeholder parents. Designating the Shell to have `Children Only' structure tells X-Designer to only generate code for structured children. This enables X-Designer to generate code for arbitrarily small GUI fragments, without worrying about name clashes between designs, unwanted code, etc. Note that the `Children Only' option is useful for any structured code generation (C, C++ or UIL) and is not just useful for definitions.
    • Once the definition has been constructed, and its C++ source and header files generated, it can be immediately reused by multiple developers across multiple projects. It provides an excellent means for building a GUI component library or assuring a common `look and feel' across a product range.

    Even more importantly, developers are free to customise their definition instances. As usual this can be done in more than one way:

    The simplest way to extend or modify an instance is to set resources on instance widgets or add new children widgets. Note that some understanding of the way the code is structured is necessary. For instance, adding an additional Button to a Form contained within an instance requires that the Form is accessible to the create() function responsible for adding the new button. If the Form was a private data member of the definition's class then only member functions of that class could access its widget ID. The external create() function that instantiated the definition could therefore not obtain the widget ID required for its call to XmCreatePushButton(). The definition designer must decide which elements of his definition are to be exposed outside the class boundary so that they may be changed, e.g. by declaring a widget to be have Public C++ access on the Code Generation panel of the Core Resources dialog.

    A more powerful mechanism for extending reusable definitions is to declare the instance to itself be structured as a C++ class. This has the effect of creating a new class, subclassed from the class created for the definition, with the advantage of providing the member functions of the new class with access to the public AND protected members of the definition class. It also allows you to inherit and override/extend the method callbacks of the definition. This subclassing approach can be combined with the techniques described in adding member functions (5 above) to support the most complex C++ structuring requirements.

    With a little practice the capabilities described above appear far less complex than they sound. Given X-Designer's ease-of-use it is very simple to try out all these features on small test designs and to find a way of structuring your generated C++ code that best suits your development environment.

    Note: Just as you cannot remove or rename members in C++ class inheritance you cannot remove widgets or change their names in instances or subclasses.


    Go to top of this page, summary of questions


  8. What is `Motif MFC'?

    As of release 4.0 X-Designer allows the user to develop applications capable of running on both UNIX/Motif and Windows. Motif MFC is an important part of this cross-platform solution, but to understand where it fits into that solution it is useful to consider some background.

    There are so many ways to solve the cross-platform problem that they can be viewed as a spectrum. At one end of the spectrum lies the brute force approach: two development teams independently developing applications with identical functionality on the two platforms. This is not a popular approach for the obvious reason that it is awfully expensive. At the other end of the spectrum is the single-source approach. One development team uses some technology to create a single set of application sources that can be built (effectively cross-compiled) for multiple target platforms. The latter is clearly ideal, but Murphy's law dictates that there will often be some pain involved in getting there!

    One drawback of the single-source approach is that many of the other cross-platform tools in the marketplace achieve single-source development by offering a `least-common-denominator' solution, where developers can only use GUI components readily available on both Motif and Windows. This turns out to be rather a small subset and tends to lead to half-rate applications on both platforms

    X-Designer's approach is to generate Motif code on UNIX and Microsoft Foundation Class (MFC) code on Windows, from a single common design. The `least-common-denominator' problem is avoided by:

    • Generating Windows code that (optionally) brings Motif functionality, like the Form widget's resize behaviour, to the Windows platform.
    • Allowing developers to include Motif-specific elements in their designs (denoted by the pink colour). These elements are not included into the generated Windows code and, if required, can be provided by additional special-purpose code on the Windows side.

    Some additional work must be done whenever application needs to make dynamic changes to the user-interface, e.g. adding an item to a list. This obviously requires Xm/Xt calls to be made on the UNIX side and MFC calls on the Windows side. Without Motif MFC these calls would have to be made from platform-specific code, maintained independently on both platforms.

    Motif MFC is a partial implementation of the MFC class library on Unix/Motif. If you look at the Motif MFC code provided you will find MFC class definitions and implementations of MFC methods, e.g. CListBox::InsertString(), that internally make Xt/Xm calls. If you generate Motif MFC code the generated code will now make calls to the same MFC class interface on both UNIX and Windows. Behind the scenes the Motif MFC class library simply translates these MFC calls to native Xm/Xt calls. Now, if your application wishes to add a line to a list it can simply call CListBox::InsertString() on both platforms.

    The Motif MFC class library encapsulates all the platform-specific GUI code. But it is only a partial implementation of the Windows MFC class library. In the course of full-scale development you may well find some class or method that has not been implemented in Motif MFC. You then have a choice. You can either extend the Motif MFC class library (we provide the source free with XDesigner) or consider investing in a full port of the MFC class library to UNIX. To date, our cross-platform customers have preferred to extend their Motif MFC class libraries as required.

    Motif MFC is a bit of a complex concept. But it is an effective solution to the problem of single-sourcing an application for both UNIX Motif and Windows, if not a silver bullet.


    Go to top of this page, summary of questions


  9. Will X-Designer support ANSI C++?

    Yes, definitely... as soon as the ANSI committee finalise their deliberations and it is widely supported by vendors. This will in any case entail only minor modifications to X-Designer since it generates plain-vanilla C++ and does not rely upon any controversial extensions.


Go to top of this page, summary of questions


An Example speaks a thousand words...

Since an example is usually better than any amount of explanation we provide two simple C++ GUI applications that illustrate much of the above.

Example 1: `sub_super'

Illustrating how to add data and function members to X-Designer-generated classes via code preludes, intermediate superclasses and `instantiate as' subclasses.


demo.xd               // Sample design file
demo.cxx              // Main C++ file (look for commented preludes)
demo.h                // C++ externs file
demo_stubs.cxx        // C++ stubs file
superclass.h          // Illustrates adding member data and functions via
		      // an intermediate superclass
subclass.h            // Illustrates adding member data and functions via
		      // an `instantiate as' subclass
Makefile              // Build file for Solaris 2.4

Assuming you have an evaluation copy of X-Designer load up demo.xd and look at the settings on the Core resources Code Generation panel, the various prelude panels and the Generate dialog. You may wish to manually inspect the demo.xd file to see what has been set, then inspect the code (demo.cxx, demo.h, demo_stubs.cxx) to see the effect on the generated code.

Example 2: `cpp_defs'

Illustrating how to use reusable definitions with C++ code generation and instance subclassing.


def.xd            // Sample design file containing a definition
def.cxx           // C++ file implementing above definition
def.h             // C++ externs for above
inst.xd           // Sample design file containing extended instance
inst.cxx          // C++ file implementing extended instance
inst.h            // C++ externs for above
Makefile          // Build file for Solaris 2.4

Installing the Example code

The sample code is a compressed tar archive and here is an ascii text version of this document.

To recover the source (assuming you have saved it to a file called sample_code.tar.Z in the current directory):

  uncompress sample_code.tar.Z
  tar xvf sample_code.tar

This will create two subdirectories (sample_code/sub_super and sample_code/cpp_defs) containing the sample files. You may need to load the .xd files and regenerate the Makefiles for non-Solaris 2.4 platforms. To generate sample_code/cpp_defs/Makefile first load defs.xd and generate Makefile, then repeat for inst.xd.


Top of Page COPYRIGHT ©1995-2024 IST Reading Ltd. ALL RIGHTS RESERVED. Legal Info | Privacy | Contact Us