miniXplus: A Simple X windows Toolkit.

Tim Marsland

March 1990

1 Introduction

The miniXplus module is a simple programmers interface to the X window system, version 11. Currently both a Pascal and C version of the toolkit are available; other language bindings will be made available as time permits. miniXplus should really be thought of as a simple X toolkit for people who want to plot graphs. It was largely inspired by its predecessor, miniX written and designed by Steve Young, together with some ideas about device independence from PostScript and its embodiment in the NeWS window system, and the display list concepts from PHIGS.

A number of basic graphical entities such as lines, rectangles, arcs and text may be drawn in various colours, with user-defined scaling and translation. Simple routines for drawing plots and graphs together with annotated axes are provided. A basic mechanism for performing animated graphics is also available, built on the concept of display lists. Other graphical objects such as pop-up menus and dialog boxes are not provided by this toolkit.

Despite their relative simplicity, programs which use miniXplus are able to accommodate the display window being stretched and resized almost arbitrarily, as well as being able to repair the damage done by obscuring then exposing part or all of the display window.

2 Using miniXplus

Simple use

Simple use of the toolkit is usually similar to the following code template:

   ...
   VAR b : Button;
   BEGIN
      ...                       {create the data to be plotted}
      MxpInit (0, 0, 0, 0);     {initialise the window}
      ...
      ...                       {various scaling & drawing operations}
      ...
      REPEAT
 b := MxpWait           {return which button was pressed}
      UNTIL b = leftButton;     {terminate on left button click}
      MxpClose                  {destroy the window}
   END
   ...

Full example

To clarify the above code template, here is a complete program that generates the data for, then plots a graph of y = x^{2}, with 30 x ordinates varying in value from 1.0 to 3.0.

PROGRAM simple (input, output);
$search '/usr/local/lib/libmXplus.a'$
IMPORT mXplus;
CONST NumPoints =   30;
   xlo       = -1.0;
   xhi       =  3.0;
VAR i : Integer;
 x, xinc : Real;
 y : ARRAY[1..NumPoints] OF Real;
 b : Button;
BEGIN
x := xlo;
xinc := (xhi - xlo) / NumPoints;
FOR i := 1 TO NumPoints DO BEGIN
   y[i] := x*x;
   x := x + xinc
END;
MxpInit (0, 0, 0, 0);
MxpTransform (0.1, 0.1, 0.8, 0.8, 0.0, 0.0, 1.0, 1.0);
MxpXAxis (0.0, 1.0,  5,  0.0, 0.015, '  x  ', xlo, xhi);
MxpYAxis (0.0, 1.0, 10, 0.25, 0.015, 'y=x^2', 0.0, xhi*xhi);
MxpTransform (0.0, 0.0, 1.0, 1.0, xlo, 0.0, xhi-xlo, xhi*xhi);
MxpPlotLines (xlo, xhi, y, 1, NumPoints);
REPEAT
   b := MxpWait
UNTIL b = leftButton;    {terminate on left button click}
MxpClose
END.

For many engineering applications, some variation on the theme of this basic template is all that is necessary. The graphics routines used above, together with the rest of the procedures and functions available are described in the appendix to this document, which is the Pascal interface header to the full miniXplus toolkit module.

Compiling and linking programs with miniXplus

Suppose that the example program above is contained in the file demo.p. In order to compile this program you need to use the usual pc command:

pc -c demo.p

to link the program into an executable file you will then need to type:

 pc -o demo -Wl,-L,/usr/lib/X11R4 demo.o /usr/local/lib/libmXplus.a -lX11

Alternatively you can compile and link in one step using:

 pc -o demo -Wl,-L,/usr/lib/X11R4 demo.p /usr/local/lib/libmXplus.a -lX11

The pathnames of the library files may change in subsequent releases. It is also possible to link programs with miniXplus when using the “Compile Pascal” icon to perform these operations.

3 Advanced use of miniXplus

Graphical output: window systems versus plotters

In the beginning, graphical output was produced by driving a particular piece of hardware, such as a chart recorder or pen plotter. The programmers model was that of a pen travelling over a piece of paper making indelible marks in (possibly different) colours. This is the basis of many existing graphics libraries, including miniXplus, and is still a useful conceptual model for thinking about the process of drawing graphs using computers. What’s so difficult about producing plots on a bit-mapped display?

The reason why it’s more complicated is because of the window system which increases the usability and versatility of the display hardware at the penalty of increased software complexity. Let’s examine this a little more carefully. The concept we need to examine is the idea of a rectangular region of the screen called a window as part of a window system, such as X windows. Windows can be thought of as a separate drawing surface in which a program can produce graphical output independently of the other windows displayed on the screen.

Now miniXplus simply uses one window to produce all of its graphical output, but the key point is that it has to coexist with all the other applications programs whose windows are sharing the resources of the display at the same time e.g. terminal emulators such as xterm and hpterm, or the emacs text editor. Problems start to occur when deciding how these windows should be displayed, that is, how big should they be when they are drawn or resized, and how much of them should be visible, that is, which parts are obscured by other windows “on top” of the window stack. Fortunately most of these issues have already been carefully thought out when the X protocol was designed, and are handled either by the X server program that manages the display, or by the window manager program e.g. uwm, hpwm or X.Desktop. However, since each window must be capable of being redrawn after it has been damaged by being obscured or resized, the program that owns the window (the client program in X terminology) has some basic responsibilities i.e. it needs to know how to repaint itself when damaged by being uncovered or resized. So, we have to think of different strategies for many interactive graphics programs, even for something as simple as painting graphs in windows.

There are several possibilities. One way is to ignore the problem altogether, and pretend the window is immune to damage. Whilst a tempting approach at first sight, this leads to client programs which tend to passively “take over” the display because the user dare not disturb the window layout once the client is running. In some cases limited immunity from damage can be achieved using automatic backing store - the X server is requested to keep an off-screen image copy of the contents of the window, and uses the copy to repair damage caused by the window being uncovered or exposure. But relying on backing store is irresponsible at best: such resources may or may not be available on any given X server implementation, and will almost certainly depend on the amount of backing store already committed to other clients. Backing store is intended to be used as a way of enhancing performance rather than a guaranteed property of a window.

Another alternative is to code the program in an event-driven form so that events generated by the window server – such as mouse button clicks or resizing – cause the client program to perform actions in response to those events. This is the form adopted by almost all of the standard X client programs, and by most X toolkits. However this coding style is comparatively sophisticated, and very different to the “I just want a graph of the values that my simulation produces,” requirement so often expressed by users. This requirement is the one addressed here; simplicity of use for quickly producing graphs is the principle impetus for the development of the miniXplus toolkit.

Display lists

So, to overcome the repainting problem, and to allow objects to be rescaled, and, at some later time, erased, the miniXplus toolkit uses the concept of display lists for storing information on everything drawn in its display window. Each operation that requires some graphical output to be produced generates a graphical object – such as a simple line or filled arc – which is appended to an internal list of objects to be displayed called the current display list. As a side effect of appending the object to the list, the object is painted in the window so as to support the simple conceptual model of a plotter.

To make the window “live” i.e. so that the window can respond to damage, or resize operations whilst being drawn in or when passively displaying information, miniXplus uses an additional display list handler process which is created by MxpInit. This process listens both to the X server for events and to the users application program for requests to manipulate its display lists. Using this technique, the display list process, automatically repairs damage “on the fly,” as well as augmenting the contents of the window as requested by calls to the miniXplus library routines.

For simple use, all of the display lists are hidden inside the miniXplus toolkit, but useful effects can be achieved by explicitly manipulating these lists. The toolkit maintains a list of display lists, in order of creation. After initialisation, the current display list is created, and subsequent drawing and painting operations are appended to that list.

At any time, the function MxpGetDisplayList can be invoked; this returns an opaque value or handle which can be used later to refer to that display list. The function MxpNewDisplayList creates a new current display list for subsequent drawing operations, returning the handle of the previous current display list. An entire display list, and thereby the set of objects which it paints in the window, can be deleted by simply calling the procedure MxpDestroyDisplayList with the handle of the display list to be destroyed as an argument. Beware that it is perfectly permissible to delete the current display list, but subsequent drawing operations will fail, probably fatally, unless a new current display list is established.

Each time a display list is created, the scaling, translation, colour and text attributes (the graphics state) are reinitialised, that is, newly created display lists do not inherit any properties of the previous current display list. However, if the current display list is explicitly set using MxpSetDisplayList the context at the time the list was last used is restored. Two routines are useful for manipulating the graphics state “within” the context of the current display list: MxpGSave saves a copy of the current graphics state on a push-down stack, and MxpGRestore restores the current graphics state from that same stack. Each display list has its own graphics state stack.

Persistent Windows

The routine MxpDetach can be used as an alternative to MxpClose which has the effect of allowing the display list handler process to continue to run after the users program has exited. One convenient use for this facility is when the same program is run with different input data a few times: the windows that are produced can be thought of as the results of the program since the lifetime of windows created in this way is longer than the program that created them – potentially the lifetime of the X server instance running on the machine. (This is much less complicated and more useful than it sounds!) Such a window needs to be explicitly killed before both it and the underlying display list handler process will die.

Mouse Buttons

Once something has been drawn in a window, it can be useful to wait for the user to press a mouse button without absorbing further processor time. The routine MxpWait returns when one of the mouse buttons is pressed, returning a value which represents which of the buttons was clicked. The routine MxpSync is similar to MxpWait, except that it takes an argument of how long to wait (in seconds) for a possible button press before returning.

Tracing

Because of the dual-threaded nature of a miniXplus application, it is somewhat tricky to debug such an application with, for example, xdb, at this time. In the interim, the toolkit supports various degrees of tracing and error reporting from almost none to voluminous information – see the definition of the TraceLevel type.

4 Examples

Suppose one wishes to draw a set of axes which must stay permanently on the display, then draw a graph which we wish to completely redraw at several points in a larger computation. A mouse click should be used to indicate that we have finished looking at the current graph, and want to see the next one. The template of such a program is as follows:

   ...
      FUNCTION PlotCase (...) : DisplayList;
      BEGIN
 MxpTransform (...);
         MxpPlotLines (...);
 PlotCase := MxpNewDisplayList
      END;
   VAR c : Integer;
       b : Button;
       axesDL, graphDL : DisplayList;
   BEGIN
      MxpInit (0, 0, 0, 0);
      MxpTransform (...);
      MxpXAxis (...);

MxpYAxis (...);
axesDL = MxpNewDisplayList;
FOR c := 1 TO NumberOfCases DO BEGIN
   ComputeValues (c, ...);
   graphDL = PlotCase (...);
   REPEAT
      b := MxpWait
   UNTIL b = leftButton;  {i.e. do next case on left button click}
MxpDestroyDisplayList (graphDL)
END;
MxpClose
END;
...

This program fragment operates in the following way. First, we open and initialise the miniXplus toolkit, draw a set of x and y axes on the plot, then ask the toolkit for the handle on the current display list i.e. the list that contains just the axes, creating a new current display list at the same time. Then we work through the cases required. Each case computes the new data, plots it, and asks the toolkit for the handle of the current display list which this time describes the plot. The routine then pauses until the left mouse button is clicked, at which point it destroys the display list which describes the old graph, and starts all over again. Here is another program fragment which allows a simple message to be displayed in the window to, say, elicit a choice from the user:

PROCEDURE action_one;
...
PROCEDURE action_two;
...
PROCEDURE action_three;
...
VAR b : Button;
BEGIN
MxpDrawString (0.25, 0.25, 0.5, 0.1, 'hit a mouse button');
b = MxpWait;
MxpDestroyDisplayList (MxpNewDisplayList);
CASE b OF
leftButton:   action_one;
middleButton: action_two;
rightButton:  action_three
END
END;

The display list mechanism is quite powerful, and reasonably natural once you get used to the idea the miniXplus is constructing and displaying these lists all the time. Display lists can be used for presenting multiple graphs in one window, for producing simple button/text dialogues, and for performing basic animation.

5 Changes from previous versions of miniXplus

This document describes version 1.0 of miniXplus. Version 0.1 was used in the 1989 MPhil speech processing course. Version 0.4 was used in the 1990 Part 1B software engineering project. Since that time, the display list facilities have changed semantics, the notion of saving and restoring the graphics state has been added (MxpGSave and MxpGRestore), and the MxpViewPort routine has been renamed MxpTransform which better describes its function and avoids confusion with similarly named concepts in other graphics packages. MxpDetach is another new routine which reflects the multi-threaded nature of the current implementation, and leads to the idea of persistent windows as output.

In short, most existing routines will need to be changed before recompilation. However, as the library is becoming more widely used the interface is less likely to change in future, so this version, a near-relative of version 0.4, is considered to be version 1.0

6 Closing Remarks

Other examples of the use of miniXplus in Pascal are available in the Examples directory of the Toolbox. Whilst we’re always ready to listen to suggestions for improvements to both the documentation and the library, please bear in mind that a key goal of the design of miniXplus is simplicity for basic use. Thus we’re unlikely to provide support for menus, scrollbars, buttons or other widgets – these are already available in other X Toolkits in a more suitable form e.g. in the XView toolkit. Please mail suggestions and bug reports to tpl@uk.ac.cam.eng.

7 Module Definition

MODULE mXplus;
{ Yet another simple interface to X11, Version 04/10/90 }
EXPORT
{====================================================================}
{                   Part 1 - Basic Interface                         }
{====================================================================}
{------------------- General Constants and Types --------------------}
TYPE  Coordinate = LongReal;
Angle      = LongReal;
NaturalNum = 0 .. maxint;
Str255     = String[255];
{---------------------- Starting and Stopping -----------------------}
PROCEDURE MxpInit (ScreenX, ScreenY : Integer; ScreenW, ScreenH : NaturalNum);
{Initialises the mXplus package - must be called before any other }
{mXplus routine.  ScreenX and ScreenY are the coordinate of the   }
{top left corner of the mXplus window in the standard X11         }
{coordinate system. ScreenW and ScreenH are the width and height  }
{of the window respectively in units of pixels.  If either of     }
{the width or height are zero, the window is created with a       }
{default size, centred in the corresponding dimension(s).         }
PROCEDURE MxpClose;
{Closes down the mXplus window, freeing all the associated        }
{resources before returning.  mXplus can only be restarted using  }
{MxpInit.                                                         }
{-------------------------- Debugging -------------------------------}
TYPE  TraceLevel  = ( traceOff,      {all tracing disabled}
traceSync,     {X routines called synchronously}
traceErrors,   {non-fatal errors reported}
traceDrawCmds, {all draw commands reported}
traceAll,      {voluminous information }
traceTotal     {of interest to miniX+ maintainers}
 );
PROCEDURE MxpSetTrace (level : TraceLevel);
{Set the trace level  - see the definition of TraceLevel above.   }
{The default is traceSync.  When tracing of draw commands is      }
{enabled, a delay is inserted between each command.               }

{------------------- Setting the current context --------------------}
{                               (0.0,1.0)             (1.0,1.0)   }
{                                   +---------------------+       }
{All coordinates are a pair of      |                     |       }
{real numbers, with the direction   |                     |       }
{of increasing x and y as shown     |                     |       }
{in the diagram opposite.           +---------------------+       }
{                               (0.0,0.0)             (1.0,0.0)   }
{                                                                 }
{The default translation and scaling parameters are such that the }
{coordinates shown in the diagram fill the entire window.         }
PROCEDURE MxpTranslate (x, y : Coordinate);
{Translate future drawing operations on the current display list  }
{by (x, y) in the current coordinate frame.                       }
PROCEDURE MxpScale (xscale, yscale : Coordinate);
{Scale future drawing operations on the current display list      }
{by xscale in the x direction and yscale in the y direction in the}
{current coordinate frame.  Note that both translation and scaling}
{operations are cumulative.                                       }
PROCEDURE MxpTransform ( x,  y,  w,  h : Coordinate;
ux, uy, uw, uh : Coordinate);
{Perform translation and scaling operations such that the         }
{rectangle of width w and height h with bottom left corner at     }
{coordinate (x,y) in the old coordinate system is mapped to the   }
{rectangle of width uw and uh with bottom left corner at (ux,uy)  }
{in the new coordinate system.                                    }
TYPE  Colour     = (white,
 aquamarine, blue, brown, cyan, gold, green, grey,
 magenta, maroon, navy, orange, pink, plum, red,
 tan, turquoise, violet, yellow,
 black);
PROCEDURE MxpSetColour (c : Colour);
{Set the current colour to c                                      }
{-------------------- Drawing graphics primitives -------------------}
PROCEDURE MxpDrawLine (x0, y0, x1, y1 : Coordinate);
{Draw line from (x0,y0) to (x1,y1)                                }
PROCEDURE MxpDrawRect (x, y, w, h : Coordinate);
{Draw rectangle with bottom left corner at (x,y) of width w       }
{and height h.                                                    }
PROCEDURE MxpDrawArc (x, y, w, h : Coordinate;

startAngle, arcAngle : Angle);
{Draw an arc subtending arcAngle degrees, with sweep starting at  }
{startAngle degrees from x axis.  The size of the arc is fixed    }
{by the imaginary rectangle with bottom left corner at (x,y)      }
{of width w and height h into which the arc would just fit if     }
{arcAngle=360.                                                    }
{                                                                 }
{   Example:    MxpDrawArc (x, y, w, h, 0.0, 90.0)                }
{         -- the NorthWest quadrant of an ellipse of x-diameter   }
{            w and y-diameter h                                   }

PROCEDURE MxpFillRect (x, y, w, h : Coordinate);
{As for MxpDrawRect but rectangle is filled-in with the current   }
{colour                                                           }
PROCEDURE MxpFillArc (x, y, w, h : Coordinate;
startAngle, arcAngle : Angle);
{As for MxpDrawArc but arc is filled-in with the current colour   }
{------------------------- Drawing text -----------------------------}
{mXplus tries to use the limited set of fonts available to         }
{provide text which scales with the size of the window.  to        }
{achieve this, the text routines require a bounding box to be      }
{specified as a way of locating the text, and working out, where   }
{and how big it should be in proportion to the rest of the window. }
PROCEDURE MxpDrawString (x, y, w, h : Coordinate; s : Str255);
{Draw string s centred in bounding rectangle situated at          }
{coordinate (x,y), of width w and height h.                       }
PROCEDURE MxpDrawInteger (x, y, w, h : Coordinate; value : Integer);
{Print integer value in bounding rectangle at coordinate (x,y) of }
{width w, and height h.                                           }
PROCEDURE MxpDrawReal (x, y, w, h : Coordinate; value : LongReal);
{Print real value in bounding rectangle at coordinate (x,y) of    }
{width w, and height h.                                           }
{-------------------------- Plotting data ---------------------------}
PROCEDURE MxpPlotPoints (xmin, xmax : LongReal;
VAR ydata : ARRAY[lo..hi : Integer] OF Real;
st, en : Integer);
{Plots ydata[st..en] in current coordinate system, assuming that  }
{the data values are linearly sampled from the range xmin to xmax.}
{The data are plotted as small `+' symbol in the current colour   }
PROCEDURE MxpPlotLines (xmin, xmax : LongReal;

VAR ydata : ARRAY[lo..hi : Integer] OF Real;
st, en : Integer);
{Like MxpPlotPoints but points are connected by lines instead.    }
PROCEDURE MxpPlotXYPoints (VAR xdata : ARRAY[xlo..xhi : Integer] OF Real;
VAR ydata : ARRAY[ylo..yhi : Integer] OF Real;
st, en : Integer);
{Plots ydata[st..en] against xdata[st..en] in current coordinate  }
{system.  The data are plotted as a small `+' symbol in the       }
{current colour.                                                  }
PROCEDURE MxpPlotXYLines (VAR xdata : ARRAY[xlo..xhi : Integer] OF Real;
VAR ydata : ARRAY[ylo..yhi : Integer] OF Real;
st, en : Integer);
{Like MxpPlotXYPoints but points are connected by lines instead.  }
{------------------------- Drawing axes -----------------------------}
PROCEDURE MxpXAxis (xlo, xhi : Coordinate;
 nxTicks  : Integer;
 y, ticksize : Coordinate;
 title : Str255;
 labello, labelhi : Coordinate);
{Draw an x axis, between (xlo,y) and (xhi,y) in the current       }
{coordinate system.  The axis should have nxticks of length       }
{ticksize, and a title string which is placed under the axis.  The}
{ticks themselves are labelled with values ranging from labello to}
{labelhi.  Note that the routine uses the ticksize parameter for  }
{working out where to place the labels and title, and this may    }
{need some careful tweaking for best results.                     }
PROCEDURE MxpYAxis (ylo, yhi : Coordinate;
 nyTicks  : Integer;
 x, ticksize : Coordinate;
 title : Str255;
 labello, labelhi : Coordinate);
{Draw a y axis, between (x,ylo) and (x,yhi) in the current        }
{coordinate system.  The axis should have nyticks of length       }
{ticksize, and a title string which is placed to the left of the  }
{axis.  The ticks themselves are labelled with values ranging from}
{labello to labelhi.  Note that the routine uses the ticksize     }
{parameter for working out where to place the labels and title,   }
{and this may need some careful tweaking for best results.        }
{----------------------- More context setting -----------------------}
PROCEDURE MxpSetFloatFormat (fieldWidth, decimalPlaces : NaturalNum);
{Sets the current format for the routines that print floating     }
{point numbers such as MxpDrawReal, MxpXAxis, and MxpYAxis.       }
{If either parameter is set to zero, an implementation dependent  }

{default is used.  The initial value is 2 decimal places, and as  }
{large a fieldwidth as is needed i.e. MxpFloatFormat (0, 2)       }
TYPE Gravity = ( NoGravity,          {the default}
TopGravity,
BottomGravity,
LeftGravity,
RightGravity
);
PROCEDURE MxpSetStringGravity (stringGravity : Gravity);
{To handle the cases where the centring algorithm for placing text}
{doesn't work too well, the current string gravity can be changed }
{from the default value of `NoGravity.' This allows the user to   }
{specify which wall of the text bounding box is to be treated as a}
{hard limit which text will not cross.                            }
{--------------- saving and restoring graphics state ----------------}
PROCEDURE MxpGSave;
{Save current graphics state.  The state includes the current     }
{scale and offset, colour, text gravity, and floating point       }
{format.  Saving the state simply pushes a copy of the current    }
{values onto a stack so that they can be restored later.          }
PROCEDURE MxpGRestore;
{Restore the graphics state from the state stack.                 }
{====================================================================}
{               Part two - User input from mouse buttons             }
{====================================================================}
TYPE Button = ( noButton,
leftButton, middleButton, rightButton
);
FUNCTION MxpWait : Button;
{Holds Display.  User can move/resize windows etc.  Mouse click   }
{in window causes function to return with an indication of        }
{which button was pressed.                                        }
FUNCTION MxpSync (pauseTime : NaturalNum) : Button;
{The routine waits for up to pauseTime seconds, checking that the }
{display is up-to-date as in MxpWait, and returns an indication   }
{of which, if any, of the mouse button has been pressed. }
{This is suitable for use when refreshing the screen during the   }
{presentation of a sequence of graphical output.                  }
{====================================================================}
{                Part three - Display list management                }

{====================================================================}
{All graphical operations take place in mXplus by putting simple  }
{descriptions of graphical objects on a display list, then asking }
{the package to display all of its lists.  For simple use, this   }
{operation is hidden from the user, but in the case where objects }
{(such as graphs or prompts) are to be erased or updated during,  }
{say, the progress of a computation, more explicit control over   }
{the creation and destruction of display lists is available.      }
TYPE DisplayList = ^Integer;
{A DisplayList is actually a pointer to an opaque object, the     }
{internals of which are not visible outside the package.          }
FUNCTION MxpNewDisplayList : DisplayList;
{Initializes a new display list and makes it the current one.     }
{Note that this initializes the current colour, scale, offset,    }
{floating point printing format and string gravity.  There is an  }
{implicit call of MxpNewDisplayList in MxpInit, so this routine   }
{does not need to be used for simple plots.  The previous current }
{DisplayList is returned.                                         }
PROCEDURE MxpDestroyDisplayList (dl : DisplayList);
{Destroys a DisplayList, unpainting all the graphical objects on  }
{the list, and repainting the holes left behind as it goes.       }
FUNCTION MxpGetDisplayList : DisplayList;
{Returns the current DisplayList.                                 }
PROCEDURE MxpSetDisplayList (dl : DisplayList);
{Set the current Display List to a previously created one.        }
{This also restores the context associated with the old display   }
{list.                                                            }
{====================================================================}
{               Part four - controlling window lifetime              }
{====================================================================}
PROCEDURE MxpDetach;
{Sets current mXplus window free from current process.  The window}
{will continue to display it's existing Display Lists until it is }
{explicitly killed, but those Display Lists are thenceforth not   }
{accessible i.e. it is as if MxpClose has been called.  Further   }
{mXplus windows can be created using MxpInit once more.           }
{====================================================================}
{                     that's all (for now) folks..                   }
{====================================================================}