|
|
1.1 root 1: /**************************************************************************\
2: * track.c -- support for direct manipulation of parallelogram object.
3: *
1.1.1.2 ! root 4: * Steve Firebaugh
! 5: * Microsoft Developer Support
! 6: * Copyright (c) 1992 Microsoft Corporation
! 7: *
1.1 root 8: \**************************************************************************/
9:
10: #include <windows.h>
11: #include <math.h>
12: #include "track.h"
13:
14: #define EPSILON (float) 0.0001
15: #define RECTSIZE 60
16:
17:
18:
19: /**************************************************************************\
20: *
21: * function: doTrackObject()
22: *
23: * input parameters:
24: * pto - pointer to a track object.
25: * msg - message selecting what action to take. Values may include WM_*'s
26: * (see case statements below for more information.)
27: * hwnd - Window handle for the window the track object exists within.
28: * lParam - Usually fourth param to window proc. varies based on msg.
29: *
30: * global variables: none.
31: *
32: * coordinate spaces: There are three coordinate spaces of interest here,
33: * and this routine is frequently switching between them...
34: *
35: * WORLD DEVICE SCREEN
36: *
37: * object coordinates input mouse pos used w/ SetCursorPos()
38: * (pto->rect) (lParam for WM_*)
39: *
40: * -----> LPtoDP() ----> ----> ClientToScreen() -->
41: * <----- DPtoLP() <---- <---- ScreenToClient() <--
42: *
43: * in addition, the HDC has an offset origin. Device coordinates for the
44: * mouse (lParam) never take this into account, but it is necessary to
45: * translate them in order to get direct manipulation right.
46: *
47: \**************************************************************************/
48: PTrackObject doTrackObject(PTrackObject pto, int msg, HWND hwnd, LONG lParam)
49: {
50: if ((pto == NULL) && (msg != TROB_NEW)) return NULL;
51:
52: switch (msg) {
53:
54:
55: /**********************************************************************\
56: * TROB_NEW
57: *
58: * Allocate new PTrackObject structure. Fill in default values
59: * for the fields of the structure. Set up the HDC correctly.
60: * return - pointer to the new object.
61: \**********************************************************************/
62: case TROB_NEW: {
63: PTrackObject pto;
64:
65: /* with LPTR returned value is a pointer. */
66: pto = (PTrackObject) LocalAlloc (LPTR, sizeof (TrackObject));
67:
68: /* initialize the HDC and other fields. */
69: pto->hdc = GetDC (hwnd);
70: SetROP2(pto->hdc, R2_NOT);
71: SelectObject (pto->hdc, GetStockObject (NULL_BRUSH));
72: pto->Mode = TMNONE;
73: pto->allowedModes = TMMOVE | TMSIZEXY;
74:
75: GetWorldTransform (pto->hdc, &(pto->xfmChange));
76:
77: /* initialize the size. */
78: pto->rect.top = pto->rect.left = 0;
79: pto->rect.bottom = pto->rect.right = RECTSIZE;
80:
81: return (pto);
82: }
83:
84:
85:
86: /**********************************************************************\
87: * TROB_DELETE
88: *
89: * Complement of TROB_NEW. Free up the memory allocated for the object.
90: \**********************************************************************/
91: case TROB_DELETE:
92: doTrackObject (pto, TROB_PAINT, hwnd, lParam);
93: ReleaseDC (hwnd, pto->hdc);
94: LocalFree (LocalHandle ((LPSTR)pto));
95: return NULL;
96:
97:
98:
99: /**********************************************************************\
100: * TROB_PAINT
101: *
102: * Paint the object into its hdc. Called half the time to erase
103: * the object, and half the time to redraw it.
104: \**********************************************************************/
105: case TROB_PAINT: {
106: MoveToEx (pto->hdc, pto->rect.right, pto->rect.top, NULL);
107: LineTo (pto->hdc, pto->rect.left, pto->rect.top);
108: LineTo (pto->hdc, pto->rect.left, pto->rect.bottom);
109:
110: if (pto->allowedModes & TMSIZEXY) {
111: LineTo (pto->hdc, pto->rect.right, pto->rect.bottom);
112: LineTo (pto->hdc, pto->rect.right, pto->rect.top);
113: }
114:
115: if (pto->allowedModes & TMROTATE) {
116: MoveToEx (pto->hdc, pto->rect.left, pto->rect.bottom/ 4, NULL);
117: AngleArc (pto->hdc, pto->rect.left, pto->rect.top,
118: (DWORD) pto->rect.bottom/ 4, (float) 270.0, (float) 90.0);
119: }
120:
121: } return NULL;
122:
123:
124:
125: /**********************************************************************\
126: * TROB_HITTEST
127: *
128: * Check the point sent in in the lParam to see if it lays within
129: * the bounds of the objects defining rectangle.
130: * return - pointer to the object iff the point is in rectangle,
131: * otherwise return NULL.
132: \**********************************************************************/
133: case TROB_HITTEST:{
134: POINT mouWorld;
135: mouWorld.x = LOWORD(lParam);
136: mouWorld.y = HIWORD(lParam);
137:
138: DPtoLP (pto->hdc, &mouWorld, 1);
139:
140: if (PtInRect (&pto->rect, mouWorld)) return pto;
141: else return NULL;
142: }
143:
144:
145:
146: /**********************************************************************\
147: * WM_LBUTTONDOWN & WM_RBUTTONDOWN
148: *
149: * Capture the mouse, set the tracking mode depending on the mouse
150: * location in world coordinates, reset the mouse position.
151: *
152: \**********************************************************************/
153: case WM_LBUTTONDOWN:
154: case WM_RBUTTONDOWN: {
155: POINT newmouScreen;
156: POINT mouWorld;
157:
158: mouWorld.x = LOWORD(lParam);
159: mouWorld.y = HIWORD(lParam);
160: DPtoLP (pto->hdc, &mouWorld, 1);
161:
162: /* upper left hand corner. right button is no-op. */
163: if ((mouWorld.x <= (pto->rect.right / 2)) &&
164: (mouWorld.y <= (pto->rect.bottom / 2))) {
165: if (msg == WM_RBUTTONDOWN) return NULL;
166: pto->Mode = TMMOVE;
167: newmouScreen.x = pto->rect.left;
168: newmouScreen.y = pto->rect.top;
169:
170: /* lower left hand corner */
171: } else if ((mouWorld.x <= (pto->rect.right / 2)) &&
172: (mouWorld.y > (pto->rect.bottom / 2))) {
173:
174: pto->Mode = (msg == WM_RBUTTONDOWN) ? TMSHEARY : TMSIZEY;
175: newmouScreen.x = pto->rect.left;
176: newmouScreen.y = pto->rect.bottom;
177:
178: /* upper right hand corner */
179: } else if ((mouWorld.x > (pto->rect.right / 2)) &&
180: (mouWorld.y <= (pto->rect.bottom / 2))) {
181:
182: pto->Mode = (msg == WM_RBUTTONDOWN) ? TMSHEARX : TMSIZEX;
183: newmouScreen.x = pto->rect.right;
184: newmouScreen.y = pto->rect.top;
185:
186: /* lower right hand corner */
187: } else if ((mouWorld.x > (pto->rect.right / 2)) &&
188: (mouWorld.y > (pto->rect.bottom / 2))) {
189:
190: pto->Mode = (msg == WM_RBUTTONDOWN) ? TMROTATE : TMSIZEXY;
191: newmouScreen.x = pto->rect.right;
192: newmouScreen.y = pto->rect.bottom;
193: }
194:
195: if (! (pto->Mode & pto->allowedModes)) {
196: pto->Mode = TMNONE;
197: return NULL;
198: }
199:
200: SetCapture(hwnd);
201: LPtoDP (pto->hdc, &newmouScreen, 1);
202: ClientToScreen (hwnd, &newmouScreen);
203: SetCursorPos (newmouScreen.x,newmouScreen.y);
204:
205: GetWorldTransform (pto->hdc, &pto->xfmDown);
206: } return NULL;
207:
208:
209:
210: /**********************************************************************\
211: * WM_MOUSEMOVE
212: *
213: * this is where almost all of the interesting calculation is done.
214: * First clip the mouse location to be in rectClip, then
215: * call MouseMove() to handle the different tracking modes.
216: \**********************************************************************/
217: case WM_MOUSEMOVE: {
218: if ((short) LOWORD(lParam) < (short)pto->rectClip.left)
219: lParam = MAKELONG ((WORD)pto->rectClip.left, HIWORD(lParam));
220:
221: if (LOWORD(lParam) > (WORD)pto->rectClip.right)
222: lParam = MAKELONG ((WORD)pto->rectClip.right, HIWORD(lParam));
223:
224: if ((short) HIWORD(lParam) < (short)pto->rectClip.top)
225: lParam = MAKELONG (LOWORD(lParam), (WORD)pto->rectClip.top);
226:
227: if (HIWORD(lParam) > (WORD)pto->rectClip.bottom)
228: lParam = MAKELONG (LOWORD(lParam),(WORD)pto->rectClip.bottom);
229:
230: MouseMove (pto, msg, hwnd, lParam);
231:
232: } return NULL;
233:
234:
235:
236: /**********************************************************************\
237: * WM_RBUTTONUP & WM_LBUTTONUP
238: *
239: * simply release the mouse capture, and set the mode to TMNONE.
240: \**********************************************************************/
241: case WM_RBUTTONUP:
242: case WM_LBUTTONUP: {
243: if (pto->Mode) {
244: ReleaseCapture();
245: pto->Mode = TMNONE;
246: }
247: } return NULL;
248:
249: } /* end switch(msg) */
250: }
251:
252:
253:
254:
255:
256:
257: /**************************************************************************\
258: * function: MouseMove()
259: *
260: * input parameters:
261: * pto - pointer to a track object.
262: * msg - not used.
263: * hwnd - Window handle for the window the track object exists within.
264: * lParam - Usually fourth param to window proc. varies based on msg.
265: *
266: * The tracking behavior which the user observers when moving the mouse
267: * is based on the current tracking mode of the object. This is usually
268: * determined on the mouse down event (c.f. TM*). First erase the old
269: * object, then figure out the change to the transform matrix, finally
270: * change the world transform matrix and redraw the object.
271: *
272: * Tranform:
273: * ( eM11 eM12 0 )
274: * ( eM21 eM22 0 )
275: * ( eDx eDy 1 )
276: *
277: * xDevice = (xWorld * eM11) + (yWorld * eM21) + eDx
278: * yDevice = (xWorld * eM12) + (yWorld * eM22) + eDy
279: *
280: * In this routine the Device (mouse location) and World (rectangle corner)
281: * points are known. Therefore, the two equations above are solved for
282: * the desired matrix entry value (e.g. eM11, 1M12, ... eDy). The tracking
283: * mode determines which one of these entries may be changed. E.g. scaling
284: * in X modifies eM11 while shearing in X modifies eM12. So rather than
285: * using the world transform to map from world to device points, we are
286: * back-computing the proper contents of the world transform.
287: *
288: \**************************************************************************/
289: VOID MouseMove(PTrackObject pto, int msg, HWND hwnd, LONG lParam)
290: {
291: POINT mouWorld, mouDevice, orgDevice;
292:
293: UNREFERENCED_PARAMETER(msg);
294:
295: doTrackObject(pto, TROB_PAINT, hwnd, lParam);
296: mouDevice.x = mouWorld.x = LOWORD(lParam);
297: mouDevice.y = mouWorld.y = HIWORD(lParam);
298:
299: SetWorldTransform(pto->hdc, &pto->xfmDown);
300: DPtoLP (pto->hdc, &mouWorld, 1);
301:
302: /* offset the mouse device point for the viewport's origin. */
303: GetViewportOrgEx (pto->hdc, &orgDevice);
304: mouDevice.x -= orgDevice.x;
305: mouDevice.y -= orgDevice.y;
306:
307: GetWorldTransform(pto->hdc, &pto->xfmChange);
308:
309: switch (pto->Mode) {
310: /*******************************************************\
311: * ( 1 xShear 0 )
312: * ( 0 1 0 )
313: * ( 0 0 1 )
314: *
315: * xWorld = rect.left == 0;
316: \*******************************************************/
317: case TMSHEARX: {
318: pto->xfmChange.eM12 = (float) mouDevice.y;
319: pto->xfmChange.eM12 -=pto->xfmChange.eDy;
320: pto->xfmChange.eM12 /=(float) pto->rect.right ;
321: SetWorldTransform (pto->hdc, &pto->xfmChange);
322: } break;
323:
324:
325: /*******************************************************\
326: * ( 1 0 0 )
327: * ( yShear 1 0 )
328: * ( 0 0 1 )
329: *
330: * yWorld = rect.top == 0;
331: \*******************************************************/
332: case TMSHEARY: {
333: pto->xfmChange.eM21 = (float) mouDevice.x;
334: pto->xfmChange.eM21 -=pto->xfmChange.eDx;
335: pto->xfmChange.eM21 /=(float) pto->rect.bottom ;
336: SetWorldTransform (pto->hdc, &pto->xfmChange);
337:
338: } break;
339:
340:
341: /*******************************************************\
342: * ( cos(a) -sin(a) 0 )
343: * ( sin(a) cos(a) 0 )
344: * ( 0 0 1 )
345: *
346: * a == rotation angle. Since mouse in in lower right,
347: * we need to shift this back 45 degrees (assuming that
348: * straight down is 0 degrees). Thus we actually compute
349: * cos(a) = cos(b - 45) = cos(b)sin(45) + cos(45)sin(45)
350: * where b is angle from the origin to the mouse (x,y)
351: * cos(45) = sin(45) ~= 0.707107
352: * cos(b) = y/r sin(b) = x/r
353: *
354: \*******************************************************/
355: case TMROTATE: {
356: float r;
357:
358: /* translate back to the origin. */
359: pto->xfmChange.eDx = pto->xfmChange.eDy = (float)0.0;
360: SetWorldTransform (pto->hdc, &pto->xfmChange);
361:
362: /* rotate about the origin. */
363: r = (float) sqrt( (double)(mouWorld.x * mouWorld.x) +
364: (double)(mouWorld.y * mouWorld.y));
365:
366: pto->xfmChange.eM11 = (float) mouWorld.y / r;
367: pto->xfmChange.eM11 += (float) mouWorld.x / r;
368: pto->xfmChange.eM11 *= (float) 0.707107;
369: pto->xfmChange.eM22 = pto->xfmChange.eM11;
370:
371: pto->xfmChange.eM12 = (float) mouWorld.y / r;
372: pto->xfmChange.eM12 -= (float) mouWorld.x / r;
373: pto->xfmChange.eM12 *= (float) 0.707107;
374: pto->xfmChange.eM21 = -pto->xfmChange.eM12;
375:
376: pto->xfmChange.eDx = pto->xfmChange.eDy = (float)0.0;
377:
378: ModifyWorldTransform (pto->hdc, &pto->xfmChange, MWT_RIGHTMULTIPLY);
379:
380: /* translate back to the original offset. */
381: pto->xfmChange.eM11 =
382: pto->xfmChange.eM22 = (float) 1.0;
383: pto->xfmChange.eM12 =
384: pto->xfmChange.eM21 = (float) 0.0;
385:
386: pto->xfmChange.eDx = pto->xfmDown.eDx;
387: pto->xfmChange.eDy = pto->xfmDown.eDy;
388: ModifyWorldTransform (pto->hdc, &pto->xfmChange, MWT_RIGHTMULTIPLY);
389: GetWorldTransform (pto->hdc, &pto->xfmChange);
390: } break;
391:
392:
393: /*******************************************************\
394: * ( Size X 0 0 )
395: * ( 0 Size Y 0 )
396: * ( 0 0 1 )
397: *
398: \*******************************************************/
399: case TMSIZEXY: {
400: pto->xfmChange.eM11 = (float) mouDevice.x;
401: pto->xfmChange.eM11 -=pto->xfmChange.eDx;
402: pto->xfmChange.eM11 -=((float) pto->rect.bottom*pto->xfmChange.eM21);
403: pto->xfmChange.eM11 /=(float) pto->rect.right ;
404: if (fabs(pto->xfmChange.eM11) < EPSILON) // HACK. system bug ?
405: pto->xfmChange.eM11 = EPSILON;
406:
407: pto->xfmChange.eM22 = (float) mouDevice.y;
408: pto->xfmChange.eM22 -=pto->xfmChange.eDy;
409: pto->xfmChange.eM22 -=((float) pto->rect.right*pto->xfmChange.eM12);
410: pto->xfmChange.eM22 /=(float) pto->rect.bottom ;
411: if (fabs(pto->xfmChange.eM22) < EPSILON) // HACK. system bug ?
412: pto->xfmChange.eM22 = EPSILON;
413: SetWorldTransform (pto->hdc, &pto->xfmChange);
414: } break;
415:
416:
417: /*******************************************************\
418: * ( Size X 0 0 )
419: * ( 0 1 0 )
420: * ( 0 0 1 )
421: *
422: * yWorld = rect.top == 0;
423: \*******************************************************/
424: case TMSIZEX: {
425: pto->xfmChange.eM11 = (float) mouDevice.x;
426: pto->xfmChange.eM11 -=pto->xfmChange.eDx;
427: pto->xfmChange.eM11 /=(float) pto->rect.right ;
428: if (fabs(pto->xfmChange.eM11) < EPSILON) // HACK. system bug ?
429: pto->xfmChange.eM11 = EPSILON;
430:
431: SetWorldTransform (pto->hdc, &pto->xfmChange);
432: } break;
433:
434:
435: /*******************************************************\
436: * ( 1 0 0 )
437: * ( 0 Size Y 0 )
438: * ( 0 0 1 )
439: *
440: * xWorld = rect.left == 0;
441: \*******************************************************/
442: case TMSIZEY: {
443: pto->xfmChange.eM22 = (float) mouDevice.y;
444: pto->xfmChange.eM22 -=pto->xfmChange.eDy;
445: pto->xfmChange.eM22 /=(float) pto->rect.bottom ;
446: if (fabs(pto->xfmChange.eM22) < EPSILON) // HACK. system bug ?
447: pto->xfmChange.eM22 = EPSILON;
448: SetWorldTransform (pto->hdc, &pto->xfmChange);
449: } break;
450:
451:
452: /*******************************************************\
453: * ( 1 0 0 )
454: * ( 0 1 0 )
455: * ( Move x Move y 1 )
456: *
457: * xWorld = rect.left == 0;
458: * yWorld = rect.top == 0;
459: \*******************************************************/
460: case TMMOVE: {
461: pto->xfmChange.eDx = (float) mouDevice.x ;
462: pto->xfmChange.eDy = (float) mouDevice.y ;
463: SetWorldTransform (pto->hdc, &pto->xfmChange);
464: } break;
465: } /* end switch */
466:
467: doTrackObject(pto, TROB_PAINT, hwnd, lParam);
468:
469: return;
470: }
471:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.