4 * Copyright 2000 Mike McCormack <Mike_McCormack@looksmart.com.au>
5 * Copyright 1997,98 Marcel Baur <mbaur@g26.ethz.ch>
6 * To be distributed under the Wine License
9 * - Use wine Heap instead of malloc/free (done)
10 * - use scroll bars (vertical done)
11 * - cut 'n paste (clipboard)
15 * - encapsulate data structures (?) - half done
16 * - free unused memory
17 * - solve Open problems
18 * - smoother scrolling
19 * - separate view code and document code
21 * This program is intended as a testbed for winelib as much as
22 * a useful application.
37 extern BOOL FileExists(LPCSTR szFilename);
38 extern BOOL DoCloseFile(void);
39 extern void DoOpenFile(LPCSTR szFileName);
41 NOTEPAD_GLOBALS Globals;
44 /* Using a pointer to pointer data structure to
45 achieve a little more efficiency. Hopefully
46 it will be worth it, because it complicates the
47 code - mjm 26 Jun 2000 */
49 #define BUFFERCHUNKSIZE 0xe0
50 typedef struct TAGLine {
56 /* FIXME: make this info into a structure */
57 /* typedef struct tagBUFFER { */
62 DWORD dwXpos=0,dwYpos=0; /* position of caret in char coords */
63 DWORD dwCaretXpos=0,dwCaretYpos=0; /* position of caret in pixel coords */
64 TEXTMETRIC tm; /* textmetric for current font */
65 RECT rectClient; /* client rectangle of the window we're drawing in */
66 /* } BUFFER, *LPBUFFER */
68 VOID InitFontInfo(HWND hWnd)
70 HDC hDC = GetDC(hWnd);
74 GetTextMetrics(hDC, &tm);
88 /* convert x,y character co-ords into x pixel co-ord */
89 DWORD CalcStringWidth(HDC hDC, DWORD x, DWORD y)
101 if(lpBuffer[y].lpLine == NULL)
103 len = (x<lpBuffer[y].dwWidth) ?
104 x : lpBuffer[y].dwWidth;
105 GetTextExtentPoint(hDC, lpBuffer[y].lpLine, len, &size);
110 void CalcCaretPos(HDC hDC, DWORD dwXpos, DWORD dwYpos)
112 dwCaretXpos = CalcStringWidth(hDC, dwXpos, dwYpos);
113 dwCaretYpos = tm.tmHeight*(dwYpos-dwVOffset);
114 SetCaretPos(dwCaretXpos,dwCaretYpos);
117 DWORD GetLinesPerPage(HWND hWnd)
119 return (rectClient.bottom/tm.tmHeight); /* round down */
122 /* render one line of text and blank space */
123 void RenderLine(HDC hDC, DWORD lineno)
131 /* erase the space at the end of a line using a white rectangle */
132 rect.top = tm.tmHeight*(lineno-dwVOffset);
133 rect.bottom = tm.tmHeight*(lineno-dwVOffset+1);
135 if(lpBuffer && (lineno<dwLines))
136 rect.left = CalcStringWidth(hDC, lpBuffer[lineno].dwWidth,lineno);
139 rect.right = rectClient.right;
141 /* use the white pen so there's not outline */
142 hPrev = SelectObject(hDC, GetStockObject(WHITE_PEN));
143 Rectangle(hDC, rect.left, rect.top, rect.right, rect.bottom);
144 SelectObject(hDC, hPrev);
146 if(lpBuffer && lpBuffer[lineno].lpLine)
148 TextOut(hDC, 0, rect.top, lpBuffer[lineno].lpLine,
149 lpBuffer[lineno].dwWidth);
154 * Paint the buffer onto the window.
156 void RenderWindow(HDC hDC) {
162 /* FIXME: render only necessary lines */
163 for(i = dwVOffset; i < (dwVOffset+GetLinesPerPage(0)); i++)
170 * Check that correct buffers exist to access line y pos x
171 * Only manages memory.
173 * Returns TRUE if the line is accessable
174 * FALSE if there is a problem
176 BOOL ValidateLine(DWORD y,DWORD x)
180 /* check to see that the BUFFER has enough lines */
182 if( (max<=y) || (lpBuffer == NULL))
185 max += BUFFERCHUNKSIZE;
186 /* use GlobalAlloc first time round */
188 lpBuffer = (LPLINE) GlobalReAlloc((HGLOBAL)lpBuffer,GMEM_FIXED,
191 lpBuffer = (LPLINE) GlobalAlloc(GMEM_FIXED, max*sizeof(LINE));
194 ZeroMemory(&lpBuffer[dwLines], sizeof(LINE)*(max-dwLines) );
198 /* check to see that the LINE is wide enough */
199 max = lpBuffer[y].dwMaxWidth;
200 if( (max <= x) || (lpBuffer[y].lpLine == NULL) )
203 max += BUFFERCHUNKSIZE;
204 /* use GlobalAlloc first */
205 if(lpBuffer[y].lpLine)
206 lpBuffer[y].lpLine = (LPSTR)GlobalReAlloc((HGLOBAL)lpBuffer[y].lpLine,
209 lpBuffer[y].lpLine = (LPSTR)GlobalAlloc( GMEM_FIXED, max);
210 if(lpBuffer[y].lpLine == NULL)
212 lpBuffer[y].dwWidth = 0;
213 lpBuffer[y].dwMaxWidth = max;
218 /* inserts a new line into the buffer */
219 BOOL DoNewLine(HDC hDC)
224 /* check to see if we need more memory for the buffer pointers */
225 if(!ValidateLine(dwLines,0))
228 /* shuffle up all the lines */
229 for(i=dwLines; i>(dwYpos+1); i--)
231 lpBuffer[i] = lpBuffer[i-1];
234 ZeroMemory(&lpBuffer[dwYpos+1],sizeof(LINE));
236 /* copy the characters after the carat (if any) to the next line */
237 src = &lpBuffer[dwYpos].lpLine[dwXpos];
238 cnt = lpBuffer[dwYpos].dwWidth-dwXpos;
239 if(!ValidateLine(dwYpos+1,cnt)) /* allocates the buffer */
240 return FALSE; /* FIXME */
241 dst = &lpBuffer[dwYpos+1].lpLine[0];
242 memcpy(dst, src, cnt);
243 lpBuffer[dwYpos+1].dwWidth = cnt;
244 lpBuffer[dwYpos].dwWidth -= cnt;
246 /* move the cursor */
251 /* update the window */
252 RenderLine(hDC, dwYpos-1);
253 RenderLine(hDC, dwYpos);
254 CalcCaretPos(hDC, dwXpos, dwYpos);
255 /* FIXME: don't use globals */
256 SetScrollRange(Globals.hMainWnd, SB_VERT, 0, dwLines, TRUE);
262 * Attempt a basic edit buffer
264 BOOL AddCharToBuffer(HDC hDC, char ch)
266 /* we can use lpBuffer[dwYpos] */
267 if(!ValidateLine(dwYpos,0))
270 /* shuffle the rest of the line*/
271 if(!ValidateLine(dwYpos, lpBuffer[dwYpos].dwWidth))
273 lpBuffer[dwYpos].dwWidth++;
274 memmove(&lpBuffer[dwYpos].lpLine[dwXpos+1],
275 &lpBuffer[dwYpos].lpLine[dwXpos],
276 lpBuffer[dwYpos].dwWidth-dwXpos);
278 /* add the character */
279 lpBuffer[dwYpos].lpLine[dwXpos] = ch;
284 /* update the window and cursor position */
285 RenderLine(hDC,dwYpos);
286 CalcCaretPos(hDC,dwXpos,dwYpos);
292 /* erase a character */
293 BOOL DoBackSpace(HDC hDC)
299 if(lpBuffer[dwYpos].lpLine && (dwXpos>0))
302 /* FIXME: use memmove */
303 for(i=dwXpos; i<(lpBuffer[dwYpos].dwWidth-1); i++)
304 lpBuffer[dwYpos].lpLine[i]=lpBuffer[dwYpos].lpLine[i+1];
306 lpBuffer[dwYpos].dwWidth--;
307 RenderLine(hDC, dwYpos);
308 CalcCaretPos(hDC,dwXpos,dwYpos);
312 /* Erase a newline. To do this we join two lines */
319 oldlen = lpBuffer[dwYpos-1].dwWidth;
320 if(lpBuffer[dwYpos-1].lpLine&&lpBuffer[dwYpos].lpLine)
322 /* concatonate to the end of the line above line */
323 src = &lpBuffer[dwYpos].lpLine[0];
324 dest = &lpBuffer[dwYpos-1].lpLine[lpBuffer[dwYpos-1].dwWidth];
325 len = lpBuffer[dwYpos].dwWidth;
327 /* check the length of the new line */
328 if(!ValidateLine(dwYpos-1,lpBuffer[dwYpos-1].dwWidth + len))
331 memcpy(dest,src,len);
332 lpBuffer[dwYpos-1].dwWidth+=len;
333 GlobalFree( (HGLOBAL)lpBuffer[dwYpos].lpLine);
335 else if (!lpBuffer[dwYpos-1].lpLine)
337 lpBuffer[dwYpos-1]=lpBuffer[dwYpos];
338 } /* else both are NULL */
339 RenderLine(hDC,dwYpos-1);
341 /* don't zero - it's going to get trashed anyhow */
343 /* shuffle up all the lines below this one */
344 for(i=dwYpos; i<(dwLines-1); i++)
346 lpBuffer[i] = lpBuffer[i+1];
350 /* clear the last line */
351 ZeroMemory(&lpBuffer[dwLines-1],sizeof (LINE));
352 RenderLine(hDC,dwLines-1);
355 /* adjust the cursor position to joining point */
359 CalcCaretPos(hDC,dwXpos,dwYpos);
360 SetScrollRange(Globals.hMainWnd, SB_VERT, 0, dwLines, TRUE);
365 /* as used by File->New */
366 void TrashBuffer(void)
370 /* variables belonging to the buffer */
373 for(i=0; i<dwLines; i++)
375 if(lpBuffer[i].lpLine)
376 GlobalFree ((HGLOBAL)lpBuffer[i].lpLine);
377 ZeroMemory(&lpBuffer[i],sizeof (LINE));
379 GlobalFree((HGLOBAL)lpBuffer);
385 /* variables belonging to the view */
389 /* FIXME: don't use globals */
390 SetScrollPos(Globals.hMainWnd, SB_VERT, dwVOffset, FALSE);
391 SetScrollRange(Globals.hMainWnd, SB_VERT, 0, dwLines, TRUE);
396 * Add a line to the buffer
398 /* FIXME: this breaks lines longer than BUFFERCHUNKSIZE */
400 LPSTR buffer, /* pointer to buffer with file data */
401 DWORD size, /* number of bytes available in buffer */
409 for(i=0; i<size; i++)
413 if(ValidateLine(dwLines,i))
415 memcpy(&lpBuffer[dwLines].lpLine[0],&buffer[0],i);
416 lpBuffer[dwLines].dwWidth = i;
423 /* make a line of the rest */
424 if( (i == BUFFERCHUNKSIZE) || nomore )
426 if(ValidateLine(dwLines,i))
428 memcpy(&lpBuffer[dwLines].lpLine[0],&buffer[0],i);
429 lpBuffer[dwLines].dwWidth = i;
440 * This is probably overcomplicated by lpBuffer data structure...
441 * Read blocks from the file, then add each line from the
442 * block to the buffer until there is none left. If all
443 * a slab isn't used, try load some more data from the file.
445 void LoadBufferFromFile(LPCSTR szFileName)
449 DWORD size,i,len,bytes_left,bytes_read;
451 hFile = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
452 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
453 if(hFile == INVALID_HANDLE_VALUE)
455 size = BUFFERCHUNKSIZE;
456 pTemp = (LPSTR) GlobalAlloc(GMEM_FIXED, size);
459 bytes_read = 1; /* anything non-zero */
468 bytes_left+=bytes_read;
470 /* add strings to buffer */
473 (len = CreateLine(&pTemp[i], bytes_left, !bytes_read));
474 i+= len,bytes_left-=len );
476 /* move leftover to front of buffer */
478 memmove(&pTemp[0],&pTemp[i], bytes_left);
483 BOOL DoInput(HDC hDC, WPARAM wParam, LPARAM lParam)
488 return DoBackSpace(hDC);
490 return DoNewLine(hDC);
492 return AddCharToBuffer(hDC,wParam);
496 BOOL GotoHome(HWND hWnd)
504 BOOL GotoEndOfLine(HWND hWnd)
506 dwXpos = lpBuffer[dwYpos].dwWidth;
510 BOOL GotoDown(HWND hWnd)
512 if((dwYpos+1) >= dwLines)
517 if (dwXpos>lpBuffer[dwYpos].dwWidth)
522 BOOL GotoUp(HWND hWnd)
527 if (dwXpos>lpBuffer[dwYpos].dwWidth)
532 BOOL GotoLeft(HWND hWnd)
540 return GotoEndOfLine(hWnd);
544 BOOL GotoRight(HWND hWnd)
546 if(dwXpos<lpBuffer[dwYpos].dwWidth)
557 /* check the caret is still on the screen */
558 BOOL ScrollABit(HWND hWnd)
565 if(dwYpos>(dwVOffset+GetLinesPerPage(hWnd)))
567 dwVOffset = dwYpos - GetLinesPerPage(hWnd) + 1;
573 /* FIXME: move the window around so we can still see the caret */
574 VOID DoEdit(HWND hWnd, WPARAM wParam, LPARAM lParam)
582 case VK_HOME: GotoHome(hWnd);
585 case VK_END: GotoEndOfLine(hWnd);
588 case VK_LEFT: GotoLeft(hWnd);
591 case VK_RIGHT: GotoRight(hWnd);
594 case VK_DOWN: GotoDown(hWnd);
597 case VK_UP: GotoUp(hWnd);
607 CalcCaretPos(hDC, dwXpos, dwYpos);
611 InvalidateRect(hWnd, NULL, FALSE);
614 void ButtonDownToCaretPos(HWND hWnd, WPARAM wParam, LPARAM lParam)
616 DWORD x, y, caretx, carety;
617 BOOL refine_guess = TRUE;
623 caretx = x/tm.tmAveCharWidth; /* guess */
624 carety = dwVOffset + y/tm.tmHeight;
632 refine_guess = FALSE;
635 /* if the cursor is past the bottom, put it after the last char */
636 if(refine_guess && (carety>=dwLines) )
639 caretx=lpBuffer[carety].dwWidth;
640 refine_guess = FALSE;
643 /* cursor past end of line? */
644 if(refine_guess && (x>CalcStringWidth(hDC,lpBuffer[carety].dwWidth,carety)))
646 caretx = lpBuffer[carety].dwWidth;
647 refine_guess = FALSE;
650 /* FIXME: doesn't round properly */
653 if(CalcStringWidth(hDC,caretx,carety)<x)
655 while( (caretx<lpBuffer[carety].dwWidth) &&
656 (CalcStringWidth(hDC,caretx+1,carety)<x))
661 while((caretx>0)&&(CalcStringWidth(hDC,caretx-1,carety)>x))
666 /* set the caret's position */
669 CalcCaretPos(hDC, caretx, carety);
673 void DoScroll(HWND hWnd, WPARAM wParam, LPARAM lParam)
675 DWORD dy = GetLinesPerPage(hWnd);
677 switch(wParam) /* vscroll code */
684 if(dwVOffset<dwLines)
688 if( (dy+dwVOffset) > dwLines)
689 dwVOffset = dwLines - 1;
696 /* position scroll */
697 SetScrollPos(hWnd, SB_VERT, dwVOffset, TRUE);
700 /***********************************************************************
702 * NOTEPAD_MenuCommand
704 * All handling of main menu events
707 int NOTEPAD_MenuCommand (WPARAM wParam)
710 case NP_FILE_NEW: DIALOG_FileNew(); break;
711 case NP_FILE_OPEN: DIALOG_FileOpen(); break;
712 case NP_FILE_SAVE: DIALOG_FileSave(); break;
713 case NP_FILE_SAVEAS: DIALOG_FileSaveAs(); break;
714 case NP_FILE_PRINT: DIALOG_FilePrint(); break;
715 case NP_FILE_PAGESETUP: DIALOG_FilePageSetup(); break;
716 case NP_FILE_PRINTSETUP: DIALOG_FilePrinterSetup();break;
717 case NP_FILE_EXIT: DIALOG_FileExit(); break;
719 case NP_EDIT_UNDO: DIALOG_EditUndo(); break;
720 case NP_EDIT_CUT: DIALOG_EditCut(); break;
721 case NP_EDIT_COPY: DIALOG_EditCopy(); break;
722 case NP_EDIT_PASTE: DIALOG_EditPaste(); break;
723 case NP_EDIT_DELETE: DIALOG_EditDelete(); break;
724 case NP_EDIT_SELECTALL: DIALOG_EditSelectAll(); break;
725 case NP_EDIT_TIMEDATE: DIALOG_EditTimeDate();break;
726 case NP_EDIT_WRAP: DIALOG_EditWrap(); break;
728 case NP_SEARCH_SEARCH: DIALOG_Search(); break;
729 case NP_SEARCH_NEXT: DIALOG_SearchNext(); break;
731 case NP_HELP_CONTENTS: DIALOG_HelpContents(); break;
732 case NP_HELP_SEARCH: DIALOG_HelpSearch(); break;
733 case NP_HELP_ON_HELP: DIALOG_HelpHelp(); break;
734 case NP_HELP_LICENSE: DIALOG_HelpLicense(); break;
735 case NP_HELP_NO_WARRANTY: DIALOG_HelpNoWarranty(); break;
736 case NP_HELP_ABOUT_WINE: DIALOG_HelpAboutWine(); break;
738 /* Handle languages */
740 LANGUAGE_DefaultHandle(wParam);
747 /***********************************************************************
752 LRESULT WINAPI NOTEPAD_WndProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
756 HANDLE hDrop; /* drag & drop */
757 CHAR szFileName[MAX_STRING_LEN];
760 lstrcpy(szFileName, "");
765 GetClientRect(hWnd, &rectClient);
770 CreateCaret(Globals.hMainWnd, 0, 1, tm.tmHeight);
771 SetCaretPos(dwCaretXpos, dwCaretYpos);
772 ShowCaret(Globals.hMainWnd);
780 GetClientRect(hWnd, &rectClient);
781 hContext = BeginPaint(hWnd, &ps);
782 RenderWindow(hContext);
787 DoEdit(hWnd, wParam, lParam);
791 GetClientRect(hWnd, &rectClient);
793 hContext = GetDC(hWnd);
794 DoInput(hContext,wParam,lParam);
795 ReleaseDC(hWnd,hContext);
800 /* figure out where the mouse was clicked */
801 ButtonDownToCaretPos(hWnd, wParam, lParam);
805 DoScroll(hWnd, wParam, lParam);
806 InvalidateRect(hWnd, NULL, FALSE); /* force a redraw */
810 /* FIXME: this is a bit messy */
811 NOTEPAD_MenuCommand(wParam);
812 InvalidateRect(hWnd, NULL, FALSE); /* force a redraw */
813 hContext = GetDC(hWnd);
814 CalcCaretPos(hContext,dwXpos,dwYpos);
815 ReleaseDC(hWnd,hContext);
818 case WM_DESTROYCLIPBOARD:
819 MessageBox(Globals.hMainWnd, "Empty clipboard", "Debug", MB_ICONEXCLAMATION);
833 GetClientRect(Globals.hMainWnd, &Windowsize);
837 /* User has dropped a file into main window */
838 hDrop = (HANDLE) wParam;
839 DragQueryFile(hDrop, 0, (CHAR *) &szFileName, sizeof(szFileName));
841 DoOpenFile(szFileName);
845 return DefWindowProc (hWnd, msg, wParam, lParam);
850 int AlertFileDoesNotExist(LPSTR szFileName) {
853 CHAR szMessage[MAX_STRING_LEN];
854 CHAR szRessource[MAX_STRING_LEN];
856 LoadString(Globals.hInstance, IDS_DOESNOTEXIST, szRessource,
857 sizeof(szRessource));
858 wsprintf(szMessage, szRessource, szFileName);
860 LoadString(Globals.hInstance, IDS_ERROR, szRessource, sizeof(szRessource));
862 nResult = MessageBox(Globals.hMainWnd, szMessage, szRessource,
863 MB_ICONEXCLAMATION | MB_YESNO);
868 void HandleCommandLine(LPSTR cmdline)
871 while (*cmdline && (*cmdline == ' ' || *cmdline == '-'))
876 if (*cmdline++ == ' ') continue;
879 if (option) cmdline++;
880 while (*cmdline && *cmdline == ' ') cmdline++;
885 case 'P': printf("Print file: ");
886 /* Not yet able to print a file */
893 /* file name is passed in the command line */
898 if (FileExists(cmdline))
905 /* try to find file with ".txt" extention */
906 if (!strcmp(".txt", cmdline + strlen(cmdline) - strlen(".txt")))
913 strncpy(buf, cmdline, MAX_PATH - strlen(".txt") - 1);
916 file_exists = FileExists(buf);
922 DoOpenFile(file_name);
923 InvalidateRect(Globals.hMainWnd, NULL, FALSE);
927 switch (AlertFileDoesNotExist(file_name)) {
929 DoOpenFile(file_name);
940 /***********************************************************************
945 int PASCAL WinMain (HINSTANCE hInstance, HINSTANCE prev, LPSTR cmdline, int show)
949 char className[] = "NPClass"; /* To make sure className >= 0x10000 */
950 char winName[] = "Notepad";
957 Globals.lpszIniFile = "notepad.ini";
958 Globals.lpszIcoFile = "notepad.ico";
960 Globals.hInstance = hInstance;
963 Globals.hMainIcon = ExtractIcon(Globals.hInstance,
964 Globals.lpszIcoFile, 0);
966 if (!Globals.hMainIcon) {
967 Globals.hMainIcon = LoadIcon(0, MAKEINTRESOURCE(DEFAULTICON));
970 lstrcpy(Globals.szFindText, "");
971 lstrcpy(Globals.szFileName, "");
972 lstrcpy(Globals.szMarginTop, "25 mm");
973 lstrcpy(Globals.szMarginBottom, "25 mm");
974 lstrcpy(Globals.szMarginLeft, "20 mm");
975 lstrcpy(Globals.szMarginRight, "20 mm");
976 lstrcpy(Globals.szHeader, "&n");
977 lstrcpy(Globals.szFooter, "Page &s");
978 lstrcpy(Globals.Buffer, "Hello World");
981 class.style = CS_HREDRAW | CS_VREDRAW;
982 class.lpfnWndProc = NOTEPAD_WndProc;
983 class.cbClsExtra = 0;
984 class.cbWndExtra = 0;
985 class.hInstance = Globals.hInstance;
986 class.hIcon = LoadIcon (0, IDI_APPLICATION);
987 class.hCursor = LoadCursor (0, IDC_ARROW);
988 class.hbrBackground = GetStockObject (WHITE_BRUSH);
989 class.lpszMenuName = 0;
990 class.lpszClassName = className;
993 if (!RegisterClass (&class)) return FALSE;
998 Globals.hMainWnd = CreateWindow (className, winName,
999 WS_OVERLAPPEDWINDOW + WS_HSCROLL + WS_VSCROLL,
1000 CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, 0,
1001 LoadMenu(Globals.hInstance, STRING_MENU_Xx),
1002 Globals.hInstance, 0);
1004 Globals.hFindReplaceDlg = 0;
1006 LANGUAGE_SelectByNumber(0);
1008 SetMenu(Globals.hMainWnd, Globals.hMainMenu);
1010 ShowWindow (Globals.hMainWnd, show);
1011 UpdateWindow (Globals.hMainWnd);
1013 /* Set up dialogs */
1015 /* Identify Messages originating from FindReplace */
1017 Globals.nCommdlgFindReplaceMsg = RegisterWindowMessage("commdlg_FindReplace");
1018 if (Globals.nCommdlgFindReplaceMsg==0) {
1019 MessageBox(Globals.hMainWnd, "Could not register commdlg_FindReplace window message",
1020 "Error", MB_ICONEXCLAMATION);
1023 HandleCommandLine(cmdline);
1025 /* Set up Drag&Drop */
1027 DragAcceptFiles(Globals.hMainWnd, TRUE);
1029 /* now enter mesage loop */
1031 while (GetMessage (&msg, 0, 0, 0)) {
1032 if (IsDialogMessage(Globals.hFindReplaceDlg, &msg)!=0) {
1033 /* Message belongs to FindReplace dialog */
1034 /* We just let IsDialogMessage handle it */
1038 /* Message belongs to the Notepad Main Window */
1039 TranslateMessage (&msg);
1040 DispatchMessage (&msg);