The Design and Implementation of VIOWIN - Part 7

From EDM2
Jump to: navigation, search
The Design and Implementation of VIOWIN
Part: 1 2 3 4 5 6 7 8

Written by Larry Salomon Jr.

Introduction

For my job, I once had to write an application that ran only when OS/2 booted from the floppy diskettes. Because I had no access to the functionality PM provides, I resorted to a line-oriented interface, where messages were displayed on the screen and scrolled up when necessary. It was a good interface, I thought; it was fully NLS enabled and had intelligent defaults so the user basically only had to type in the name of the application. Unfortunately, the Quality Assurance team didn't concur with my opinion. "We want a nice interface!" one exclaimed. "Yeah, one with different windows and such!" another shouted.

I was backed into a corner that I could only get out of one way.

This series describes the design and implementation of VIOWIN, a library that implements a subset of the Win APIs provided by PM for fullscreen sessions. The reasoning behind writing this series is that it provided me and will hopefully provide you with some unique insights into how a windowing system is developed; and since it is based on PM, your familiarity with the already defined interface will increase your capability to fully understand what is being described.

Obviously, this series assumes you have PM application development experience, but it isn't required.

This Month

This month, we will look at the VWWC_STATIC class as implemented in the library.

Static Controls

VIOWIN implements two types of static controls only: text and groupboxes. These were chosen because they are the most widely used (from my observations), although other, non- graphically oriented ones could also have been implemented.

Statics, as you are well-aware, are idiot-controls - they do absolutely nothing except paint themselves. This makes our job much easier, since there is no need for instance data or complicated processing of user-input.

There are three helper functions used by the static control:

paintText() - paints the text including handling the DT_WORDBREAK style.

drawLine() - paints any one of the four bounding lines of a groupbox.

paintGroupbox() - paints the groupbox.

The paintText() Function

The paintText() function is fairly easy to digest because the hard work is done by the vwDrawText() function which was looked at earlier in this series.

VOID paintText(HVWWND hwndWnd,ULONG ulStyle)
//-------------------------------------------------------------------------
// This function paints the text for the static control.  If DT_WORDBREAK
// is set in the style bits, word-wrapping is done here also.
//
// Input:  hwndWnd - window handle
//	     ulStyle - window style
//-------------------------------------------------------------------------
{
   CHAR achText[256];
   RECTL rclWnd;
   SHORT sNumLines;
   SHORT sLine;
   ULONG ulFlags;
   SHORT sDrawn;
   SHORT sTotal;

   vwQueryWindowText(hwndWnd,sizeof(achText),achText);

   //----------------------------------------------------------------------
   // Calculate the size of our drawing area
   //----------------------------------------------------------------------
   vwQueryWindowRect(hwndWnd,&rclWnd);
   rclWnd.xRight--;
   rclWnd.yTop--;

   sNumLines=rclWnd.yTop-rclWnd.yBottom+1;
   sLine=0;

   vwFillRect(hwndWnd,&rclWnd,vwQueryBackColor(hwndWnd));

   //----------------------------------------------------------------------
   // Check our alignment flags
   //----------------------------------------------------------------------
   ulFlags=0;

   ulFlags|=(ulStyle & (DT_LEFT | DT_CENTER | DT_RIGHT));
   ulFlags|=(ulStyle & (DT_TOP | DT_VCENTER | DT_BOTTOM));

   //----------------------------------------------------------------------
   // Add the DT_WORDBREAK flag if it exists and only if DT_LEFT and
   // DT_TOP were specified
   //----------------------------------------------------------------------
   if (ulFlags==(DT_LEFT | DT_TOP)) {
     ulFlags|=(ulStyle & DT_WORDBREAK);
   } /* endif */

   vwQueryWindowRect(hwndWnd,&rclWnd);
   rclWnd.xRight--;
   rclWnd.yTop--;

   rclWnd.yTop-=sLine;
   rclWnd.yBottom=rclWnd.yTop-1;

   //----------------------------------------------------------------------
   // Draw the first line of text and keep track of how many characters
   // of the total were drawn.
   //----------------------------------------------------------------------
   sTotal=0;

   sDrawn=vwDrawText(hwndWnd,
			   -1,
			   achText,
			   &rclWnd,
			   vwQueryForeColor(hwndWnd),
			   vwQueryBackColor(hwndWnd),
			   ulFlags);
   sLine++;
   sTotal+=sDrawn;

   //----------------------------------------------------------------------
   // If DT_WORDBREAK, loop until we're done.
   //----------------------------------------------------------------------
   if (ulFlags & DT_WORDBREAK) {
     while ((sDrawn>0) && (sLine<sNumLines)) {
	   vwQueryWindowRect(hwndWnd,&rclWnd);
	   rclWnd.xRight--;
	   rclWnd.yTop--;

	   rclWnd.yTop-=sLine;
	   rclWnd.yBottom=rclWnd.yTop-1;

   //----------------------------------------------------------------
   // Fine the beginning of the next word to be drawn on the new
   // line
   //----------------------------------------------------------------
   while ((achText[sTotal]!=0) && (isspace(achText[sTotal]))) {
	sTotal++;
	   } /* endwhile */

   if (achText[sTotal]!=0) {

sDrawn=vwDrawText(hwndWnd,

			       -1,
		 &achText[sTotal],
			  &rclWnd,
	vwQueryForeColor(hwndWnd),
       vwQueryBackColor(hwndWnd),
			 ulFlags);
		          sLine++;
		   sTotal+=sDrawn;
	   } else {
		sDrawn=0;
	   } /* endif */
	} /* endwhile */
   } /* endif */
}

The only thing that it needs to do is avoid drawing extra white space at the beginning of a line, which it does by scanning for the first non-space character in the DT_WORDBREAK loop.

The drawLine() Function

The drawLine() function is another easy-to-digest piece of code because its purpose is simple: draw a horizontal or vertical line which will be joined later by other lines at its two corners. Thus, the appropriate corner character needs to be drawn as well as the line proper.

#define DL_TOP			0x0001
#define DL_BOTTOM              0x0002
#define DL_LEFT		0x0004
#define DL_RIGHT		0x0008
#define DL_HORIZONTAL		(DL_TOP|DL_BOTTOM)
#define DL_VERTICAL		(DL_LEFT|DL_RIGHT)

VOID drawLine(HVWWND hwndWnd,PRECTL prclDraw,USHORT usType)
//-------------------------------------------------------------------------
// This function is used by the group box to draw the borders around the
// control.
//
// Input:  hwndWnd - window handle
//	     prclDraw - points to the RECTL structure containing the drawing
//			    area
//	     usType - DL_ constant specifying the type of line
//-------------------------------------------------------------------------
{
   ULONG ulFore;
   ULONG ulBack;
   CHAR achChar[2];
   ULONG ulIndex;
   RECTL rclChar;

   ulFore=vwQueryForeColor(hwndWnd);
   ulBack=vwQueryBackColor(hwndWnd);

   vwFillRect(hwndWnd,prclDraw,ulBack);

   if ((usType & DL_VERTICAL)!=0) {
	//-------------------------------------------------------------------
	// Vertical line
	//-------------------------------------------------------------------
	achChar[0]='³';
	achChar[1]=0;

	for (ulIndex=prclDraw->yBottom; ulIndex<=prclDraw->yTop; ulIndex++) {
	   rclChar=*prclDraw;
	   rclChar.yBottom=ulIndex;
	   rclChar.yTop=ulIndex;

	   vwDrawText(hwndWnd,
			  1,
			  achChar,
			  &rclChar,
			  ulFore,
			  ulBack,
			  DT_LEFT | DT_TOP);
	} /* endfor */

	rclChar=*prclDraw;

	if (usType==DL_LEFT) {
	   achChar[0]='À';
	} else {
	   achChar[0]='Ù';
	} /* endif */

	rclChar.yBottom=prclDraw->yBottom;
	rclChar.yTop=rclChar.yBottom;

	vwDrawText(hwndWnd,
		     1,
		     achChar,
		     &rclChar,
		     ulFore,
		     ulBack,
		     DT_LEFT | DT_TOP);

	if (usType==DL_LEFT) {
	   achChar[0]='Ú';
	} else {
	   achChar[0]='¿';
	} /* endif */

	rclChar.yBottom=prclDraw->yTop;
	rclChar.yTop=rclChar.yBottom;

	vwDrawText(hwndWnd,
		     1,
		     achChar,
		     &rclChar,
		     ulFore,
		     ulBack,
		     DT_LEFT | DT_TOP);
   } else {
	//-------------------------------------------------------------------
	// Horizontal line
	//-------------------------------------------------------------------
	achChar[0]='Ä';
	achChar[1]=0;

	for (ulIndex=prclDraw->xLeft; ulIndex<=prclDraw->xRight; ulIndex++) {
	   rclChar=*prclDraw;
	   rclChar.xLeft=ulIndex;
	   rclChar.xRight=ulIndex;

	   vwDrawText(hwndWnd,
			  1,
			  achChar,
			  &rclChar,
			  ulFore,
			  ulBack,
			  DT_LEFT | DT_TOP);
	} /* endfor */

	rclChar=*prclDraw;

	if (usType==DL_TOP) {
	   achChar[0]='Ú';
	} else {
	   achChar[0]='À';
	} /* endif */

	rclChar.xLeft=prclDraw->xLeft;
	rclChar.xRight=rclChar.xLeft;

	vwDrawText(hwndWnd,
		     1,
		     achChar,
		     &rclChar,
		     ulFore,
		     ulBack,
		     DT_LEFT | DT_TOP);

	if (usType==DL_TOP) {
	   achChar[0]='¿';
	} else {
	   achChar[0]='Ú';
	} /* endif */

	rclChar.xLeft=prclDraw->xRight;
	rclChar.xRight=rclChar.xLeft;

	vwDrawText(hwndWnd,
		     1,
		     achChar,
		     &rclChar,
		     ulFore,
		     ulBack,
		     DT_LEFT | DT_TOP);
   } /* endif */
}

The paintGroupbox() Function

The paintGroupbox() function was removed from the window procedure to keep it from getting crowded, but there is no other reason why this was done. It calls drawLine() once for each line and then draws the text of the groupbox in the top border.

VOID paintGroupbox(HVWWND hwndWnd,ULONG ulStyle)
//-------------------------------------------------------------------------
// This function paints the group box.
//
// Input:  hwndWnd - window handle
//	     ulStyle - window style
//-------------------------------------------------------------------------
{
   CHAR achText[256];
   RECTL rclWnd;
   RECTL rclSide;
   CHAR achDraw[256];

   vwQueryWindowText(hwndWnd,sizeof(achText),achText);

   vwQueryWindowRect(hwndWnd,&rclWnd);
   rclWnd.xRight--;
   rclWnd.yTop--;

   //----------------------------------------------------------------------
   // Top side
   //----------------------------------------------------------------------
   rclSide.xLeft=rclWnd.xLeft;
   rclSide.yTop=rclWnd.yTop;
   rclSide.xRight=rclWnd.xRight;
   rclSide.yBottom=rclSide.yTop;

   drawLine(hwndWnd,&rclSide,DL_TOP);

   //----------------------------------------------------------------------
   // Bottom side
   //----------------------------------------------------------------------
   rclSide.xLeft=rclWnd.xLeft;
   rclSide.yTop=rclWnd.yBottom;
   rclSide.xRight=rclWnd.xRight;
   rclSide.yBottom=rclSide.yTop;

   drawLine(hwndWnd,&rclSide,DL_BOTTOM);

   //----------------------------------------------------------------------
   // Left side
   //----------------------------------------------------------------------
   rclSide.xLeft=rclWnd.xLeft;
   rclSide.yTop=rclWnd.yTop;
   rclSide.xRight=rclSide.xLeft;
   rclSide.yBottom=rclWnd.yBottom;

   drawLine(hwndWnd,&rclSide,DL_LEFT);

   //----------------------------------------------------------------------
   // Right side
   //----------------------------------------------------------------------
   rclSide.xLeft=rclWnd.xRight;
   rclSide.yTop=rclWnd.yTop;
   rclSide.xRight=rclSide.xLeft;
   rclSide.yBottom=rclWnd.yBottom;

   drawLine(hwndWnd,&rclSide,DL_RIGHT);

   //----------------------------------------------------------------------
   // Draw the text if it exists
   //----------------------------------------------------------------------
   if (achText[0]!=0) {
	if (rclWnd.xRight-rclWnd.xLeft-6>=0) {
	   achText[(rclWnd.xRight-rclWnd.xLeft-6)]=0;
	   sprintf(achDraw," %s ",achText);
	} else {
	   achText[0]=0;
	   achDraw[0]=0;
	} /* endif */

	rclSide.xLeft=rclWnd.xLeft;
	rclSide.yTop=rclWnd.yTop;
	rclSide.xRight=rclWnd.xRight;
	rclSide.yBottom=rclSide.yTop;

	vwDrawText(hwndWnd,
		     -1,
		     achDraw,
		     &rclSide,
		     vwQueryForeColor(hwndWnd),
		     vwQueryBackColor(hwndWnd),
		     DT_CENTER | DT_TOP);
   } /* endif */
}

The Window Procedure

The window procedure does nothing except paint itself during WM_PAINT and signal that it needs to be repainted when the window text changes. How much easier do you want? <grin>

MRESULT EXPENTRY VwStaticClassProc(HVWWND hwndWnd,
					     ULONG ulMsg,
					     MPARAM mpParm1,
					     MPARAM mpParm2)
{
   switch (ulMsg) {
   case WM_PAINT:
	{
	   ULONG ulStyle;

	   ulStyle=vwQueryWindowULong(hwndWnd,QWL_STYLE);

	   if ((ulStyle & SS_TEXT)!=0) {
		paintText(hwndWnd,ulStyle);
	   } else
	   if ((ulStyle & SS_GROUPBOX)!=0) {
		paintGroupbox(hwndWnd,ulStyle);
	   } else {
		vwAlarm(WA_ERROR);
	   } /* endif */
	}
	break;
   case WM_QUERYDLGCODE:
	return MRFROMLONG(DLGC_STATIC|DLGC_TABONCLICK);
   case WM_SETWINDOWPARAMS:
	{
	   PWNDPARAMS pwpParms;
	   MRESULT mrRc;

	   pwpParms=(PWNDPARAMS)PVOIDFROMMP(mpParm1);

	   mrRc=vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);

	   if (pwpParms->fsStatus==WPM_TEXT) {
		vwSendMsg(hwndWnd,WM_PAINT,0,0);
	   } /* endif */

	   return mrRc;
	}
	break;
   default:
	return vwDefWindowProc(hwndWnd,ulMsg,mpParm1,mpParm2);
   } /* endswitch */

   return MRFROMLONG(FALSE);
}

Conclusion

Okay, I'll admit that I chose this window class this month because I knew there was nothing to it. However, it does demonstrate that PM isn't as mysterious as you believe, at least for the parts which have analogies in VIOWIN. Next month, we'll wrap up the series with a look at the VWWC_BUTTON class which is arguably the most complex of the classes.

The Design and Implementation of VIOWIN
Part: 1 2 3 4 5 6 7 8