SWT Step 3. Basics of GUI, part 2

Objective

Implement basic visualization facilities such as primitive drawing. In particular, this means the implementation of o.e.swt.graphics.GC [#Base] class. The test example of this step should be able to create an SWT top window and draw some graphic primitives in it.

Task notes

Frame client window (obsolete)

In OS/2, as opposed to Windows (at least from the API point of view), the client part of the frame window (the area where the custom drawing takes place) is the separate window with its own window class and procedure. SWT uses the Windows approach and therefore the frame window (i.e. Decorations and Shell widgets) should encapsulate the client window functionality to be as a whole for the user. To achieve this the client window, an instance of the special ClientArea class (which is derived from the Composite), is anonymously created by the Decorations.createHandle() method. "Anonymously" means that this instance is unaccessible anywhere outside the Decorations class. The special feature of the ClientArea class is that it uses its parent's (i.e. the Decorations) event table and, when sending or posting messages, sets the Event.widget field to the value of parent rather than this. This means that all events (specifically, the SWT.Paint event) generated by the client window look as Decorations events to the user (and directed to listeners registered on the Decorations instance).

Window presentation space

The presentation space handle to do low level painting in the window is obtained via the Control.internal_new_GC() method and must be freed by the Control.internal_dispose_GC() method when no more needed. The logic of work of these two methods is as follows:

WM_PAINT message

In the Windows version, the WM_PAINT handler uses the device context obtained via the BeginPaint() API call, which initializes it with default attributes. To setup the correct graphics context (a pen, brush, etc.) the GC.win32_new() method is called every time when the WM_PAINT message occurs. Although the same technique can be used in OS/2 too (and the existing code is ready to do it) at the present time the "long-term" presentation space is used to save attributes between WM_PAINT messages (and for some other purposes). This presentation space is allocated only once (lazily in the WM_PAINT handler) for every Control widget which has at least one SWT.Paint event listener registered, and it is released when the widget is being destroyed. However, the Composite class (and its descendants, of course) allocates such presentation space even if no SWT.Paint listeners are registered, to draw the background (this behavior may change in the future). This approach should lead to a better performance since no calls to setup attributes on every paint message (except the first one) are made (the GCData.doInit field is used by GC.pm_new() and the WM_PAINT handler to indicate the need to setup the presentation space). At the same time, this should not cause the increase of memory usage because the presentation space is obtained via the WinGetPS() call, which returns the cached presentation space.

Besides other things, the long-term presentation space is used to store the default view transformation matrix to do the automatic coordinate space flipping when using Gpi* functions for painting (see also here). It allows to update the matrix only when the window is resized instead of doing it on every paint message.

Graphic primitives (obsolete)

There is some muddle with dimensions of graphic primitives in the Win32 version of SWT: for example, the javadoc says that GC.drawRoundRectangle()draws a rectangle, those left and right edges are at x and x + width, top and bottom edges are at y and y + height. But it's not the case -- actually, the right edge is at x + width - 1, and the bottom one is at y + height - 1, i.e. the horizontal and vertical size is one pixel smaller than it stated. Besides this, all fill* versions of primitives draw figures one pixel smaller in size than their draw* versions, i.e. the rectangle drawn by GC.fillRectangle(0,0,10,10) is not the same in size as by GC.drawRectangle(0,0,10,10) -- the size of the former is 11 by 11 pixels while the size of the latter is 10 by 10. The docs just say that fill* versions fill the interior of a primitive, but we see no reasons for the interior to be one pixel smaller than the outline (although it's an expected behavior of the corresponding Win32 API calls, but not OS/2 ones). Also, there are errors when drawing very small figures (few pixels in size) and figures with the negative width and/or height (see the SWT003_01 example). Most of these things look just like bugs, so in OS/2 version we decided to fix them until any incompatibility issue appears. "To fix" here means that the bounding rectangle of all primitives in the OS/2 version of SWT always follows the rule stated in the javadoc for GC.drawRectangle() about the edges and covers an area width + 1 pixels wide by height + 1 pixels tall.

Colors

In the Win32 version of SWT, if the device is in the palette mode (that is, when it can display only 256 distinct colors simultaneously) and we try to create new colors inside the paint event listener (as we draw in a window) these colors will not appear on the screen until the next window invalidation (see the SWT003_02 example). It is because new colors are being added to the physical palette when it's already selected to the device context at the beginning of the paint event handling and won't be selected again until the next paint event. In the OS/2 version we solve this problem by posting the WM_REALIZEPALETTE message to every Shell widget when the new color is created, which causes widgets to invalidate themselves.

Styled lines

In Windows, when drawing styled lines (dotted, dashed etc.) intervals between sytle segments are considered to be the "background" of the line and therefore are drawn by the current brush (see the SWT003_01 example). In OS/2 they are considered to be transparent and this behavior cannot be altered. Due to the inefficiency of the only possible application-level implementation of this functionality it is left as is at the present.

Step checklist

Operation Status Remarks
Implement OS.WinWindowFromID() and FID_* constants Done [dmik]
Implement the frame client window creation and the message routing for it Done [dmik]
Add OS.GpiCreateRegion(), WinQueryUpdateRegion(), WinInvalidateRegion() and RGN_* constants Done [dmik] The RECTL structure (which is originally passed as a pointer) is passed as an int array (with four elements) from Java to simplify access to it from the native implementation.
Add OS.GpiQueryRegionRects(), RGNRECT class, RECTDIR_* constants Done [dmik]
Implement WM_PAINT message handling and SWT.Paint event firing Done [dmik]
Implement OS.WinGetMaxPosition() Done [dmik] Used to implement the Display.getClientArea() method, which returns the size and position of maximized windows of the desktop. These values can differ from Display.getBounds(), e.g. when XCenter is running. Note: XCenter 1.0.1 has a strange feature -- it reduces the maximum height (reported by WinGetMaxPosition()) by the value of its own height plus additional 5 pixels, therefore the reported maximum height is 5 pixels less than the actual. Possibly, it's a bug.
Implement Device.getBounds(), getClientArea() for Device, Display and controls that support this Done [dmik] Scrollable.getClientArea() code is temporary (should take the scrollers into account)
Add OS.GpiMove, OS.GpiBox, GPI_* and DRO_* constants, implement GC.{fill|draw}[Round]Rectangle() methods Done [dmik] POINTL structures are passed as arrays of int with two elements (x and y).
Add WM_CALCVALIDRECTS message handling, OS.CVR_* constants Done [dmik] Composite instance returns CVR_REDRAW on WM_CALCVALIDRECTS if it has the CANVAS state and no SWT.NO_REDRAW_RESIZE style to completely invalidate itself when resizing.
Create the draft SWT003 testcase Done [dmik]
Fix the bug of GpiBox() Done [dmik] GpiBox() draws the rectangle 1 pixel higher than it should do, when rounded corners and DRO_FILL or DRO_OUTLINEFILL are specified.
Add the long-term presentation space creation for a Control object Done [dmik]
Implement OS.GpiSetDefaultViewMatrix(), TRANSFORM_* constants Done [dmik] Necessary for automatic coordinate space flipping.
Add OS.SHORT{1|2}FROMMP pseudo macros Done [dmik]
Add the o.e.swt.widgets.ClientArea class Done [dmik] This class represents the frame client window.
Implement OS.DevQueryCaps() and CAPS_* constants Done [dmik] Currently, constants are almost fully commented -- uncomment when needed.
Overload OS.WinSendMsg() with one, that takes OS.RECTL as the first message parameter Done [dmik] Currently used to send WM_CALCFRAMERECT message.
Add OS.GpiCreatePalette(), GpiDeletePalette(), LCOL_* and LCOLF_* constants, GpiQueryNearestPaletteIndex(), GpiQueryPaletteInfo(), GpiSetPaletteEntries(), GpiSelectPalette(), GpiCreateLogColorTable(), WinRealizePalette() Done [dmik] Note: GpiQueryNearestPaletteIndex() is undocumented. We hope it presents in Warp4 and earlier OS/2 versions...
Add OS.GpiQueryColor(), GpiSetColor(), GpiQueryBackColor(), GpiSetBackColor(), GpiQieryMix(), GpiSetMix(), FM_* constants, GpiQueryBackMix(), GpiSetBackMix(), BM_* constants, GpiQueryPattern(), GpiSetPattern(), PATSYM_* constants Done [dmik]
Add WM_REALIZEPALETTE message handling, implement {get|set}{Foreground|Background}() methods of Control and GC classes, OS.WinQuerySysColor() and SYSCLR_* constants Done [dmik]
Add the GC.Color class and implement color handling Done [dmik]
Add OS.GpiLine(), GpiBeginArea(), GpiEndArea(), GpiPartialArc(), GpiSetArcParams(), GpiQueryCurrentPosition(), implement GC.drawLine(), {draw|fill}Arc(), {draw|fill}Oval() methods Done [dmik] ARCPARAMS structure is passed to GpiSetArcParams() as an array of int[4].
Create TestCase and SWTTestCase classes to simplify test case writing, remove the SWT003 tescase and create SWT003_01 and SWT003_02 instead, update the SWT004 testcase Done [dmik] If the step requires several different testcases each with its own top shell window, it's possible to do so, naming them as SWTXXX_YY where XXX is the step number and YY is the number of the testcase. Such testcases can be run as usual, specifying the step name in the format above via the test.step property in the build.prp file or on the command line.
Add OS.GpiPolyLine(), implement GC.getBackground(), getForeground(), drawPolyline(), drawPolygon(), fillPolygon() Done [dmik]
Add OS.Gpi{Query|Set}LineType(), LINETYPE_* constants, implement GC.{get|set}LineStyle(), drawFocus() Done [dmik]
Add OS.Gpi{Begin|End|Stoke}Path(), Gpi{Query|Set}LineWidthGeom(), GpiSetLine{End|Join}, implement GC.{get|set}LineWidth, {get|set}XORMode() Done [dmik]
Readress the Decorations.redraw() methods to the client window; add OS.WinEnableWindowUpdate(), implement Control.setRedraw(); add OS.WinIsWindowShowing(), implement Control.isShowing() Done [dmik]
Add OS.GpiQueryClipBox(), GpiSetClipRegion(), GpiQueryRegionBox(), GpiCombineRegion(), GpiPtInRegion() and PRGN_* constants, GpiRectInRegion() abd RRGN_* constants; implement the o.e.swt.graphics.Region class, implement GC.{get|set}Clipping(); add testcase SWT003_03 to test clipping Done [dmik] Region class is fully implemented. However, there's a potential question about using regions with other devices than the screen (since OS/2 requires the presentation space to be specified when operating regions, the screen PS is used for this purpose).
Move the frame class and window procedure definition code from the Shell to the Decorations class Will be done on next steps Decorations is logically a frame too. And if it will be used/subclassed directly (i.e. not by the Shell) then we need this code in it.
Try to re-enable SWT.CLIP_SIBLINGS and SWT.CLIP_CHILDREN styles recognition in Control.widgetStyle(), and remove forcing them in Composite.WM_PAINT() and Composite.widgetStyle() Will be done on next steps In Windows version it is intentionally commented because: "When strict clipping was not enforced on all widgets, poorly written application code could draw outside of the control". Is it the same in OS/2? Possibly, not.