xref: /reactos/base/applications/mspaint/main.cpp (revision 3a49e26f)
1 /*
2  * PROJECT:    PAINT for ReactOS
3  * LICENSE:    LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later)
4  * PURPOSE:    The main window and wWinMain etc.
5  * COPYRIGHT:  Copyright 2015 Benedikt Freisen <b.freisen@gmx.net>
6  *             Copyright 2017-2023 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
7  *             Copyright 2018 Stanislav Motylkov <x86corez@gmail.com>
8  */
9 
10 #include "precomp.h"
11 
12 #include <dlgs.h>
13 #include <mapi.h>
14 #include <assert.h>
15 
16 BOOL g_askBeforeEnlarging = FALSE;  // TODO: initialize from registry
17 HINSTANCE g_hinstExe = NULL;
18 WCHAR g_szFileName[MAX_LONG_PATH] = { 0 };
19 WCHAR g_szMailTempFile[MAX_LONG_PATH] = { 0 };
20 BOOL g_isAFile = FALSE;
21 BOOL g_imageSaved = FALSE;
22 BOOL g_showGrid = FALSE;
23 HWND g_hStatusBar = NULL;
24 
25 CMainWindow mainWindow;
26 
27 typedef HWND (WINAPI *FN_HtmlHelpW)(HWND, LPCWSTR, UINT, DWORD_PTR);
28 
29 static HINSTANCE s_hHHCTRL_OCX = NULL; // HtmlHelpW needs "hhctrl.ocx"
30 static FN_HtmlHelpW s_pHtmlHelpW = NULL;
31 
32 /* FUNCTIONS ********************************************************/
33 
34 void ShowOutOfMemory(void)
35 {
36     WCHAR szText[256];
37     ::FormatMessageW(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM,
38                      NULL,
39                      ERROR_OUTOFMEMORY,
40                      0,
41                      szText, _countof(szText),
42                      NULL);
43     mainWindow.MessageBox(szText, NULL, MB_ICONERROR);
44 }
45 
46 // get file name extension from filter string
47 static BOOL
48 FileExtFromFilter(LPWSTR pExt, OPENFILENAME *pOFN)
49 {
50     LPWSTR pchExt = pExt;
51     *pchExt = 0;
52 
53     DWORD nIndex = 1;
54     for (LPCWSTR pch = pOFN->lpstrFilter; *pch; ++nIndex)
55     {
56         pch += lstrlen(pch) + 1;
57         if (pOFN->nFilterIndex == nIndex)
58         {
59             for (++pch; *pch && *pch != L';'; ++pch)
60             {
61                 *pchExt++ = *pch;
62             }
63             *pchExt = 0;
64             CharLower(pExt);
65             return TRUE;
66         }
67         pch += wcslen(pch) + 1;
68     }
69     return FALSE;
70 }
71 
72 // Hook procedure for OPENFILENAME to change the file name extension
73 static UINT_PTR APIENTRY
74 OFNHookProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
75 {
76     HWND hParent;
77     OFNOTIFYW *pon;
78     WCHAR Path[MAX_PATH];
79     switch (uMsg)
80     {
81     case WM_NOTIFY:
82         pon = (OFNOTIFYW *)lParam;
83         if (pon->hdr.code == CDN_TYPECHANGE)
84         {
85             hParent = GetParent(hwnd);
86             SendMessageW(hParent, CDM_GETFILEPATH, _countof(Path), (LPARAM)Path);
87             FileExtFromFilter(PathFindExtensionW(Path), pon->lpOFN);
88             SendMessageW(hParent, CDM_SETCONTROLTEXT, cmb13, (LPARAM)PathFindFileNameW(Path));
89             StringCchCopyW(pon->lpOFN->lpstrFile, pon->lpOFN->nMaxFile, Path);
90         }
91         break;
92     }
93     return 0;
94 }
95 
96 typedef ULONG (WINAPI *FN_MAPISendMail)(LHANDLE, ULONG_PTR, lpMapiMessage, FLAGS, ULONG);
97 typedef ULONG (WINAPI *FN_MAPISendMailW)(LHANDLE, ULONG_PTR, lpMapiMessageW, FLAGS, ULONG);
98 
99 BOOL OpenMailer(HWND hWnd, LPCWSTR pszPathName)
100 {
101     // Delete the temporary file if any
102     if (g_szMailTempFile[0])
103     {
104         ::DeleteFileW(g_szMailTempFile);
105         g_szMailTempFile[0] = UNICODE_NULL;
106     }
107 
108     CStringW strFileTitle;
109     if (PathFileExistsW(pszPathName) && imageModel.IsImageSaved())
110     {
111         strFileTitle = PathFindFileNameW(pszPathName);
112     }
113     else // Not existing or not saved
114     {
115         // Get the name of a temporary file
116         WCHAR szTempDir[MAX_PATH];
117         ::GetTempPathW(_countof(szTempDir), szTempDir);
118         if (!::GetTempFileNameW(szTempDir, L"afx", 0, g_szMailTempFile))
119             return FALSE; // Failure
120 
121         if (PathFileExistsW(g_szFileName))
122         {
123             // Set file title
124             strFileTitle = PathFindFileNameW(g_szFileName);
125 
126             // Copy to the temporary file
127             if (!::CopyFileW(g_szFileName, g_szMailTempFile, FALSE))
128             {
129                 g_szMailTempFile[0] = UNICODE_NULL;
130                 return FALSE; // Failure
131             }
132         }
133         else
134         {
135             // Set file title
136             strFileTitle.LoadString(IDS_DEFAULTFILENAME);
137             strFileTitle += L".png";
138 
139             // Save it to the temporary file
140             HBITMAP hbmLocked = imageModel.LockBitmap();
141             BOOL ret = SaveDIBToFile(hbmLocked, g_szMailTempFile, FALSE, Gdiplus::ImageFormatPNG);
142             imageModel.UnlockBitmap(hbmLocked);
143             if (!ret)
144             {
145                 g_szMailTempFile[0] = UNICODE_NULL;
146                 return FALSE; // Failure
147             }
148         }
149 
150         // Use the temporary file
151         pszPathName = g_szMailTempFile;
152     }
153 
154     // Load "mapi32.dll"
155     HINSTANCE hMAPI = LoadLibraryW(L"mapi32.dll");
156     if (!hMAPI)
157         return FALSE; // Failure
158 
159     // Attachment
160     MapiFileDescW attachmentW = { 0 };
161     attachmentW.nPosition = (ULONG)-1;
162     attachmentW.lpszPathName = (LPWSTR)pszPathName;
163     attachmentW.lpszFileName = (LPWSTR)(LPCWSTR)strFileTitle;
164 
165     // Message with attachment
166     MapiMessageW messageW = { 0 };
167     messageW.lpszSubject = NULL;
168     messageW.nFileCount = 1;
169     messageW.lpFiles = &attachmentW;
170 
171     // First, try to open the mailer by the function of Unicode version
172     FN_MAPISendMailW pMAPISendMailW = (FN_MAPISendMailW)::GetProcAddress(hMAPI, "MAPISendMailW");
173     if (pMAPISendMailW)
174     {
175         pMAPISendMailW(0, (ULONG_PTR)hWnd, &messageW, MAPI_DIALOG | MAPI_LOGON_UI, 0);
176         ::FreeLibrary(hMAPI);
177         return TRUE; // MAPISendMailW will show an error message on failure
178     }
179 
180     // Convert to ANSI strings
181     CStringA szPathNameA(pszPathName), szFileTitleA(strFileTitle);
182 
183     MapiFileDesc attachment = { 0 };
184     attachment.nPosition = (ULONG)-1;
185     attachment.lpszPathName = (LPSTR)(LPCSTR)szPathNameA;
186     attachment.lpszFileName = (LPSTR)(LPCSTR)szFileTitleA;
187 
188     MapiMessage message = { 0 };
189     message.lpszSubject = NULL;
190     message.nFileCount = 1;
191     message.lpFiles = &attachment;
192 
193     // Try again but in ANSI version
194     FN_MAPISendMail pMAPISendMail = (FN_MAPISendMail)::GetProcAddress(hMAPI, "MAPISendMail");
195     if (pMAPISendMail)
196     {
197         pMAPISendMail(0, (ULONG_PTR)hWnd, &message, MAPI_DIALOG | MAPI_LOGON_UI, 0);
198         ::FreeLibrary(hMAPI);
199         return TRUE; // MAPISendMail will show an error message on failure
200     }
201 
202     ::FreeLibrary(hMAPI);
203     return FALSE; // Failure
204 }
205 
206 BOOL CMainWindow::GetOpenFileName(IN OUT LPWSTR pszFile, INT cchMaxFile)
207 {
208     static OPENFILENAMEW ofn = { 0 };
209     static CStringW strFilter;
210 
211     if (ofn.lStructSize == 0)
212     {
213         // The "All Files" item text
214         CStringW strAllPictureFiles;
215         strAllPictureFiles.LoadString(g_hinstExe, IDS_ALLPICTUREFILES);
216 
217         // Get the import filter
218         CSimpleArray<GUID> aguidFileTypesI;
219         CImage::GetImporterFilterString(strFilter, aguidFileTypesI, strAllPictureFiles,
220                                         CImage::excludeDefaultLoad, L'|');
221         strFilter.Replace(L'|', UNICODE_NULL);
222 
223         // Initializing the OPENFILENAME structure for GetOpenFileName
224         ZeroMemory(&ofn, sizeof(ofn));
225         ofn.lStructSize = sizeof(ofn);
226         ofn.hwndOwner   = m_hWnd;
227         ofn.hInstance   = g_hinstExe;
228         ofn.lpstrFilter = strFilter;
229         ofn.Flags       = OFN_EXPLORER | OFN_HIDEREADONLY;
230         ofn.lpstrDefExt = L"png";
231     }
232 
233     ofn.lpstrFile = pszFile;
234     ofn.nMaxFile  = cchMaxFile;
235     return ::GetOpenFileNameW(&ofn);
236 }
237 
238 BOOL CMainWindow::GetSaveFileName(IN OUT LPWSTR pszFile, INT cchMaxFile)
239 {
240     static OPENFILENAMEW sfn = { 0 };
241     static CStringW strFilter;
242 
243     if (sfn.lStructSize == 0)
244     {
245         // Get the export filter
246         CSimpleArray<GUID> aguidFileTypesE;
247         CImage::GetExporterFilterString(strFilter, aguidFileTypesE, NULL,
248                                         CImage::excludeDefaultSave, L'|');
249         strFilter.Replace(L'|', UNICODE_NULL);
250 
251         // Initializing the OPENFILENAME structure for GetSaveFileName
252         ZeroMemory(&sfn, sizeof(sfn));
253         sfn.lStructSize = sizeof(sfn);
254         sfn.hwndOwner   = m_hWnd;
255         sfn.hInstance   = g_hinstExe;
256         sfn.lpstrFilter = strFilter;
257         sfn.Flags       = OFN_EXPLORER | OFN_OVERWRITEPROMPT | OFN_ENABLEHOOK;
258         sfn.lpfnHook    = OFNHookProc;
259         sfn.lpstrDefExt = L"png";
260 
261         LPWSTR pchDotExt = PathFindExtensionW(pszFile);
262         if (*pchDotExt == UNICODE_NULL)
263         {
264             // Choose PNG
265             StringCchCatW(pszFile, cchMaxFile, L".png");
266             for (INT i = 0; i < aguidFileTypesE.GetSize(); ++i)
267             {
268                 if (aguidFileTypesE[i] == Gdiplus::ImageFormatPNG)
269                 {
270                     sfn.nFilterIndex = i + 1;
271                     break;
272                 }
273             }
274         }
275     }
276 
277     sfn.lpstrFile = pszFile;
278     sfn.nMaxFile  = cchMaxFile;
279     return ::GetSaveFileNameW(&sfn);
280 }
281 
282 BOOL CMainWindow::ChooseColor(IN OUT COLORREF *prgbColor)
283 {
284     static CHOOSECOLOR choosecolor = { 0 };
285     static COLORREF custColors[16] =
286     {
287         0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff,
288         0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff, 0xffffff
289     };
290 
291     if (choosecolor.lStructSize == 0)
292     {
293         // Initializing the CHOOSECOLOR structure for ChooseColor
294         ZeroMemory(&choosecolor, sizeof(choosecolor));
295         choosecolor.lStructSize  = sizeof(choosecolor);
296         choosecolor.hwndOwner    = m_hWnd;
297         choosecolor.lpCustColors = custColors;
298     }
299 
300     choosecolor.Flags = CC_RGBINIT;
301     choosecolor.rgbResult = *prgbColor;
302     if (!::ChooseColor(&choosecolor))
303         return FALSE;
304 
305     *prgbColor = choosecolor.rgbResult;
306     return TRUE;
307 }
308 
309 HWND CMainWindow::DoCreate()
310 {
311     ::LoadStringW(g_hinstExe, IDS_DEFAULTFILENAME, g_szFileName, _countof(g_szFileName));
312 
313     CStringW strTitle;
314     strTitle.Format(IDS_WINDOWTITLE, PathFindFileName(g_szFileName));
315 
316     RECT& rc = registrySettings.WindowPlacement.rcNormalPosition;
317     return Create(HWND_DESKTOP, rc, strTitle, WS_OVERLAPPEDWINDOW, WS_EX_ACCEPTFILES);
318 }
319 
320 // A wrapper function for HtmlHelpW
321 static HWND DoHtmlHelpW(HWND hwndCaller, LPCWSTR pszFile, UINT uCommand, DWORD_PTR dwData)
322 {
323     WCHAR szPath[MAX_PATH];
324 
325     if (!s_hHHCTRL_OCX && (uCommand != HH_CLOSE_ALL))
326     {
327         // The function loads the system library, not local
328         GetSystemDirectoryW(szPath, _countof(szPath));
329         StringCchCatW(szPath, _countof(szPath), L"\\hhctrl.ocx");
330         s_hHHCTRL_OCX = LoadLibraryW(szPath);
331         if (s_hHHCTRL_OCX)
332             s_pHtmlHelpW = (FN_HtmlHelpW)GetProcAddress(s_hHHCTRL_OCX, "HtmlHelpW");
333     }
334 
335     if (!s_pHtmlHelpW)
336         return NULL;
337 
338     return s_pHtmlHelpW(hwndCaller, pszFile, uCommand, dwData);
339 }
340 
341 void CMainWindow::alignChildrenToMainWindow()
342 {
343     RECT clientRect, rc;
344     GetClientRect(&clientRect);
345     RECT rcSpace = clientRect;
346     const UINT uFlags = (SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION | SWP_NOCOPYBITS);
347 
348     if (::IsWindowVisible(g_hStatusBar))
349     {
350         ::GetWindowRect(g_hStatusBar, &rc);
351         rcSpace.bottom -= rc.bottom - rc.top;
352     }
353 
354     HDWP hDWP = ::BeginDeferWindowPos(3);
355 
356     if (::IsWindowVisible(toolBoxContainer))
357     {
358         if (registrySettings.Bar2ID == BAR2ID_RIGHT)
359         {
360             hDWP = ::DeferWindowPos(hDWP, toolBoxContainer, NULL,
361                                     rcSpace.right - CX_TOOLBAR, rcSpace.top,
362                                     CX_TOOLBAR, rcSpace.bottom - rcSpace.top,
363                                     uFlags);
364             rcSpace.right -= CX_TOOLBAR;
365         }
366         else
367         {
368             hDWP = ::DeferWindowPos(hDWP, toolBoxContainer, NULL,
369                                     rcSpace.left, rcSpace.top,
370                                     CX_TOOLBAR, rcSpace.bottom - rcSpace.top,
371                                     uFlags);
372             rcSpace.left += CX_TOOLBAR;
373         }
374     }
375 
376     if (::IsWindowVisible(paletteWindow))
377     {
378         if (registrySettings.Bar1ID == BAR1ID_BOTTOM)
379         {
380             hDWP = ::DeferWindowPos(hDWP, paletteWindow, NULL,
381                                     rcSpace.left, rcSpace.bottom - CY_PALETTE,
382                                     rcSpace.right - rcSpace.left, CY_PALETTE,
383                                     uFlags);
384             rcSpace.bottom -= CY_PALETTE;
385         }
386         else
387         {
388             hDWP = ::DeferWindowPos(hDWP, paletteWindow, NULL,
389                                     rcSpace.left, rcSpace.top,
390                                     rcSpace.right - rcSpace.left, CY_PALETTE,
391                                     uFlags);
392             rcSpace.top += CY_PALETTE;
393         }
394     }
395 
396     if (canvasWindow.IsWindow())
397     {
398         hDWP = ::DeferWindowPos(hDWP, canvasWindow, NULL,
399                                 rcSpace.left, rcSpace.top,
400                                 rcSpace.right - rcSpace.left, rcSpace.bottom - rcSpace.top,
401                                 uFlags);
402     }
403 
404     ::EndDeferWindowPos(hDWP);
405 }
406 
407 void CMainWindow::saveImage(BOOL overwrite)
408 {
409     canvasWindow.OnEndDraw(FALSE);
410 
411     // Is the extension not supported?
412     PWCHAR pchDotExt = PathFindExtensionW(g_szFileName);
413     if (pchDotExt && *pchDotExt && !CImageDx::IsExtensionSupported(pchDotExt))
414     {
415         // Remove the extension
416         PathRemoveExtensionW(g_szFileName);
417         // No overwrite
418         overwrite = FALSE;
419     }
420 
421     if (g_isAFile && overwrite)
422     {
423         imageModel.SaveImage(g_szFileName);
424     }
425     else if (GetSaveFileName(g_szFileName, _countof(g_szFileName)))
426     {
427         imageModel.SaveImage(g_szFileName);
428     }
429 }
430 
431 void CMainWindow::InsertSelectionFromHBITMAP(HBITMAP bitmap, HWND window)
432 {
433     int width = GetDIBWidth(bitmap);
434     int height = GetDIBHeight(bitmap);
435     int curWidth = imageModel.GetWidth();
436     int curHeight = imageModel.GetHeight();
437 
438     if (width > curWidth || height > curHeight)
439     {
440         BOOL shouldEnlarge = TRUE;
441 
442         if (g_askBeforeEnlarging)
443         {
444             WCHAR programname[20];
445             WCHAR shouldEnlargePromptText[100];
446 
447             ::LoadStringW(g_hinstExe, IDS_PROGRAMNAME, programname, _countof(programname));
448             ::LoadStringW(g_hinstExe, IDS_ENLARGEPROMPTTEXT, shouldEnlargePromptText, _countof(shouldEnlargePromptText));
449 
450             switch (MessageBox(shouldEnlargePromptText, programname, MB_YESNOCANCEL | MB_ICONQUESTION))
451             {
452                 case IDYES:
453                     break;
454                 case IDNO:
455                     shouldEnlarge = FALSE;
456                     break;
457                 case IDCANCEL:
458                     return;
459             }
460         }
461 
462         if (shouldEnlarge)
463         {
464             if (width > curWidth)
465                 curWidth = width;
466 
467             if (height > curHeight)
468                 curHeight = height;
469 
470             imageModel.Crop(curWidth, curHeight, 0, 0);
471         }
472     }
473 
474     toolsModel.SetActiveTool(TOOL_RECTSEL);
475 
476     selectionModel.InsertFromHBITMAP(bitmap, 0, 0);
477     selectionModel.m_bShow = TRUE;
478     imageModel.NotifyImageChanged();
479 }
480 
481 LRESULT CMainWindow::OnMouseWheel(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
482 {
483     INT zDelta = (SHORT)HIWORD(wParam);
484 
485     if (::GetKeyState(VK_CONTROL) < 0) // Ctrl+Wheel
486     {
487         if (zDelta < 0)
488         {
489             if (toolsModel.GetZoom() > MIN_ZOOM)
490                 canvasWindow.zoomTo(toolsModel.GetZoom() / 2);
491         }
492         else if (zDelta > 0)
493         {
494             if (toolsModel.GetZoom() < MAX_ZOOM)
495                 canvasWindow.zoomTo(toolsModel.GetZoom() * 2);
496         }
497     }
498     else // Wheel only
499     {
500         UINT nCount = 3;
501         if (::GetAsyncKeyState(VK_SHIFT) < 0)
502         {
503 #ifndef SPI_GETWHEELSCROLLCHARS
504     #define SPI_GETWHEELSCROLLCHARS 0x006C  // Needed for pre-NT6 PSDK
505 #endif
506             SystemParametersInfoW(SPI_GETWHEELSCROLLCHARS, 0, &nCount, 0);
507             for (UINT i = 0; i < nCount; ++i)
508             {
509                 if (zDelta < 0)
510                     ::PostMessageW(canvasWindow, WM_HSCROLL, MAKEWPARAM(SB_LINEDOWN, 0), 0);
511                 else if (zDelta > 0)
512                     ::PostMessageW(canvasWindow, WM_HSCROLL, MAKEWPARAM(SB_LINEUP, 0), 0);
513             }
514         }
515         else
516         {
517             SystemParametersInfoW(SPI_GETWHEELSCROLLLINES, 0, &nCount, 0);
518             for (UINT i = 0; i < nCount; ++i)
519             {
520                 if (zDelta < 0)
521                     ::PostMessageW(canvasWindow, WM_VSCROLL, MAKEWPARAM(SB_LINEDOWN, 0), 0);
522                 else if (zDelta > 0)
523                     ::PostMessageW(canvasWindow, WM_VSCROLL, MAKEWPARAM(SB_LINEUP, 0), 0);
524             }
525         }
526     }
527 
528     return 0;
529 }
530 
531 LRESULT CMainWindow::OnDropFiles(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
532 {
533     WCHAR droppedfile[MAX_PATH];
534 
535     HDROP hDrop = (HDROP)wParam;
536     DragQueryFile(hDrop, 0, droppedfile, _countof(droppedfile));
537     DragFinish(hDrop);
538 
539     ConfirmSave() && DoLoadImageFile(m_hWnd, droppedfile, TRUE);
540 
541     return 0;
542 }
543 
544 LRESULT CMainWindow::OnCreate(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
545 {
546     // Loading and setting the window menu from resource
547     m_hMenu = ::LoadMenuW(g_hinstExe, MAKEINTRESOURCEW(ID_MENU));
548     SetMenu(m_hMenu);
549 
550     // Create the status bar
551     DWORD style = SBARS_SIZEGRIP | WS_CHILD | (registrySettings.ShowStatusBar ? WS_VISIBLE : 0);
552     g_hStatusBar = ::CreateWindowExW(0, STATUSCLASSNAME, NULL, style, 0, 0, 0, 0, m_hWnd,
553                                      NULL, g_hinstExe, NULL);
554     ::SendMessageW(g_hStatusBar, SB_SETMINHEIGHT, 21, 0);
555 
556     // Create the tool box
557     toolBoxContainer.DoCreate(m_hWnd);
558 
559     // Create the palette window
560     RECT rcEmpty = { 0, 0, 0, 0 }; // Rely on WM_SIZE
561     style = WS_CHILD | (registrySettings.ShowPalette ? WS_VISIBLE : 0);
562     paletteWindow.Create(m_hWnd, rcEmpty, NULL, style, WS_EX_STATICEDGE);
563 
564     // Create the canvas
565     style = WS_CHILD | WS_GROUP | WS_HSCROLL | WS_VSCROLL | WS_VISIBLE;
566     canvasWindow.Create(m_hWnd, rcEmpty, NULL, style, WS_EX_CLIENTEDGE);
567 
568     // Create and show the miniature if necessary
569     if (registrySettings.ShowThumbnail)
570     {
571         miniature.DoCreate(m_hWnd);
572         miniature.ShowWindow(SW_SHOWNOACTIVATE);
573     }
574 
575     // Set icon
576     SendMessage(WM_SETICON, ICON_BIG, (LPARAM)::LoadIconW(g_hinstExe, MAKEINTRESOURCEW(IDI_APPICON)));
577     SendMessage(WM_SETICON, ICON_SMALL, (LPARAM)::LoadIconW(g_hinstExe, MAKEINTRESOURCEW(IDI_APPICON)));
578 
579     return 0;
580 }
581 
582 LRESULT CMainWindow::OnDestroy(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
583 {
584     registrySettings.WindowPlacement.length = sizeof(WINDOWPLACEMENT);
585     GetWindowPlacement(&(registrySettings.WindowPlacement));
586 
587     DoHtmlHelpW(NULL, NULL, HH_CLOSE_ALL, 0);
588 
589     if (s_hHHCTRL_OCX)
590     {
591         FreeLibrary(s_hHHCTRL_OCX);
592         s_hHHCTRL_OCX = NULL;
593         s_pHtmlHelpW = NULL;
594     }
595 
596     SetMenu(NULL);
597     if (m_hMenu)
598     {
599         ::DestroyMenu(m_hMenu);
600         m_hMenu = NULL;
601     }
602 
603     PostQuitMessage(0); /* send a WM_QUIT to the message queue */
604     return 0;
605 }
606 
607 BOOL CMainWindow::ConfirmSave()
608 {
609     canvasWindow.OnEndDraw(FALSE);
610 
611     if (imageModel.IsImageSaved())
612         return TRUE;
613 
614     CStringW strProgramName;
615     strProgramName.LoadString(IDS_PROGRAMNAME);
616 
617     CStringW strSavePromptText;
618     strSavePromptText.Format(IDS_SAVEPROMPTTEXT, PathFindFileName(g_szFileName));
619 
620     switch (MessageBox(strSavePromptText, strProgramName, MB_YESNOCANCEL | MB_ICONQUESTION))
621     {
622         case IDYES:
623             saveImage(TRUE);
624             return imageModel.IsImageSaved();
625         case IDNO:
626             return TRUE;
627         case IDCANCEL:
628             return FALSE;
629     }
630 
631     return TRUE;
632 }
633 
634 LRESULT CMainWindow::OnClose(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
635 {
636     if (ConfirmSave())
637     {
638         DestroyWindow();
639     }
640     return 0;
641 }
642 
643 void CMainWindow::ProcessFileMenu(HMENU hPopupMenu)
644 {
645     LPCWSTR dotext = PathFindExtensionW(g_szFileName);
646     BOOL isBMP = FALSE;
647     if (_wcsicmp(dotext, L".bmp") == 0 ||
648         _wcsicmp(dotext, L".dib") == 0 ||
649         _wcsicmp(dotext, L".rle") == 0)
650     {
651         isBMP = TRUE;
652     }
653 
654     UINT uWallpaperEnabled = ENABLED_IF(g_isAFile && isBMP && g_fileSize > 0);
655     ::EnableMenuItem(hPopupMenu, IDM_FILEASWALLPAPERPLANE,     uWallpaperEnabled);
656     ::EnableMenuItem(hPopupMenu, IDM_FILEASWALLPAPERCENTERED,  uWallpaperEnabled);
657     ::EnableMenuItem(hPopupMenu, IDM_FILEASWALLPAPERSTRETCHED, uWallpaperEnabled);
658 
659     for (INT iItem = 0; iItem < MAX_RECENT_FILES; ++iItem)
660         RemoveMenu(hPopupMenu, IDM_FILE1 + iItem, MF_BYCOMMAND);
661 
662     if (registrySettings.strFiles[0].IsEmpty())
663         return;
664 
665     RemoveMenu(hPopupMenu, IDM_FILEMOSTRECENTLYUSEDFILE, MF_BYCOMMAND);
666 
667     INT cMenuItems = GetMenuItemCount(hPopupMenu);
668 
669     for (INT iItem = 0; iItem < MAX_RECENT_FILES; ++iItem)
670     {
671         CStringW& strFile = registrySettings.strFiles[iItem];
672         if (strFile.IsEmpty())
673             break;
674 
675         // Condense the lengthy pathname by using '...'
676 #define MAX_RECENT_PATHNAME_DISPLAY 30
677         CPath pathFile(strFile);
678         pathFile.CompactPathEx(MAX_RECENT_PATHNAME_DISPLAY);
679         assert(wcslen((LPCWSTR)pathFile) <= MAX_RECENT_PATHNAME_DISPLAY);
680 
681         // Add an accelerator (by '&') to the item number for quick access
682         WCHAR szText[4 + MAX_RECENT_PATHNAME_DISPLAY + 1];
683         StringCchPrintfW(szText, _countof(szText), L"&%u %s", iItem + 1, (LPCWSTR)pathFile);
684 
685         INT iMenuItem = (cMenuItems - 2) + iItem;
686         InsertMenu(hPopupMenu, iMenuItem, MF_BYPOSITION | MF_STRING, IDM_FILE1 + iItem, szText);
687     }
688 }
689 
690 BOOL CMainWindow::CanUndo() const
691 {
692     if (toolsModel.GetActiveTool() == TOOL_TEXT && ::IsWindowVisible(textEditWindow))
693         return (BOOL)textEditWindow.SendMessage(EM_CANUNDO);
694     if (selectionModel.m_bShow && toolsModel.IsSelection())
695         return TRUE;
696     return imageModel.CanUndo();
697 }
698 
699 BOOL CMainWindow::CanRedo() const
700 {
701     if (toolsModel.GetActiveTool() == TOOL_TEXT && ::IsWindowVisible(textEditWindow))
702         return FALSE; // There is no "WM_REDO" in EDIT control
703     return imageModel.CanRedo();
704 }
705 
706 BOOL CMainWindow::CanPaste() const
707 {
708     if (toolsModel.GetActiveTool() == TOOL_TEXT && ::IsWindowVisible(textEditWindow))
709         return ::IsClipboardFormatAvailable(CF_UNICODETEXT);
710 
711     return (::IsClipboardFormatAvailable(CF_ENHMETAFILE) ||
712             ::IsClipboardFormatAvailable(CF_DIB) ||
713             ::IsClipboardFormatAvailable(CF_BITMAP));
714 }
715 
716 LRESULT CMainWindow::OnInitMenuPopup(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
717 {
718     HMENU menu = (HMENU)wParam;
719     BOOL trueSelection = (selectionModel.m_bShow && toolsModel.IsSelection());
720     BOOL textShown = (toolsModel.GetActiveTool() == TOOL_TEXT && ::IsWindowVisible(textEditWindow));
721     DWORD dwStart = 0, dwEnd = 0;
722     if (textShown)
723         textEditWindow.SendMessage(EM_GETSEL, (WPARAM)&dwStart, (LPARAM)&dwEnd);
724     BOOL hasTextSel = (dwStart < dwEnd);
725 
726     //
727     // File menu
728     //
729     if (::GetSubMenu(GetMenu(), 0) == menu)
730     {
731         ProcessFileMenu(menu);
732     }
733 
734     //
735     // Edit menu
736     //
737     EnableMenuItem(menu, IDM_EDITUNDO, ENABLED_IF(CanUndo()));
738     EnableMenuItem(menu, IDM_EDITREDO, ENABLED_IF(CanRedo()));
739     EnableMenuItem(menu, IDM_EDITCUT, ENABLED_IF(textShown ? hasTextSel : trueSelection));
740     EnableMenuItem(menu, IDM_EDITCOPY, ENABLED_IF(textShown ? hasTextSel : trueSelection));
741     EnableMenuItem(menu, IDM_EDITDELETESELECTION,
742                    ENABLED_IF(textShown ? hasTextSel : trueSelection));
743     EnableMenuItem(menu, IDM_EDITINVERTSELECTION, ENABLED_IF(trueSelection));
744     EnableMenuItem(menu, IDM_EDITCOPYTO, ENABLED_IF(trueSelection));
745     EnableMenuItem(menu, IDM_EDITPASTE, ENABLED_IF(CanPaste()));
746     EnableMenuItem(menu, IDM_CROPSELECTION, ENABLED_IF(trueSelection));
747 
748     //
749     // View menu
750     //
751     CheckMenuItem(menu, IDM_VIEWTOOLBOX, CHECKED_IF(::IsWindowVisible(toolBoxContainer)));
752     CheckMenuItem(menu, IDM_VIEWCOLORPALETTE, CHECKED_IF(::IsWindowVisible(paletteWindow)));
753     CheckMenuItem(menu, IDM_VIEWSTATUSBAR,    CHECKED_IF(::IsWindowVisible(g_hStatusBar)));
754     CheckMenuItem(menu, IDM_FORMATICONBAR, CHECKED_IF(::IsWindowVisible(fontsDialog)));
755     EnableMenuItem(menu, IDM_FORMATICONBAR, ENABLED_IF(toolsModel.GetActiveTool() == TOOL_TEXT));
756     CheckMenuItem(menu, IDM_VIEWZOOM125, CHECKED_IF(toolsModel.GetZoom() == 125));
757     CheckMenuItem(menu, IDM_VIEWZOOM25,  CHECKED_IF(toolsModel.GetZoom() == 250));
758     CheckMenuItem(menu, IDM_VIEWZOOM50,  CHECKED_IF(toolsModel.GetZoom() == 500));
759     CheckMenuItem(menu, IDM_VIEWZOOM100, CHECKED_IF(toolsModel.GetZoom() == 1000));
760     CheckMenuItem(menu, IDM_VIEWZOOM200, CHECKED_IF(toolsModel.GetZoom() == 2000));
761     CheckMenuItem(menu, IDM_VIEWZOOM400, CHECKED_IF(toolsModel.GetZoom() == 4000));
762     CheckMenuItem(menu, IDM_VIEWZOOM800, CHECKED_IF(toolsModel.GetZoom() == 8000));
763     CheckMenuItem(menu, IDM_VIEWSHOWGRID,      CHECKED_IF(g_showGrid));
764     CheckMenuItem(menu, IDM_VIEWSHOWMINIATURE, CHECKED_IF(registrySettings.ShowThumbnail));
765 
766     //
767     // Image menu
768     //
769     EnableMenuItem(menu, IDM_IMAGECROP, ENABLED_IF(selectionModel.m_bShow));
770     EnableMenuItem(menu, IDM_IMAGEDELETEIMAGE, ENABLED_IF(!selectionModel.m_bShow));
771     CheckMenuItem(menu, IDM_IMAGEDRAWOPAQUE, CHECKED_IF(!toolsModel.IsBackgroundTransparent()));
772 
773     //
774     // Palette menu
775     //
776     CheckMenuItem(menu, IDM_COLORSMODERNPALETTE, CHECKED_IF(paletteModel.SelectedPalette() == PAL_MODERN));
777     CheckMenuItem(menu, IDM_COLORSOLDPALETTE,    CHECKED_IF(paletteModel.SelectedPalette() == PAL_OLDTYPE));
778     return 0;
779 }
780 
781 LRESULT CMainWindow::OnSize(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
782 {
783     int test[] = { LOWORD(lParam) - 260, LOWORD(lParam) - 140, LOWORD(lParam) - 20 };
784     if (::IsWindow(g_hStatusBar))
785     {
786         ::SendMessageW(g_hStatusBar, WM_SIZE, 0, 0);
787         ::SendMessageW(g_hStatusBar, SB_SETPARTS, 3, (LPARAM)&test);
788     }
789     alignChildrenToMainWindow();
790     return 0;
791 }
792 
793 LRESULT CMainWindow::OnGetMinMaxInfo(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
794 {
795     MINMAXINFO *mm = (MINMAXINFO*)lParam;
796     mm->ptMinTrackSize = { 330, 360 };
797     return 0;
798 }
799 
800 LRESULT CMainWindow::OnKeyDown(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
801 {
802     switch (wParam)
803     {
804         case VK_ESCAPE:
805             canvasWindow.PostMessage(nMsg, wParam, lParam);
806             break;
807         case VK_LEFT:
808             selectionModel.moveSelection(-1, 0);
809             break;
810         case VK_RIGHT:
811             selectionModel.moveSelection(+1, 0);
812             break;
813         case VK_UP:
814             selectionModel.moveSelection(0, -1);
815             break;
816         case VK_DOWN:
817             selectionModel.moveSelection(0, +1);
818             break;
819         default:
820             break;
821     }
822     return 0;
823 }
824 
825 LRESULT CMainWindow::OnSysColorChange(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
826 {
827     /* Redirect message to common controls */
828     HWND hToolbar = FindWindowEx(toolBoxContainer.m_hWnd, NULL, TOOLBARCLASSNAME, NULL);
829     ::SendMessageW(hToolbar, WM_SYSCOLORCHANGE, 0, 0);
830     return 0;
831 }
832 
833 LRESULT CMainWindow::OnCommand(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
834 {
835     // Disable commands while dragging mouse
836     if (canvasWindow.m_drawing && ::GetCapture())
837     {
838         ATLTRACE("locking!\n");
839         return 0;
840     }
841 
842     BOOL textShown = (toolsModel.GetActiveTool() == TOOL_TEXT && ::IsWindowVisible(textEditWindow));
843     switch (LOWORD(wParam))
844     {
845         case IDM_HELPINFO:
846         {
847             WCHAR infotitle[100], infotext[200];
848             ::LoadStringW(g_hinstExe, IDS_INFOTITLE, infotitle, _countof(infotitle));
849             ::LoadStringW(g_hinstExe, IDS_INFOTEXT, infotext, _countof(infotext));
850             ::ShellAboutW(m_hWnd, infotitle, infotext,
851                           LoadIconW(g_hinstExe, MAKEINTRESOURCEW(IDI_APPICON)));
852             break;
853         }
854         case IDM_HELPHELPTOPICS:
855             DoHtmlHelpW(m_hWnd, L"%SystemRoot%\\Help\\mspaint.chm", HH_DISPLAY_TOPIC, 0);
856             break;
857         case IDM_FILEEXIT:
858             SendMessage(WM_CLOSE, wParam, lParam);
859             break;
860         case IDM_FILENEW:
861             if (ConfirmSave())
862             {
863                 InitializeImage(NULL, NULL, FALSE);
864             }
865             break;
866         case IDM_FILEOPEN:
867             {
868                 WCHAR szFileName[MAX_LONG_PATH] = L"";
869                 if (ConfirmSave() && GetOpenFileName(szFileName, _countof(szFileName)))
870                 {
871                     DoLoadImageFile(m_hWnd, szFileName, TRUE);
872                 }
873                 break;
874             }
875         case IDM_FILESAVE:
876             saveImage(TRUE);
877             break;
878         case IDM_FILESAVEAS:
879             saveImage(FALSE);
880             break;
881         case IDM_FILEPAGESETUP:
882             // DUMMY: Shows the dialog only, no functionality
883             PAGESETUPDLG psd;
884             ZeroMemory(&psd, sizeof(psd));
885             psd.lStructSize = sizeof(psd);
886             psd.hwndOwner = m_hWnd;
887             PageSetupDlg(&psd);
888             break;
889         case IDM_FILEPRINT:
890             // TODO: Test whether it actually works
891             PRINTDLG pd;
892             ZeroMemory(&pd, sizeof(pd));
893             pd.lStructSize = sizeof(pd);
894             pd.hwndOwner = m_hWnd;
895             pd.hDevMode = NULL;  // freed by user
896             pd.hDevNames = NULL;  // freed by user
897             pd.Flags = PD_USEDEVMODECOPIESANDCOLLATE | PD_RETURNDC;
898             pd.nCopies = 1;
899             pd.nFromPage = 0xffff;
900             pd.nToPage = 0xffff;
901             pd.nMinPage = 1;
902             pd.nMaxPage = 0xffff;
903             if (PrintDlg(&pd) == TRUE)
904             {
905                 ::BitBlt(pd.hDC, 0, 0, imageModel.GetWidth(), imageModel.GetHeight(), imageModel.GetDC(), 0, 0, SRCCOPY);
906                 DeleteDC(pd.hDC);
907             }
908             if (pd.hDevMode)
909                 GlobalFree(pd.hDevMode);
910             if (pd.hDevNames)
911                 GlobalFree(pd.hDevNames);
912             break;
913         case IDM_FILESEND:
914             canvasWindow.OnEndDraw(FALSE);
915             if (!OpenMailer(m_hWnd, g_szFileName))
916             {
917                 ShowError(IDS_CANTSENDMAIL);
918             }
919             break;
920         case IDM_FILEASWALLPAPERPLANE:
921             RegistrySettings::SetWallpaper(g_szFileName, RegistrySettings::TILED);
922             break;
923         case IDM_FILEASWALLPAPERCENTERED:
924             RegistrySettings::SetWallpaper(g_szFileName, RegistrySettings::CENTERED);
925             break;
926         case IDM_FILEASWALLPAPERSTRETCHED:
927             RegistrySettings::SetWallpaper(g_szFileName, RegistrySettings::STRETCHED);
928             break;
929         case IDM_FILE1:
930         case IDM_FILE2:
931         case IDM_FILE3:
932         case IDM_FILE4:
933         {
934             INT iFile = LOWORD(wParam) - IDM_FILE1;
935             if (ConfirmSave())
936                 DoLoadImageFile(m_hWnd, registrySettings.strFiles[iFile], TRUE);
937             break;
938         }
939         case IDM_EDITUNDO:
940             if (textShown)
941             {
942                 textEditWindow.PostMessage(WM_UNDO, 0, 0);
943                 break;
944             }
945             canvasWindow.OnEndDraw(FALSE);
946             imageModel.Undo();
947             break;
948         case IDM_EDITREDO:
949             if (textShown)
950             {
951                 // There is no "WM_REDO" in EDIT control
952                 break;
953             }
954             canvasWindow.OnEndDraw(FALSE);
955             imageModel.Redo();
956             break;
957         case IDM_EDITCOPY:
958             if (textShown)
959             {
960                 textEditWindow.SendMessage(WM_COPY);
961                 break;
962             }
963             if (!selectionModel.m_bShow || !OpenClipboard())
964                 break;
965 
966             EmptyClipboard();
967 
968             selectionModel.TakeOff();
969 
970             {
971                 HBITMAP hbmCopy = selectionModel.GetSelectionContents();
972                 HGLOBAL hGlobal = BitmapToClipboardDIB(hbmCopy);
973                 if (hGlobal)
974                     ::SetClipboardData(CF_DIB, hGlobal);
975                 else
976                     ShowOutOfMemory();
977                 ::DeleteObject(hbmCopy);
978             }
979 
980             CloseClipboard();
981             break;
982         case IDM_EDITCUT:
983             if (textShown)
984             {
985                 textEditWindow.SendMessage(WM_CUT);
986                 break;
987             }
988             /* Copy */
989             SendMessage(WM_COMMAND, IDM_EDITCOPY, 0);
990             /* Delete selection */
991             SendMessage(WM_COMMAND, IDM_EDITDELETESELECTION, 0);
992             break;
993         case IDM_EDITPASTE:
994             if (textShown)
995             {
996                 textEditWindow.SendMessage(WM_PASTE);
997                 break;
998             }
999 
1000             if (!OpenClipboard())
1001                 break;
1002 
1003             // In many cases, CF_ENHMETAFILE provides a better image than CF_DIB
1004             if (::IsClipboardFormatAvailable(CF_ENHMETAFILE))
1005             {
1006                 HENHMETAFILE hEMF = (HENHMETAFILE)::GetClipboardData(CF_ENHMETAFILE);
1007                 if (hEMF)
1008                 {
1009                     HBITMAP hbm = BitmapFromHEMF(hEMF);
1010                     ::DeleteEnhMetaFile(hEMF);
1011                     if (hbm)
1012                     {
1013                         InsertSelectionFromHBITMAP(hbm, m_hWnd);
1014                         CloseClipboard();
1015                         break;
1016                     }
1017                 }
1018             }
1019 
1020             // In many cases, CF_DIB provides a better image than CF_BITMAP
1021             if (::IsClipboardFormatAvailable(CF_DIB))
1022             {
1023                 HBITMAP hbm = BitmapFromClipboardDIB(::GetClipboardData(CF_DIB));
1024                 if (hbm)
1025                 {
1026                     InsertSelectionFromHBITMAP(hbm, m_hWnd);
1027                     CloseClipboard();
1028                     break;
1029                 }
1030             }
1031 
1032             // The last resort
1033             if (::IsClipboardFormatAvailable(CF_BITMAP))
1034             {
1035                 HBITMAP hbm = (HBITMAP)::GetClipboardData(CF_BITMAP);
1036                 if (hbm)
1037                 {
1038                     InsertSelectionFromHBITMAP(hbm, m_hWnd);
1039                     CloseClipboard();
1040                     break;
1041                 }
1042             }
1043 
1044             // Failed to paste
1045             {
1046                 CStringW strText, strTitle;
1047                 strText.LoadString(IDS_CANTPASTE);
1048                 strTitle.LoadString(IDS_PROGRAMNAME);
1049                 MessageBox(strText, strTitle, MB_ICONINFORMATION);
1050             }
1051 
1052             CloseClipboard();
1053             break;
1054         case IDM_CROPSELECTION:
1055         {
1056             HBITMAP hbmSelection = selectionModel.GetSelectionContents();
1057             if (hbmSelection)
1058             {
1059                 imageModel.PushImageForUndo(hbmSelection);
1060                 selectionModel.HideSelection();
1061                 imageModel.NotifyImageChanged();
1062             }
1063             break;
1064         }
1065         case IDM_EDITDELETESELECTION:
1066         {
1067             if (textShown)
1068             {
1069                 textEditWindow.SendMessage(WM_CLEAR);
1070                 break;
1071             }
1072             switch (toolsModel.GetActiveTool())
1073             {
1074                 case TOOL_FREESEL:
1075                 case TOOL_RECTSEL:
1076                     selectionModel.DeleteSelection();
1077                     break;
1078 
1079                 case TOOL_TEXT:
1080                     canvasWindow.OnEndDraw(TRUE);
1081                     break;
1082                 default:
1083                     break;
1084             }
1085             break;
1086         }
1087         case IDM_EDITSELECTALL:
1088         {
1089             if (textShown)
1090             {
1091                 textEditWindow.SendMessage(EM_SETSEL, 0, -1);
1092                 break;
1093             }
1094             HWND hToolbar = FindWindowEx(toolBoxContainer.m_hWnd, NULL, TOOLBARCLASSNAME, NULL);
1095             ::SendMessageW(hToolbar, TB_CHECKBUTTON, ID_RECTSEL, MAKELPARAM(TRUE, 0));
1096             toolsModel.selectAll();
1097             canvasWindow.Invalidate(TRUE);
1098             break;
1099         }
1100         case IDM_EDITCOPYTO:
1101         {
1102             WCHAR szFileName[MAX_LONG_PATH];
1103             ::LoadStringW(g_hinstExe, IDS_DEFAULTFILENAME, szFileName, _countof(szFileName));
1104             if (GetSaveFileName(szFileName, _countof(szFileName)))
1105             {
1106                 HBITMAP hbmSelection = selectionModel.GetSelectionContents();
1107                 if (!hbmSelection)
1108                 {
1109                     ShowOutOfMemory();
1110                     break;
1111                 }
1112                 SaveDIBToFile(hbmSelection, szFileName, FALSE);
1113                 DeleteObject(hbmSelection);
1114             }
1115             break;
1116         }
1117         case IDM_EDITPASTEFROM:
1118         {
1119             WCHAR szFileName[MAX_LONG_PATH] = L"";
1120             if (GetOpenFileName(szFileName, _countof(szFileName)))
1121             {
1122                 HBITMAP hbmNew = DoLoadImageFile(m_hWnd, szFileName, FALSE);
1123                 if (hbmNew)
1124                     InsertSelectionFromHBITMAP(hbmNew, m_hWnd);
1125             }
1126             break;
1127         }
1128         case IDM_COLORSEDITPALETTE:
1129         {
1130             COLORREF rgbColor = paletteModel.GetFgColor();
1131             if (ChooseColor(&rgbColor))
1132                 paletteModel.SetFgColor(rgbColor);
1133             break;
1134         }
1135         case IDM_COLORSMODERNPALETTE:
1136             paletteModel.SelectPalette(PAL_MODERN);
1137             break;
1138         case IDM_COLORSOLDPALETTE:
1139             paletteModel.SelectPalette(PAL_OLDTYPE);
1140             break;
1141         case IDM_IMAGEINVERTCOLORS:
1142         {
1143             if (selectionModel.m_bShow)
1144                 selectionModel.InvertSelection();
1145             else
1146                 imageModel.InvertColors();
1147             break;
1148         }
1149         case IDM_IMAGEDELETEIMAGE:
1150             imageModel.PushImageForUndo();
1151             Rect(imageModel.GetDC(), 0, 0, imageModel.GetWidth(), imageModel.GetHeight(), paletteModel.GetBgColor(), paletteModel.GetBgColor(), 0, TRUE);
1152             imageModel.NotifyImageChanged();
1153             break;
1154         case IDM_IMAGEROTATEMIRROR:
1155             {
1156                 CWaitCursor waitCursor;
1157                 canvasWindow.updateScrollPos();
1158                 switch (mirrorRotateDialog.DoModal(mainWindow.m_hWnd))
1159                 {
1160                     case 1: /* flip horizontally */
1161                     {
1162                         if (selectionModel.m_bShow)
1163                             selectionModel.FlipHorizontally();
1164                         else
1165                             imageModel.FlipHorizontally();
1166                         break;
1167                     }
1168                     case 2: /* flip vertically */
1169                     {
1170                         if (selectionModel.m_bShow)
1171                             selectionModel.FlipVertically();
1172                         else
1173                             imageModel.FlipVertically();
1174                         break;
1175                     }
1176                     case 3: /* rotate 90 degrees */
1177                     {
1178                         if (selectionModel.m_bShow)
1179                             selectionModel.RotateNTimes90Degrees(1);
1180                         else
1181                             imageModel.RotateNTimes90Degrees(1);
1182                         break;
1183                     }
1184                     case 4: /* rotate 180 degrees */
1185                     {
1186                         if (selectionModel.m_bShow)
1187                             selectionModel.RotateNTimes90Degrees(2);
1188                         else
1189                             imageModel.RotateNTimes90Degrees(2);
1190                         break;
1191                     }
1192                     case 5: /* rotate 270 degrees */
1193                     {
1194                         if (selectionModel.m_bShow)
1195                             selectionModel.RotateNTimes90Degrees(3);
1196                         else
1197                             imageModel.RotateNTimes90Degrees(3);
1198                         break;
1199                     }
1200                 }
1201             }
1202             break;
1203         case IDM_IMAGEATTRIBUTES:
1204         {
1205             if (attributesDialog.DoModal(mainWindow.m_hWnd))
1206             {
1207                 CWaitCursor waitCursor;
1208                 if (attributesDialog.m_bBlackAndWhite && !imageModel.IsBlackAndWhite())
1209                 {
1210                     CStringW strText(MAKEINTRESOURCEW(IDS_LOSECOLOR));
1211                     CStringW strTitle(MAKEINTRESOURCEW(IDS_PROGRAMNAME));
1212                     INT id = MessageBox(strText, strTitle, MB_ICONINFORMATION | MB_YESNOCANCEL);
1213                     if (id != IDYES)
1214                         break;
1215 
1216                     imageModel.PushBlackAndWhite();
1217                 }
1218 
1219                 if (imageModel.GetWidth() != attributesDialog.newWidth ||
1220                     imageModel.GetHeight() != attributesDialog.newHeight)
1221                 {
1222                     imageModel.Crop(attributesDialog.newWidth, attributesDialog.newHeight);
1223                 }
1224             }
1225             break;
1226         }
1227         case IDM_IMAGESTRETCHSKEW:
1228         {
1229             if (stretchSkewDialog.DoModal(mainWindow.m_hWnd))
1230             {
1231                 CWaitCursor waitCursor;
1232                 if (selectionModel.m_bShow)
1233                 {
1234                     selectionModel.StretchSkew(stretchSkewDialog.percentage.x, stretchSkewDialog.percentage.y,
1235                                                stretchSkewDialog.angle.x, stretchSkewDialog.angle.y);
1236                 }
1237                 else
1238                 {
1239                     imageModel.StretchSkew(stretchSkewDialog.percentage.x, stretchSkewDialog.percentage.y,
1240                                            stretchSkewDialog.angle.x, stretchSkewDialog.angle.y);
1241                 }
1242             }
1243             break;
1244         }
1245         case IDM_IMAGEDRAWOPAQUE:
1246             toolsModel.SetBackgroundTransparent(!toolsModel.IsBackgroundTransparent());
1247             break;
1248         case IDM_IMAGECROP:
1249         {
1250             HBITMAP hbmCopy = selectionModel.GetSelectionContents();
1251             imageModel.PushImageForUndo(hbmCopy);
1252             selectionModel.HideSelection();
1253             break;
1254         }
1255         case IDM_VIEWTOOLBOX:
1256             registrySettings.ShowToolBox = !toolBoxContainer.IsWindowVisible();
1257             toolBoxContainer.ShowWindow(registrySettings.ShowToolBox ? SW_SHOWNOACTIVATE : SW_HIDE);
1258             alignChildrenToMainWindow();
1259             break;
1260         case IDM_VIEWCOLORPALETTE:
1261             registrySettings.ShowPalette = !paletteWindow.IsWindowVisible();
1262             paletteWindow.ShowWindow(registrySettings.ShowPalette ? SW_SHOWNOACTIVATE : SW_HIDE);
1263             alignChildrenToMainWindow();
1264             break;
1265         case IDM_VIEWSTATUSBAR:
1266             registrySettings.ShowStatusBar = !::IsWindowVisible(g_hStatusBar);
1267             ::ShowWindow(g_hStatusBar, (registrySettings.ShowStatusBar ? SW_SHOWNOACTIVATE : SW_HIDE));
1268             alignChildrenToMainWindow();
1269             break;
1270         case IDM_FORMATICONBAR:
1271             if (toolsModel.GetActiveTool() == TOOL_TEXT)
1272             {
1273                 if (!fontsDialog.IsWindow())
1274                 {
1275                     fontsDialog.Create(mainWindow);
1276                 }
1277                 registrySettings.ShowTextTool = !::IsWindowVisible(fontsDialog);
1278                 fontsDialog.ShowWindow(registrySettings.ShowTextTool ? SW_SHOW : SW_HIDE);
1279                 fontsDialog.SendMessage(DM_REPOSITION, 0, 0);
1280             }
1281             break;
1282         case IDM_VIEWSHOWGRID:
1283             g_showGrid = !g_showGrid;
1284             canvasWindow.Invalidate(FALSE);
1285             break;
1286         case IDM_VIEWSHOWMINIATURE:
1287             registrySettings.ShowThumbnail = !::IsWindowVisible(miniature);
1288             miniature.DoCreate(m_hWnd);
1289             miniature.ShowWindow(registrySettings.ShowThumbnail ? SW_SHOWNOACTIVATE : SW_HIDE);
1290             break;
1291 
1292         case IDM_VIEWZOOM125:
1293             canvasWindow.zoomTo(125);
1294             break;
1295         case IDM_VIEWZOOM25:
1296             canvasWindow.zoomTo(250);
1297             break;
1298         case IDM_VIEWZOOM50:
1299             canvasWindow.zoomTo(500);
1300             break;
1301         case IDM_VIEWZOOM100:
1302             canvasWindow.zoomTo(1000);
1303             break;
1304         case IDM_VIEWZOOM200:
1305             canvasWindow.zoomTo(2000);
1306             break;
1307         case IDM_VIEWZOOM400:
1308             canvasWindow.zoomTo(4000);
1309             break;
1310         case IDM_VIEWZOOM800:
1311             canvasWindow.zoomTo(8000);
1312             break;
1313 
1314         case IDM_VIEWFULLSCREEN:
1315             // Create and show the fullscreen window
1316             fullscreenWindow.DoCreate();
1317             fullscreenWindow.ShowWindow(SW_SHOWMAXIMIZED);
1318             break;
1319 
1320         case IDM_CTRL_PLUS:
1321             toolsModel.SpecialTweak(FALSE);
1322             break;
1323         case IDM_CTRL_MINUS:
1324             toolsModel.SpecialTweak(TRUE);
1325             break;
1326     }
1327     return 0;
1328 }
1329 
1330 VOID CMainWindow::TrackPopupMenu(POINT ptScreen, INT iSubMenu)
1331 {
1332     HMENU hMenu = ::LoadMenuW(g_hinstExe, MAKEINTRESOURCEW(ID_POPUPMENU));
1333     HMENU hSubMenu = ::GetSubMenu(hMenu, iSubMenu);
1334 
1335     ::SetForegroundWindow(m_hWnd);
1336     INT_PTR id = ::TrackPopupMenu(hSubMenu, TPM_RIGHTBUTTON | TPM_RETURNCMD,
1337                                   ptScreen.x, ptScreen.y, 0, m_hWnd, NULL);
1338     PostMessage(WM_NULL);
1339     if (id != 0)
1340         PostMessage(WM_COMMAND, id);
1341 
1342     ::DestroyMenu(hMenu);
1343 }
1344 
1345 // entry point
1346 INT WINAPI
1347 wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, INT nCmdShow)
1348 {
1349     g_hinstExe = hInstance;
1350 
1351     // Initialize common controls library
1352     INITCOMMONCONTROLSEX iccx;
1353     iccx.dwSize = sizeof(iccx);
1354     iccx.dwICC = ICC_STANDARD_CLASSES | ICC_USEREX_CLASSES | ICC_BAR_CLASSES;
1355     InitCommonControlsEx(&iccx);
1356 
1357     // Load settings from registry
1358     registrySettings.Load(nCmdShow);
1359 
1360     // Create the main window
1361     if (!mainWindow.DoCreate())
1362     {
1363         MessageBox(NULL, L"Failed to create main window.", NULL, MB_ICONERROR);
1364         return 1;
1365     }
1366 
1367     // Initialize imageModel
1368     if (__argc < 2 || !DoLoadImageFile(mainWindow, __targv[1], TRUE))
1369         InitializeImage(NULL, NULL, FALSE);
1370 
1371     // Make the window visible on the screen
1372     mainWindow.ShowWindow(registrySettings.WindowPlacement.showCmd);
1373 
1374     // Load the access keys
1375     HACCEL hAccel = ::LoadAcceleratorsW(hInstance, MAKEINTRESOURCEW(800));
1376 
1377     // The message loop
1378     MSG msg;
1379     while (::GetMessage(&msg, NULL, 0, 0))
1380     {
1381         if (fontsDialog.IsWindow() && fontsDialog.IsDialogMessage(&msg))
1382             continue;
1383 
1384         if (::TranslateAcceleratorW(mainWindow, hAccel, &msg))
1385             continue;
1386 
1387         ::TranslateMessage(&msg);
1388         ::DispatchMessage(&msg);
1389     }
1390 
1391     // Unload the access keys
1392     ::DestroyAcceleratorTable(hAccel);
1393 
1394     // Write back settings to registry
1395     registrySettings.Store();
1396 
1397     if (g_szMailTempFile[0])
1398         ::DeleteFileW(g_szMailTempFile);
1399 
1400     // Return the value that PostQuitMessage() gave
1401     return (INT)msg.wParam;
1402 }
1403