Create the Functions

You can create the library functions by following the description given in Chapter 18, “The Cursor, the Mouse, and the Keyboard.” Simply copy the statements used to make the graphics selection into the corresponding functions. Also, to make the selection functions more flexible, add the additional block capability.

After you change it, the StartSelection function should look like this:

int FAR PASCAL StartSelection(hWnd, ptCurrent, lpSelectRect, fFlags)

HWND hWnd;

POINT ptCurrent;

LPRECT lpSelectRect;

int fFlags;

{

if (!IsEmptyRect(lpSelectRect))

ClearSelection(hWnd, lpSelectRect, fFlags);

if (!fFlags & SL_EXTEND) {

lpSelectRect->left = ptCurrent.x;

lpSelectRect->top = ptCurrent.y;

}

lpSelectRect->right = ptCurrent.x;

lpSelectRect->bottom = ptCurrent.y;

SetCapture(hWnd);

}

This function receives four parameters: a window handle, hWnd; the current mouse location, ptCurrent; a long pointer to the selection rectangle, lpSelectRect; and the selection flags, fFlags.

The first step is to clear the selection if the selection rectangle is not empty. The IsRectEmpty function returns TRUE if the rectangle is empty. The StartSelection function clears the selection by calling the ClearSelection function, which is also in this library.

The next step is to initialize the selection rectangle. The StartSelection function extends the selection (it leaves the upper-left corner of the selection unchanged), if the SL_EXTEND bit in the fFlags argument is set. Otherwise, it sets the upper-left and lower-right corners of the selection rectangle to the current mouse location. The SetCapture function directs all subsequent mouse input to the window even if the cursor moves outside of the window. This is to ensure that the selection process continues uninterrupted. To call this function, an application would use the following statements:

case WM_LBUTTONDOWN:

bTrack = TRUE;

StartSelection(hWnd, MAKEPOINT(lParam), &SelectRect,

(wParam & MK_SHIFT) ? SL_EXTEND : NULL);

break;

After you change it, the UpdateSelection function should look like this:

int FAR PASCAL UpdateSelection(hWnd, ptCurrent, lpSelectRect, fFlags)

HWND hWnd;

POINT ptCurrent;

LPRECT lpSelectRect;

int fFlags;

{

HDC hDC;

short OldROP;

hDC = GetDC(hWnd);

switch (fFlags & SL_TYPE) {

case SL_BOX:

OldROP = SetROP2(hDC, R2_XORPEN);

MoveTo(hDC, lpSelectRect->left,

lpSelectRect->top);

LineTo(hDC, lpSelectRect->right,

lpSelectRect->top);

LineTo(hDC, lpSelectRect->right,

lpSelectRect->bottom);

LineTo(hDC, lpSelectRect->left,

lpSelectRect->bottom);

LineTo(hDC, lpSelectRect->left,

lpSelectRect->top);

LineTo(hDC, ptCurrent.x, lpSelectRect->top);

LineTo(hDC, ptCurrent.x, ptCurrent.y);

LineTo(hDC, lpSelectRect->left, ptCurrent.y);

LineTo(hDC, lpSelectRect->left, lpSelectRect->top);

SetROP2(hDC, OldROP);

break;

case SL_BLOCK:

PatBlt(hDC,

lpSelectRect->left, lpSelectRect->bottom,

lpSelectRect->right - lpSelectRect->left,

ptCurrent.y - lpSelectRect->bottom,

DSTINVERT);

PatBlt(hDC, PrevX, OrgY,

lpSelectRect->right, lpSelectRect->top,

ptCurrent.x - lpSelectRect->right,

ptCurrent.y - lpSelectRect->top, DSTINVERT);

break;

}

lpSelectRect->right = ptCurrent.x;

lpSelectRect->bottom = ptCurrent.y;

ReleaseDC(hWnd, hDC);

}

As the user makes the selection, the UpdateSelection function provides feedback about the user's progress. For the box selection, the function first clears the current box by drawing over it, then draws the new box. This requires eight calls to the LineTo function.

To update a block selection, the UpdateSelection function inverts the rectangle by using the PatBlt function. To avoid flicker while the user selects, UpdateSelection inverts only the portions of the rectangle that are different from the previous selection rectangle. This means the function inverts two separate pieces of the screen. It assumes that the only area that needs inverting is the area between the previous and current mouse locations. Figure 28.1 shows the typical coordinates for describing the areas being inverted.

The first PatBlt call inverts the left-most rectangle by using lpSelectRect->left, the original location on the x-coordinate of the mouse button when first pressed, and lpSelectRect->bottom, the most recent update of the location of the mouse on the y-coordinate, to set the origin of the area to be inverted. The width of the first area is determined by subtracting lpSelectRect->left from lpSelectRect->right, the most recent update of the location of the mouse on the x-coordinate. The height of this area is determined by subtracting lpSelectRect->bottom from ptCurrent.y, the current location of the mouse on the y-coordinate.

The second PatBlt call inverts the right-most rectangle by using lpSelectRect->right, the most recent location on the x-coordinate of the mouse button, and lpSelectRect->top, the original location on the y-coordinate of the mouse, to set the origin of the area to be inverted. The width of this second area is determined by subtracting lpSelectRect->bottom, the most recent update of the location of the mouse on the x-coordinate, from ptCurrent.x, the current location of the mouse on the x-coordinate. The height of this area is determined by subtracting lpSelectRect->top from ptCurrent.y, the current location of the mouse on the y-coordinate.

When the selection updating is complete, the values lpSelectRect->right and lpSelectRect->bottom are updated by assigning them the current values contained in ptCurrent.

To update a box selection, the application should call the UpdateSelection function as follows:

case WM_MOUSEMOVE:

if (bTrack)

UpdateSelection(hWnd, MAKEPOINT(lParam), &SelectRect, SL_BOX);

break;

After you change it, the EndSelection function should look like this:

int FAR PASCAL EndSelection(ptCurrent, lpSelectRect)

POINT ptCurrent;

LPRECT lpSelectRect;

{

if (ptCurrent.x < lpSelectRect->left) {

lpSelectRect->right = lpSelectRect->left;

lpSelectRect->left = ptCurrent.x;

}

else

lpSelectRect->right = ptCurrent.x;

if (ptCurrent.y < lpSelectRect->top) {

lpSelectRect->bottom = lpSelectRect->top;

lpSelectRect->top = ptCurrent.y;

}

else

lpSelectRect->bottom = ptCurrent.y;

ReleaseCapture();

}

The EndSelection function saves the current mouse position in the selection rectangle. For convenience, the final mouse position is checked to make sure it represents a point to the lower right of the original point. Rectangles typically are described by upper-left and lower-right corners. If the final position is not to the lower right (that is, if either the x- or y-coordinate of the position is less than the original x- and y-coordinates), the values of the original point and the final point are swapped as necessary. The ReleaseCapture function is required since a corresponding SetCapture function was called. In general, you should release the mouse immediately after mouse capture is no longer needed.

Finally, when the user releases the left button, the application should call the EndSelection function to save the final point:

case WM_LBUTTONUP:

bTrack = FALSE;

EndSelection(MAKEPOINT(lParam), &SelectRect);

break;

After you change it, the ClearSelection function should look like this:

int FAR PASCAL ClearSelection(hWnd, lpSelectRect, fFlags)

HWND hWnd;

LPRECT lpSelectRect;

int fFlags;

{

HDC hDC;

short OldROP;

hDC = GetDC(hWnd);

switch (fFlags & SL_TYPE) {

case SL_BOX:

OldROP = SetROP2(hDC, R2_XORPEN);

MoveTo(hDC, lpSelectRect->left, lpSelectRect->top);

LineTo(hDC, lpSelectRect->right, lpSelectRect->top);

LineTo(hDC, lpSelectRect->right, lpSelectRect->bottom);

LineTo(hDC, lpSelectRect->left, lpSelectRect->bottom);

LineTo(hDC, lpSelectRect->left, lpSelectRect->top);

SetROP2(hDC, OldROP);

break;

case SL_BLOCK:

PatBlt(hDC,

lpSelectRect->left, lpSelectRect->top,

lpSelectRect->right - lpSelectRect->left,

lpSelectRect->bottom - lpSelectRect->top,

DSTINVERT);

break;

}

ReleaseDC(hWnd, hDC);

}

Clearing a box selection means removing it from the screen. You can remove the outline by drawing over it with the XOR pen. Clearing a block selection means restoring the inverted screen to its previous state. You can restore the inverted screen by inverting the entire selection.