This section discusses various approaches for defining new graphical objects. XPCE offers three approaches for defining new graphical objects:
text_box(TB, Text, Width, Height) :- new(TB, device), send(TB, display, new(B, box(Width, Height))), send(TB, display, new(T, text(Text, center, normal))), send(T, center, B?center).
For some applications, this is a suitable and simple approach. However, it is not a very good approach to build a library of GUI objects or, more in general, to make generic and reusable new graphical objects. The above object does not handle resize properly, and the user has to know the internal structure to modify the object.
->
initialise'
is refined to display the part of the compound graphical. `device->
event'
and `device->
geometry'
are normally redefined to define event-handling and resize of the new
graphical object. See section 7.3.2
for details.
->
_redraw_area'
can be redefined to define the look of a graphical. We will discuss the
advantages and disadvantages of this approach in this section and give
some examples.
The most basic way to (re)define the look of a graphical object is by
redefining the method that paints the graphical. This method is called
->
_redraw_area. The method ->
_redraw_area cannot
be called directly by the user, but it is called by the graphical
infra-structure whenever the graphical needs to be repainted. The
definition of the method is below:
<-
area of the graphical.
It is not allowed for this method to paint outside the <-
area
of the receiver. There is no clipping (see ->
clip)
to prevent this. If there is no cheap way to prevent this, bracket the
graphical operations in ->
clip and ->
unclip,
but be aware that setting and undoing the clip-region is an expensive
operation. Note that is is not necessary to limit the applied
paint only inside the given argument Area. The graphical
infra-structure automatically clips all graphical operation to this
area. In general, Area should only be considered to avoid large
numbers of unnecessary drawing operations.
There are three sets of methods to implement the drawing job. The
first is `graphical->
draw',
that allows drawing other graphical objects in this place. The second
are methods to manipulate the clipping and state of the graphical
device. The last is a set of methods to realise primitive drawing
operations, such as drawing lines, rectangles, images, text, etc. These
methods can be used in any combination. It is allowed, but not
obligatory, to call the ->
send_super method in
order to invoke the default behaviour of the graphical. These methods
are summarised in table
9. Full documentation is available from the online manual.
-> draw | Paint other graphical |
-> clip | Clip to area
or <- area of graphical |
-> unclip | Undo last -> clip |
-> save_graphics_state | Save current pen and colours |
-> restore_graphics_state | Restore saved values |
-> graphics_state | Set graphics attributes |
-> draw_arc | Draw ellipse-part |
-> draw_box | Draw rectangle (rounded, filled, etc.) |
-> draw_fill | Fill/invert/clear rectangle |
-> draw_image | Paint (part of) image |
-> draw_line | Draw a line segment |
-> draw_poly | Draw a polygon |
-> draw_text | Draw string in font |
-> paint_selected | Paint
visual feedback of -> selected |
Table 9 : Methods for (re)defining -> _redraw_area |
XPCE built-in class window does not provide a grid. Implementing a grid using graphical objects is difficult. The best approach would be to display a device on the window that provides the background and displays the lines of the grid. The resize and scroll messages need to be trapped to ensure the proper number of lines are displayed with the correct length. Furthermore, the code handling the inside of the window needs to be aware of the grid. It should ensure the grid is not exposed or deleted, etc.
It is much simpler to redefine the `window->
_redraw_area'
method, paint the grid and then call the super-method. The code is
below.
:- pce_begin_class(grid_picture, picture, "Graphical window with optional variable(grid, '1..|size*' := 20, get, "Size of the grid"). variable(grid_pen, pen, get, "Pen used to draw the grid"). initialise(P, Lbl:[name], Size:[size], Disp:[display]) :-> send(P, send_super, initialise, Lbl, Size, Disp), ( get(@display, visual_type, monochrome) -> Texture = dotted, Colour = black ; Texture = none, Colour = grey90 ), send(P, slot, grid_pen, pen(1, Texture, Colour)). '_redraw_area'(P, A:area) :-> "Draw a grid":: get(P, grid, Grid), ( Grid \== @nil -> ( integer(Grid) -> GX = Grid, GY = Grid ; object(Grid, size(GX< GY)) ), send(P, save_graphics_state), get(P, grid_pen, pen(Pen, Texture, Colour)), send(P, graphics_state, Pen, Texture, Colour), object(A, area(X, Y, W, H)), StartX is (X//GX) * GX, StartY is (Y//GY) * GY, Xlines is ((W + X - StartX)+GX-1)//GX, Ylines is ((H + Y - StartY)+GY-1)//GY, ( between(1, Xlines, Xline), Xnow is StartX + (Xline-1)*GX, send(P, draw_line, Xnow, Y, Xnow, Y+H), fail ; true ), ( between(1, Ylines, Yline), Ynow is StartY + (Yline-1)*GY, send(P, draw_line, X, Ynow, X+W, Ynow), fail ; true ), send(P, restore_graphics_state) ; true ), send(P, send_super, '_redraw_area', A). grid(P, Grid:'1..|size*') :-> send(P, slot, grid, Grid), send(P, redraw). % changed? grid_pen(P, Penn:pen) :-> send(P, slot, grid_pen, Pen), send(P, redraw). % changed? :- pce_end_class.
The following example is yet another implementation of a shape filled
with text. Redefining ->
_redraw_area has several
advantages and disadvantages over the device
based implementation:
Implementing edit facilities for the text will be hard. The best
approach would be to display a normal text
object on top of the text-box and replace the <->
string
when editing is finished.
:- pce_begin_class(text_shape, graphical, "text with box or ellipse"). variable(string, char_array, get, "Displayed string"). variable(font, font, get, "Font used to display string"). variable(shape, {box,ellipse}, get, "Outline shape"). initialise(S, Str:string=char_array, Shape:shape={box,ellipse}, W:width=int, H:height=int, Font:[font]) :-> default(Font, normal, TheFont), send(S, send_super, initialise, 0, 0, W, H), send(S, slot, string, Str), send(S, slot, shape, Shape), send(S, slot, font, TheFont). '_redraw_area'(S, _A:area) :-> get(S, area, area(X, Y, W, H)), get(S, string, String), get(S, font, Font), get(S, shape, Shape), send(S, clip), % text may be bigger ( Shape == box -> send(S, draw_box, X, Y, W, H) ; send(S, draw_arc, X, Y, W, H) ), send(S, draw_text, String, Font, X, Y, W, H, center, center), send(S, unclip), send(S, send_super, redraw). :- pce_end_class.