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