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