Drawing ======= In WPY, only a CScrollView object can be drawn on. The only other view available is a CEditView, and a CEditView does not receive an OnDraw() message. Instead, it manages its own drawing internally. Other drawable views such as CView may be added in the future, but for now CScrollView is generally useful, since the scroll bars can be turned off if you don't need them. Note that dialog boxes can not be drawn to, although adding static controls (like text) achieves the same result. All drawing in wpy is done to a "device context", an object representing the screen, a printer or a bitmap. A device context or "DC" is associated with each view object you have. A DC contains a number of drawing tools such as pens, brushes and fonts. All drawing methods use these tools. For example, if you draw a line, the current pen is used. When you draw to a DC, the system prepares the DC for the type of output device (screen, printer, etc.) and it uses the drawing tools you have selected for that DC. Although a wpy device context is based on a Windows DC, drawing in wpy is much simpler. A wpy DC is not shared with any other application or object, so it is not necessary to replace drawing tools with the original tools in order to return the DC to its original configuration. When running on Windows, the system supplies this logic as required. Also, any tools selected into a device context stay there until replaced, so it is often unnecessary to keep replacing tools. Windows would return a DC with default tools for each call. Drawing in wpy is done in response to the view's OnDraw(self, DC) message. You must always override this view method to display your view of the document. When OnDraw(self, DC) is called, the whole view has already been erased. So your OnDraw() method must re-draw the whole view by calling the drawing methods of the DC. An OnDraw() message is sent when the view is first displayed. After that, the WPY system will refresh the screen as required using the objects you have drawn to the view, and OnDraw() will never be called again, except: 1) OnDraw() will be called whenever there are no drawn objects available and the WPY system needs to update the screen. Your OnDraw() should return promptly if there is nothing to draw. 2) If you call the view's method InvalidateRect(), the entire view is erased and OnDraw() will be called again. But you should really call the document's UpdateAllViews() instead of InvalidateRect(). UpdateAllViews() will call OnUpdate() for each view, and the base class OnUpdate() calls InvalidateRect(). Using UpdateAllViews() instead of InvalidateRect() is better because it is easier to add selective updates, and it is easier to add views. If you have a view method OnPrint(self, DC, pagenum), then it will be called when you are printing (see print.txt). If there is no such method, then the same OnDraw() mechanism is used when drawing to a printer. If you need to distinguish between the screen and a printer from within OnDraw(), check the DC.wpyIsPrinting attribute. Although the DC is a printer DC, the same code will draw to the printer just as it draws to the screen. But you must be careful to draw in a device-independent way. For example, check the sizes of drawn objects as you go along instead of assuming they have the size they would have on the screen. Remember that screens have about 70 pixels per inch, but printers have 300 (or 600). So layout must be done using attributes of the DC. If you change an attribute of a DC the change will persist, and each time you are given a DC it will have the tools last assigned to it. For example, if you change the pen in the DC with a call to SelectObject(), the next OnDraw() message will have a DC with the changed pen. This makes it easy to fix a pen, brush, font etc. in a DC once and for all. But if you want to change a pen to a different pen, you must call SelectObject() for each change. If you never call SelectObject, a default drawing tool is used. When you draw an object with DrawText(), LineTo() etc. the return value is an instance of CDrawnText, CDrawnLine etc. which represents the object as drawn on the view. You can change certain attributes of the object, for example, text color, by assigning to the object's attributes. You can move the object by assigning to the object's wpyLocX and/or wpyLocY. You must call the view's Redraw() method for these changes to be visible. Using Redraw() is ofter easier and faster than using InvalidateRect() to cause another call to OnDraw(). The drawn objects have wpyLocX/Y and wpySizeX/Y and other attributes depending on the object drawn. The wpyLocX/Y and wpySizeX/Y are the bounding box for the drawn object, not the coordinates used to create the object. For example, if you draw a rectangle, the location and size are larger than the rectangle coordinates by half the pen width because the outline is drawn centered on the coords. The CScrollView methods GetDrawnObject() and GetDrawnObjectList() are available to return the drawn objects given the view coordinates (x, y). This may be used, for example, to respond to a mouse click over a piece of text in a web browser. GetAllDrawnObjs() returns all the drawn objects as a list. It is sometimes necessary to draw other than in response to an OnDraw message. For example, you must draw to the view directly to draw lines or boxes with the mouse. In this case, call DC = view.GetDC() to return the DC associated with the view. Then call the drawing methods of the DC as before. The tools will be the same as the tools used for OnDraw(). You must call view.ReleaseDC(DC) as soon as you are done drawing, and you must not save a copy of DC for later use. You must call ReleaseDC before you exit the current function. It is a "feature" of Windows that this DC is a volatile pointer and you must call GetDC and ReleaseDC very frequently. Since you will probably be drawing with the mouse to need this feature, you will have a call to GetDC and ReleaseDC at the top and bottom of OnMouseMove(). Updating the View ================= The simplest drawing scheme for a WPY app is to draw the whole view in OnDraw() and call UpdateAllViews() to erase the whole view when anything changes. If OnPrint() is omitted, then your OnDraw() is also used for printing. This works surprisingly well, since OnDraw() is only called once, and the WPY system refreshes the screen as required. You should probably try this method first and see if it is good enough. Unfortunately it may prove to be too slow for large views with many drawn items which require small updates. It may be necessary to update the individual drawn items in the view when the document changes to achieve acceptable speed. This is harder because while the DC is available in OnDraw(), it is not available at an arbitrary point where the change to the document is made. Also the sizes of the drawn objects varies depending on the DC. For example, if a certain font is specified, and the DC is a printer DC which lacks the font, then a different font will be used instead. This is in addition to the fact that the resolutions are different for the screen and the printer. So the pixel locations and sizes for the drawn objects depend on the DC. First recall that the document has the method UpdateAllViews() which should be called when the document changes so that all views can re-draw themselves. UpdateAllViews() calls OnUpdate() for each view. The base class OnUpdate() calls InvalidateRect() which would erase the view. So first make sure you are calling UpdateAllViews() when the document changes, and then override the OnUpdate() method of the view to perform the update. UpdateAllViews(self, sender, update) will call OnUpdate(self, sender, update) for all views not equal to sender. This is intended to alert views other than the sending view which presumably already redrew itself. Use None for sender to cause OnUpdate() to be called for ALL views. The "update" object is meant to contain hints about what changed so that the views do not have to examine the document looking for changes. Any Python object is acceptable, and tuples are often convenient as are instances. For example, if a line of something in the document changed, the "update" could be the line number, the line object or both. The default update is None, which means that no update information is available. WPY has a number of methods which enable you to alter the drawn items on the view without access to the DC. The WPY system takes care of screen updates itself after you have made changes. These methods are: view::Redraw(self, obj) re-displays the drawn object after you have altered its attributes, such as text, text color, size, location. view::AddDraw(self, obj) adds a drawn object to the view. This is less useful than calling the drawing method directly, but can be used to copy drawn objects from a different view, for example, when making a view which is a copy of another view. view::DestroyDrawn(self, obj) removes an object from the view. view::DestroyAllDrawn(self) erases the view. Not needed with OnDraw() because the view is always erased before OnDraw() is called. Using the above tools, there are (at least) two schemes for updating less than all of the view. You will probably need to use a separate OnPrint() method (instead of using OnDraw() for printing) to make things simpler. A seperate OnPrint() is needed for multi-page printing anyway so this hardly matters. First, if the sizes of drawn objects are known without using the DC, perhaps because sizes were saved from the call to OnDraw() or because you are always drawing to the screen using its known resolution, then you can just manipulate drawn objects directly with Redraw(), DestroyDrawn(), etc. If you really need the DC to get accurate drawn object sizes, then you can use GetDC() and ReleaseDC() for the view within OnUpdate() to get the DC for the view. Be sure an exception does not cause ReleaseDC() to fail to be called. You will definitely need an OnPrint() for printing. Even if you are using OnUpdate() to make updates to the screen, you still need an OnDraw() which will re-draw the entire view. If MyDraw(self, update, line_start, line_end) is your drawing utility which draws lines in line_list[], then OnDraw should call it as follows: def OnDraw(self, DC): self.MyDraw(None, 0, len(line_list)) Note that an update of None (the default) means there is no specific update information was available. Make sure you do not add multiple identical drawn objects. Be sure to destroy drawn objects as required. Multiple identical objects will look OK on the screen, but will reduce efficiency. class CDC Class CDC represents a device context. See above. Device contexts are never constructed by the user. They are made available as method arguments. Instance Variables wpyIsPrinting, int 0/1 if OnDraw() is called for printing. Only valid within OnDraw(). wpySizeX/Y, int The size of the display surface. wpyOneMeter, int The number of pixels in one meter. wpyPrintPoint, float The number of pixels in a printer's point ( 1/72 inch). wpyTextColor, 3-tuple The color to draw text, as the tuple (red, green, blue). The colors have values from 0 to 255. Methods DrawText(self, text, x, y, width = 0, justify = "left") The return value is a CDrawnText instance. text, string Text to draw. x, y, int The location to draw the text in pixels. The anchor is "nw", the upper left corner. width, int If specified, the width in pixels for the text. If text exceeds this width, it is broken into lines at a blank. Otherwise, the text can be any length. A newline always breaks the text. If width is specified, text is formatted within width. If width is not specified, the returned size just bounds the text. justify, one of "left", "right", "center" How to display multiline text. Has no effect unless width is entered too. GetTextExtent(self, text) string text Return the size of the text as the tuple (width, height). Needed to do text layout and drawing. Within view::OnDraw(), you must call this with the device context given, as "DC.GetTextExtent()". This method may also be called for a window, and it uses the window's device context. Do not use "self.GetTextExtent()" within OnDraw(). LineTo(self, x, y) The return value is a CDrawnLine instance. x, y, int The ending location of the line, and the new current location. Draw a line using the current pen from the current location (see MoveTo) to the specified location, and update the current location. MoveTo(self, x, y) Change the current location to (x, y). Arc(self, x, y, w, h, start, extent): Draw an arc made of the ellipse in the rectangle x, y, w, h, starting from the angle "start" counter clockwise through the angle "extent" in degrees. The return value is a CDrawnArc instance. Chord(self, x, y, w, h, start, extent): Draw a chord made of the ellipse in the rectangle x, y, w, h, starting from the angle "start" counter clockwise through the angle "extent" in degrees. The return value is a CDrawnArc instance. Circle(self, x, y, radius): Draw a circle centered at (x, y) with the given radius. The return value is a CDrawnEllipse instance. Ellipse(self, x, y, w, h): Draw an ellipse in the rectangle specified. The return value is a CDrawnEllipse instance. Pie(self, x, y, w, h, start, extent): Draw a pie slice made of the ellipse in the rectangle x, y, w, h, starting from the angle "start" counter clockwise through the angle "extent" in degrees. The return value is a CDrawnArc instance. Polygon(self, points): Draw a closed polygon made of the points in the list "points" consisting of tuples (x, y). The return value is a CDrawnPolygon instance. Rectangle(self, x, y, w, h): The return value is a CDrawnRectangle instance. x, y, w, h, int Draw a rectangle with "w" width and "h" height at (x, y) using the current pen and brush. DrawImage(self, image, x, y) The return value is a CDrawnImage instance. image, a CImage instance. The image object to draw. x, y, int The location to draw the image in pixels. The anchor is "nw", the upper left corner. SelectObject(self, object) object, a drawing object such as a pen or font Replace the current drawing object in the device context with the specified new drawing object, and return the old object. SetTextColor(self, color) Change the color to draw text. "Color" is the tuple (r, g, b). Return the previous color. class CBrush Class CBrush represents a brush, a drawing tool. Instance Variables wpyRed, wpyGreen, wpyBlue, int 0 to 255, readonly after Create() The color of the brush. Methods __init__(self, rgb = (0, 0, 0)) Construct a brush with the color given. If the color is specified as (), then the brush is a hollow brush and there is no fill color. Create(self) Create the brush using the underlying GUI. Return self. class CPen Class CPen represents a pen, a drawing tool. Instance Variables wpyWidth, int, readonly after Create() The width of the pen in pixels. wpyRed, wpyGreen, wpyBlue, int 0 to 255, readonly after Create() The color of the pen. wpyStyle, int, readonly after Create() The style of the pen. Methods __init__(self, width = 1, rgb = (0, 0, 0), style = wpycon.PS_SOLID) Construct a pen with the attributes given. Create(self) Create the pen using the underlying GUI. Return self. Class CFont Class CFont represents a font, a drawing tool. Instance Variables wpyFamily, wpyHeight, wpyWeight Equal to the __init__ arguments. Methods __init__(self, family, height = 0, weight = wpycon.FW_NORMAL) family, string The general type of font. It must be one of the strings: "swiss" A proportional font without serifs such as Helvetica. "roman" A proportional font with serifs such as Times Roman. "modern" A fixed width font such as Courier. The family string can be followed by "italic", as in "roman italic". A feature to specify a more specific font will be added later, but the "family" will still be required in case the more specific font is not available. height, int The size of the font in printer's points. Or use zero to specify a default size. weight, int The weight of the font; that is, how dark it is. For example, FW_NORMAL and FW_BOLD are available on most systems. Create(self) Create the font using the underlying GUI. Return self. GetTextExtent(self, text) Return the text size on the screen of "text" as (sx, sy). Bugs: The function used to map fonts to X fonts when using Tk is primitive. The response of Tk when a non-existant font is requested is also primitive. Class CImage represents a color bitmap. It is not a drawing tool. Instance Variables wpySizeX/Y, int, readonly after Create() The size of the bitmap in pixels. Methods __init__(self, filename = None) Construct an image object, record the filename. Create(self) Create the image using the underlying GUI. Initialize the bitmap from the filename (currently the only bitmap creation method). The size of the bitmap is self.wpySizeX/Y. If there was an error in creating the bitmap, the size is (0, 0). You should test the size before using the CImage object. The file formats currently available are whatever Tk supports for Unix (currently GIF and PPM), and BMP, DIB, GIF and PPM on Windows. The file name extension is used to decide what read method to use, so the file name must end in one of the above extensions, ".gif" etc. Drawn Object Classes ==================== Instances of these classes are returned by the drawing methods. class CDrawnLine Class CDrawnLine represents a line drawn by LineTo(). Instance Variables wpyLocX, wpyLocY, wpySizeX, wpySizeY The bounding box for the line. wpyStartX, wpyStartY, wpyEndX, wpyEndY The starting and ending points. wpyPen The pen in effect in the DC when the text was drawn. The following attributes can be changed and then re-drawn with view::Redraw(): wpyPen class CDrawnArc Class CDrawnArc represents an arc, pie slice or chord. class CDrawnEllipse Class CDrawnEllipse represents an ellipse drawn by Ellipse() or a circle drawn by Circle(). class CDrawnPolygon Class CDrawnPolygon represents a polygon drawn by Polygon(). You may not change wpyLocX nor wpyLocY for this object. Remove and redraw instead. class CDrawnRectangle Class CDrawnRectangle represents a rectangle drawn by Rectangle(). Instance Variables wpyLocX, wpyLocY, wpySizeX, wpySizeY The bounding box for the object. wpyPen The pen in effect in the DC when the text was drawn. wpyBrush The brush in effect in the DC when the text was drawn. The following attributes can be changed and then re-drawn with view::Redraw(): wpyPen, wpyBrush class CDrawnText Class CDrawnText represents a piece of text drawn by DrawText(). Instance Variables wpyLocX, wpyLocY, wpySizeX, wpySizeY The bounding box for the text. wpyText The text drawn. wpyTextColor The text color in effect in the DC when the text was drawn as (r, g, b). wpyFont The font in effect in the DC when the text was drawn. The following attributes can be changed and then re-drawn with view::Redraw(): wpyText, wpyFont, wpyTextColor BUG: The effect of redrawing multi-line text is undefined. GetIndexXY(self, view, x, y) Return the index in self.wpyText (the text drawn) of the character located closest to the point (x, y) in view coordinates. Use this to figure out which character is located under the mouse, for example. NOTE: This only works for a single line of text, as the system is (currently) not aware of where multiline text was broken. class CDrawnImage Class CDrawnImage represents an image drawn by DrawImage(). Instance Variables wpyLocX, wpyLocY, wpySizeX, wpySizeY The bounding box for the image. The following attributes can be changed and then re-drawn with view::Redraw(): None. Class CView These are the methods of the view classes which are used for drawing. See the file view.txt. InvalidateRect(self) GetDrawnObject(self, x, y) GetDrawnObjectList(self, x, y) GetAllDrawnObjs(self) Redraw(self, object) DestroyDrawn(self, obj) DestroyAllDrawn(self) AddDrawn(self, obj)