source: trunk/src/comctl32/ipaddress.cpp@ 6502

Last change on this file since 6502 was 5416, checked in by sandervl, 24 years ago

Resync with Wine + previous merge fixes

File size: 17.9 KB
Line 
1/* $Id: ipaddress.cpp,v 1.3 2001-03-31 13:25:26 sandervl Exp $ */
2/*
3 * IP Address control
4 *
5 * Copyright 1999 Chris Morgan<cmorgan@wpi.edu> and
6 * James Abbatiello<abbeyj@wpi.edu>
7 * Copyright 1998, 1999 Eric Kohl
8 * Copyright 1998 Alex Priem <alexp@sci.kun.nl>
9 * Copyright 1999 Achim Hasenmueller
10 *
11 * NOTES
12 *
13 * TODO:
14 * -Check ranges when changing field-focus.
15 * -Check all notifications/behavior.
16 * -Optimization: include lpipsi in IPADDRESS_INFO.
17 * -CurrentFocus: field that has focus at moment of processing.
18 * -connect Rect32 rcClient.
19 * -handle right and left arrows correctly. Boring.
20 * -split GotoNextField in CheckField and GotoNextField.
21 * -check ipaddress.cpp for missing features.
22 * -refresh: draw '.' instead of setpixel.
23 * -handle VK_ESCAPE.
24 */
25
26#include <ctype.h>
27#include <stdlib.h>
28#include <stdio.h>
29
30#include "winbase.h"
31#include "commctrl.h"
32#include "ccbase.h"
33#include "ipaddress.h"
34//#include "heap.h"
35#include "comctl32.h"
36
37#define IPADDRESS_GetInfoPtr(hwnd) ((IPADDRESS_INFO*)getInfoPtr(hwnd))
38
39
40static BOOL
41IPADDRESS_SendIPAddressNotify (HWND hwnd, UINT field, BYTE newValue);
42
43
44/* property name of tooltip window handle */
45#define IP_SUBCLASS_PROP "CCIP32SubclassInfo"
46
47
48static LRESULT CALLBACK
49IPADDRESS_SubclassProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
50
51
52
53
54static VOID
55IPADDRESS_Refresh (HWND hwnd, HDC hdc)
56{
57 RECT rcClient;
58 HBRUSH hbr;
59 COLORREF clr=GetSysColor (COLOR_3DDKSHADOW);
60 int i,x,fieldsize;
61
62 GetClientRect (hwnd, &rcClient);
63 hbr = CreateSolidBrush (RGB(255,255,255));
64 DrawEdge (hdc, &rcClient, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
65 FillRect (hdc, &rcClient, hbr);
66 DeleteObject (hbr);
67
68 x=rcClient.left;
69 fieldsize=(rcClient.right-rcClient.left) /4;
70
71 for (i=0; i<3; i++) { /* Should draw text "." here */
72 x+=fieldsize;
73 SetPixel (hdc, x, 13, clr);
74 SetPixel (hdc, x, 14, clr);
75 SetPixel (hdc, x+1, 13, clr);
76 SetPixel (hdc, x+1, 14, clr);
77 }
78}
79
80
81static LRESULT
82IPADDRESS_Create (HWND hwnd, WPARAM wParam, LPARAM lParam)
83{
84 IPADDRESS_INFO *infoPtr;
85 RECT rcClient, edit;
86 int i,fieldsize;
87 LPIP_SUBCLASS_INFO lpipsi;
88
89
90 infoPtr = (IPADDRESS_INFO*)initControl(hwnd,sizeof(IPADDRESS_INFO));
91 if (!infoPtr) return (LRESULT)-1;
92
93 GetClientRect (hwnd, &rcClient);
94
95 fieldsize=(rcClient.right-rcClient.left) /4;
96
97 edit.top =rcClient.top+2;
98 edit.bottom=rcClient.bottom-2;
99
100 lpipsi=(LPIP_SUBCLASS_INFO) GetPropA ((HWND)hwnd, IP_SUBCLASS_PROP);
101 if (lpipsi == NULL) {
102 lpipsi= (LPIP_SUBCLASS_INFO) COMCTL32_Alloc (sizeof(IP_SUBCLASS_INFO));
103 lpipsi->hwnd = hwnd;
104 lpipsi->uRefCount++;
105 SetPropA ((HWND)hwnd, IP_SUBCLASS_PROP, (HANDLE)lpipsi);
106/* infoPtr->lpipsi= lpipsi; */
107// } else
108 }
109// WARN (ipaddress,"IP-create called twice\n");
110
111 for (i=0; i<=3; i++)
112 {
113 infoPtr->LowerLimit[i]=0;
114 infoPtr->UpperLimit[i]=255;
115 edit.left=rcClient.left+i*fieldsize+6;
116 edit.right=rcClient.left+(i+1)*fieldsize-2;
117 lpipsi->hwndIP[i]= CreateWindowA ("edit", NULL,
118 WS_CHILD | WS_VISIBLE | ES_CENTER,
119 edit.left, edit.top, edit.right-edit.left, edit.bottom-edit.top,
120 hwnd, (HMENU) 1, GetWindowLongA (hwnd, GWL_HINSTANCE), NULL);
121 lpipsi->wpOrigProc[i]= (WNDPROC)
122 SetWindowLongA (lpipsi->hwndIP[i],GWL_WNDPROC, (LONG)
123 IPADDRESS_SubclassProc);
124 SetPropA ((HWND)lpipsi->hwndIP[i], IP_SUBCLASS_PROP, (HANDLE)lpipsi);
125 }
126
127 lpipsi->infoPtr= infoPtr;
128
129 return 0;
130}
131
132
133static LRESULT
134IPADDRESS_Destroy (HWND hwnd, WPARAM wParam, LPARAM lParam)
135{
136 int i;
137 IPADDRESS_INFO *infoPtr = IPADDRESS_GetInfoPtr (hwnd);
138 LPIP_SUBCLASS_INFO lpipsi=(LPIP_SUBCLASS_INFO)
139
140 GetPropA ((HWND)hwnd, IP_SUBCLASS_PROP);
141
142 for (i=0; i<=3; i++) {
143 SetWindowLongA ((HWND)lpipsi->hwndIP[i], GWL_WNDPROC,
144 (LONG)lpipsi->wpOrigProc[i]);
145 }
146
147 doneControl(hwnd);
148 return 0;
149}
150
151
152static LRESULT
153IPADDRESS_KillFocus (HWND hwnd, WPARAM wParam, LPARAM lParam)
154{
155 HDC hdc;
156
157// TRACE (ipaddress,"\n");
158 hdc = GetDC (hwnd);
159 IPADDRESS_Refresh (hwnd, hdc);
160 ReleaseDC (hwnd, hdc);
161
162 IPADDRESS_SendIPAddressNotify (hwnd, 0, 0); /* FIXME: should use -1 */
163 sendCommand(hwnd,EN_KILLFOCUS);
164 InvalidateRect (hwnd, NULL, TRUE);
165
166 return 0;
167}
168
169
170static LRESULT
171IPADDRESS_Paint (HWND hwnd, WPARAM wParam)
172{
173 HDC hdc;
174 PAINTSTRUCT ps;
175
176 hdc = wParam==0 ? BeginPaint (hwnd, &ps) : (HDC)wParam;
177 IPADDRESS_Refresh (hwnd, hdc);
178 if(!wParam)
179 EndPaint (hwnd, &ps);
180 return 0;
181}
182
183
184static LRESULT
185IPADDRESS_SetFocus (HWND hwnd, WPARAM wParam, LPARAM lParam)
186{
187 HDC hdc;
188
189// TRACE (ipaddress,"\n");
190
191 hdc = GetDC (hwnd);
192 IPADDRESS_Refresh (hwnd, hdc);
193 ReleaseDC (hwnd, hdc);
194
195 return 0;
196}
197
198
199static LRESULT
200IPADDRESS_Size (HWND hwnd, WPARAM wParam, LPARAM lParam)
201{
202 /* IPADDRESS_INFO *infoPtr = IPADDRESS_GetInfoPtr (hwnd); */
203// TRACE (ipaddress,"\n");
204 return 0;
205}
206
207
208static BOOL
209IPADDRESS_SendIPAddressNotify (HWND hwnd, UINT field, BYTE newValue)
210{
211 NMIPADDRESS nmip;
212
213 nmip.iField=field;
214 nmip.iValue=newValue;
215
216 return (BOOL)sendNotify(hwnd,IPN_FIELDCHANGED,&nmip.hdr);
217}
218
219
220
221
222static LRESULT
223IPADDRESS_ClearAddress (HWND hwnd, WPARAM wParam, LPARAM lParam)
224{
225 int i;
226 HDC hdc;
227 char buf[1];
228 LPIP_SUBCLASS_INFO lpipsi=(LPIP_SUBCLASS_INFO)
229 GetPropA ((HWND)hwnd,IP_SUBCLASS_PROP);
230
231// TRACE (ipaddress,"\n");
232
233 buf[0]=0;
234 for (i=0; i<=3; i++)
235 SetWindowTextA (lpipsi->hwndIP[i],buf);
236
237 hdc = GetDC (hwnd);
238 IPADDRESS_Refresh (hwnd, hdc);
239 ReleaseDC (hwnd, hdc);
240
241 return 0;
242}
243
244static LRESULT
245IPADDRESS_IsBlank (HWND hwnd, WPARAM wParam, LPARAM lParam)
246{
247 int i;
248 char buf[20];
249 LPIP_SUBCLASS_INFO lpipsi=(LPIP_SUBCLASS_INFO)
250 GetPropA ((HWND)hwnd, IP_SUBCLASS_PROP);
251
252// TRACE (ipaddress,"\n");
253
254 for (i=0; i<=3; i++) {
255 GetWindowTextA (lpipsi->hwndIP[i],buf,5);
256 if (buf[0])
257 return 0;
258 }
259
260 return 1;
261}
262
263static LRESULT
264IPADDRESS_GetAddress (HWND hwnd, WPARAM wParam, LPARAM lParam)
265{
266 char field[20];
267 int i,valid,fieldvalue;
268 DWORD ip_addr;
269 IPADDRESS_INFO *infoPtr = IPADDRESS_GetInfoPtr (hwnd);
270 LPIP_SUBCLASS_INFO lpipsi=(LPIP_SUBCLASS_INFO)
271 GetPropA ((HWND)hwnd, IP_SUBCLASS_PROP);
272
273// TRACE (ipaddress,"\n");
274
275 valid=0;
276 ip_addr=0;
277 for (i=0; i<=3; i++) {
278 GetWindowTextA (lpipsi->hwndIP[i],field,4);
279 ip_addr*=256;
280 if (field[0]) {
281 field[3]=0;
282 fieldvalue=atoi(field);
283 if (fieldvalue<infoPtr->LowerLimit[i])
284 fieldvalue=infoPtr->LowerLimit[i];
285 if (fieldvalue>infoPtr->UpperLimit[i])
286 fieldvalue=infoPtr->UpperLimit[i];
287 ip_addr+=fieldvalue;
288 valid++;
289 }
290 }
291
292 *(LPDWORD) lParam=ip_addr;
293
294 return valid;
295}
296
297
298static LRESULT
299IPADDRESS_SetRange (HWND hwnd, WPARAM wParam, LPARAM lParam)
300{
301 IPADDRESS_INFO *infoPtr = IPADDRESS_GetInfoPtr (hwnd);
302 INT index;
303
304// TRACE (ipaddress,"\n");
305
306 index=(INT) wParam;
307 if ((index<0) || (index>3)) return 0;
308
309 infoPtr->LowerLimit[index]=lParam & 0xff;
310 infoPtr->UpperLimit[index]=(lParam >>8) & 0xff;
311 return 1;
312}
313
314static LRESULT
315IPADDRESS_SetAddress (HWND hwnd, WPARAM wParam, LPARAM lParam)
316{
317 IPADDRESS_INFO *infoPtr = IPADDRESS_GetInfoPtr (hwnd);
318 HDC hdc;
319 LPIP_SUBCLASS_INFO lpipsi=(LPIP_SUBCLASS_INFO)
320 GetPropA ((HWND)hwnd, IP_SUBCLASS_PROP);
321 int i,ip_address,value;
322 char buf[20];
323
324// TRACE (ipaddress,"\n");
325 ip_address=(DWORD) lParam;
326
327 for (i=3; i>=0; i--) {
328 value=ip_address & 0xff;
329 if ((value>=infoPtr->LowerLimit[i]) && (value<=infoPtr->UpperLimit[i]))
330 {
331 sprintf (buf,"%d",value);
332 SetWindowTextA (lpipsi->hwndIP[i],buf);
333 sendCommand(hwnd,EN_CHANGE);
334 }
335 ip_address/=256;
336 }
337
338 hdc = GetDC (hwnd); /* & send notifications */
339 IPADDRESS_Refresh (hwnd, hdc);
340 ReleaseDC (hwnd, hdc);
341
342 return TRUE;
343}
344
345
346
347
348static LRESULT
349IPADDRESS_SetFocusToField (HWND hwnd, WPARAM wParam, LPARAM lParam)
350{
351 /* IPADDRESS_INFO *infoPtr = IPADDRESS_GetInfoPtr (hwnd); */
352 LPIP_SUBCLASS_INFO lpipsi=(LPIP_SUBCLASS_INFO) GetPropA ((HWND)hwnd,
353 IP_SUBCLASS_PROP);
354 INT index;
355
356 index=(INT) wParam;
357// TRACE (ipaddress," %d\n", index);
358 if ((index<0) || (index>3)) return 0;
359
360 SetFocus (lpipsi->hwndIP[index]);
361
362 return 1;
363}
364
365
366static LRESULT
367IPADDRESS_LButtonDown (HWND hwnd, WPARAM wParam, LPARAM lParam)
368{
369// TRACE (ipaddress, "\n");
370
371 SetFocus (hwnd);
372 sendCommand(hwnd,EN_SETFOCUS);
373 IPADDRESS_SetFocusToField (hwnd, 0, 0);
374
375 return TRUE;
376}
377
378static INT
379IPADDRESS_CheckField (LPIP_SUBCLASS_INFO lpipsi, int currentfield)
380{
381 int newField,fieldvalue;
382 char field[20];
383 IPADDRESS_INFO *infoPtr=lpipsi->infoPtr;
384
385 if(currentfield >= 0 && currentfield < 4)
386 {
387// TRACE("\n");
388 GetWindowTextA (lpipsi->hwndIP[currentfield],field,4);
389 if (field[0])
390 {
391 field[3]=0;
392 newField=-1;
393 fieldvalue=atoi(field);
394
395 if (fieldvalue < infoPtr->LowerLimit[currentfield])
396 newField=infoPtr->LowerLimit[currentfield];
397
398 if (fieldvalue > infoPtr->UpperLimit[currentfield])
399 newField=infoPtr->UpperLimit[currentfield];
400
401 if (newField >= 0)
402 {
403 sprintf (field,"%d",newField);
404 SetWindowTextA (lpipsi->hwndIP[currentfield], field);
405 return TRUE;
406 }
407 }
408 }
409 return FALSE;
410}
411
412
413static INT
414IPADDRESS_GotoNextField (LPIP_SUBCLASS_INFO lpipsi, int currentfield)
415{
416 if(currentfield >= -1 && currentfield < 4)
417 {
418 IPADDRESS_CheckField(lpipsi, currentfield); /* check the fields value */
419
420 if(currentfield < 3)
421 {
422 SetFocus (lpipsi->hwndIP[currentfield+1]);
423 return TRUE;
424 }
425 }
426 return FALSE;
427}
428
429
430/* period: move and select the text in the next field to the right if */
431/* the current field is not empty(l!=0), we are not in the */
432/* left most position, and nothing is selected(startsel==endsel) */
433
434/* spacebar: same behavior as period */
435
436/* alpha characters: completely ignored */
437
438/* digits: accepted when field text length < 2 ignored otherwise. */
439/* when 3 numbers have been entered into the field the value */
440/* of the field is checked, if the field value exceeds the */
441/* maximum value and is changed the field remains the current */
442/* field, otherwise focus moves to the field to the right */
443
444/* tab: change focus from the current ipaddress control to the next */
445/* control in the tab order */
446
447/* right arrow: move to the field on the right to the left most */
448/* position in that field if no text is selected, */
449/* we are in the right most position in the field, */
450/* we are not in the right most field */
451
452/* left arrow: move to the field on the left to the right most */
453/* position in that field if no text is selected, */
454/* we are in the left most position in the current field */
455/* and we are not in the left most field */
456
457/* backspace: delete the character to the left of the cursor position, */
458/* if none are present move to the field on the left if */
459/* we are not in the left most field and delete the right */
460/* most digit in that field while keeping the cursor */
461/* on the right side of the field */
462
463
464
465LRESULT CALLBACK
466IPADDRESS_SubclassProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
467{
468 IPADDRESS_INFO *infoPtr;
469 LPIP_SUBCLASS_INFO lpipsi=(LPIP_SUBCLASS_INFO) GetPropA ((HWND)hwnd,IP_SUBCLASS_PROP);
470 CHAR c = (CHAR)wParam;
471 INT i, l, index, startsel, endsel;
472
473 infoPtr = lpipsi->infoPtr;
474 index=0; /* FIXME */
475 for (i=0; i<=3; i++)
476 if (lpipsi->hwndIP[i]==hwnd) index=i;
477
478 switch (uMsg) {
479 case WM_CHAR:
480 if(isdigit(c)) /* process all digits */
481 {
482 int return_val;
483 SendMessageA(lpipsi->hwndIP[index], EM_GETSEL, (WPARAM)&startsel, (LPARAM)&endsel);
484 l = GetWindowTextLengthA (lpipsi->hwndIP[index]);
485 if(l==2 && startsel==endsel) /* field is full after this key is processed */
486 {
487 /* process the digit press before we check the field */
488 return_val = CallWindowProcA (lpipsi->wpOrigProc[index], hwnd, uMsg, wParam, lParam);
489
490 /* if the field value was changed stay at the current field */
491 if(!IPADDRESS_CheckField(lpipsi, index))
492 IPADDRESS_GotoNextField (lpipsi,index);
493
494 return return_val;
495 }
496
497 if(l > 2) /* field is full, stop key messages */
498 {
499 lParam = 0;
500 wParam = 0;
501 }
502 break;
503 }
504
505 if(c == '.') /* VK_PERIOD */
506 {
507 l = GetWindowTextLengthA(lpipsi->hwndIP[index]);
508 SendMessageA(lpipsi->hwndIP[index], EM_GETSEL, (WPARAM)&startsel, (LPARAM)&endsel);
509 if(l != 0 && startsel==endsel && startsel != 0)
510 {
511 IPADDRESS_GotoNextField(lpipsi, index);
512 SendMessageA(lpipsi->hwndIP[index+1], EM_SETSEL, (WPARAM)0, (LPARAM)3);
513 }
514 }
515
516 /* stop all other characters */
517 wParam = 0;
518 lParam = 0;
519 break;
520
521 case WM_KEYDOWN:
522 if(c == VK_SPACE)
523 {
524 l = GetWindowTextLengthA(lpipsi->hwndIP[index]);
525 SendMessageA(lpipsi->hwndIP[index], EM_GETSEL, (WPARAM)&startsel, (LPARAM)&endsel);
526 if(l != 0 && startsel==endsel && startsel != 0)
527 {
528 IPADDRESS_GotoNextField(lpipsi, index);
529 SendMessageA(lpipsi->hwndIP[index+1], EM_SETSEL, (WPARAM)0, (LPARAM)3);
530 }
531 }
532
533 if(c == VK_RIGHT)
534 {
535 SendMessageA(lpipsi->hwndIP[index], EM_GETSEL, (WPARAM)&startsel, (LPARAM)&endsel);
536 l = GetWindowTextLengthA (lpipsi->hwndIP[index]);
537
538 if(startsel==endsel && startsel==l)
539 {
540 IPADDRESS_GotoNextField(lpipsi, index);
541 SendMessageA(lpipsi->hwndIP[index+1], EM_SETSEL, (WPARAM)0,(LPARAM)0);
542 }
543 }
544
545 if(c == VK_LEFT)
546 {
547 SendMessageA(lpipsi->hwndIP[index], EM_GETSEL, (WPARAM)&startsel, (LPARAM)&endsel);
548 if(startsel==0 && startsel==endsel && index > 0)
549 {
550 IPADDRESS_GotoNextField(lpipsi, index - 2);
551 l = GetWindowTextLengthA(lpipsi->hwndIP[index-1]);
552 SendMessageA(lpipsi->hwndIP[index-1], EM_SETSEL, (WPARAM)l,(LPARAM)l);
553 }
554 }
555
556 if(c == VK_BACK) /* VK_BACKSPACE */
557 {
558 CHAR buf[4];
559
560 SendMessageA(lpipsi->hwndIP[index], EM_GETSEL, (WPARAM)&startsel, (LPARAM)&endsel);
561 l = GetWindowTextLengthA (lpipsi->hwndIP[index]);
562 if(startsel==endsel && startsel==0 && index > 0)
563 {
564 l = GetWindowTextLengthA(lpipsi->hwndIP[index-1]);
565 if(l!=0)
566 {
567 GetWindowTextA (lpipsi->hwndIP[index-1],buf,4);
568 buf[l-1] = '\0';
569 SetWindowTextA(lpipsi->hwndIP[index-1], buf);
570 SendMessageA(lpipsi->hwndIP[index-1], EM_SETSEL, (WPARAM)l,(LPARAM)l);
571 }
572 IPADDRESS_GotoNextField(lpipsi, index - 2);
573 }
574 }
575 break;
576
577 default:
578 break;
579 }
580 return CallWindowProcA (lpipsi->wpOrigProc[index], hwnd, uMsg, wParam, lParam);
581}
582
583static LRESULT WINAPI
584IPADDRESS_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
585{
586 switch (uMsg)
587 {
588 case IPM_CLEARADDRESS:
589 return IPADDRESS_ClearAddress (hwnd, wParam, lParam);
590
591 case IPM_SETADDRESS:
592 return IPADDRESS_SetAddress (hwnd, wParam, lParam);
593
594 case IPM_GETADDRESS:
595 return IPADDRESS_GetAddress (hwnd, wParam, lParam);
596
597 case IPM_SETRANGE:
598 return IPADDRESS_SetRange (hwnd, wParam, lParam);
599
600 case IPM_SETFOCUS:
601 return IPADDRESS_SetFocusToField (hwnd, wParam, lParam);
602
603 case IPM_ISBLANK:
604 return IPADDRESS_IsBlank (hwnd, wParam, lParam);
605
606 case WM_CREATE:
607 return IPADDRESS_Create (hwnd, wParam, lParam);
608
609 case WM_DESTROY:
610 return IPADDRESS_Destroy (hwnd, wParam, lParam);
611
612 case WM_GETDLGCODE:
613 return DLGC_WANTARROWS | DLGC_WANTCHARS;
614
615 case WM_KILLFOCUS:
616 return IPADDRESS_KillFocus (hwnd, wParam, lParam);
617
618 case WM_LBUTTONDOWN:
619 return IPADDRESS_LButtonDown (hwnd, wParam, lParam);
620
621 case WM_PAINT:
622 return IPADDRESS_Paint (hwnd, wParam);
623
624 case WM_SETFOCUS:
625 return IPADDRESS_SetFocus (hwnd, wParam, lParam);
626
627 case WM_SIZE:
628 return IPADDRESS_Size (hwnd, wParam, lParam);
629
630 default:
631// if (uMsg >= WM_USER)
632// ERR (ipaddress, "unknown msg %04x wp=%08x lp=%08lx\n",
633// uMsg, wParam, lParam);
634 return defComCtl32ProcA (hwnd, uMsg, wParam, lParam);
635 }
636 return 0;
637}
638
639
640void
641IPADDRESS_Register (void)
642{
643 WNDCLASSA wndClass;
644
645 ZeroMemory (&wndClass, sizeof(WNDCLASSA));
646 wndClass.style = CS_GLOBALCLASS;
647 wndClass.lpfnWndProc = (WNDPROC)IPADDRESS_WindowProc;
648 wndClass.cbClsExtra = 0;
649 wndClass.cbWndExtra = sizeof(IPADDRESS_INFO *);
650 wndClass.hCursor = LoadCursorA (0, IDC_ARROWA);
651 wndClass.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
652 wndClass.lpszClassName = WC_IPADDRESSA;
653
654 RegisterClassA (&wndClass);
655}
656
657VOID
658IPADDRESS_Unregister (VOID)
659{
660 UnregisterClassA (WC_IPADDRESSA, (HINSTANCE)NULL);
661}
662
Note: See TracBrowser for help on using the repository browser.