xref: /reactos/base/applications/mspaint/main.cpp (revision 3b2fdc56)
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 
747     //
748     // View menu
749     //
750     CheckMenuItem(menu, IDM_VIEWTOOLBOX, CHECKED_IF(::IsWindowVisible(toolBoxContainer)));
751     CheckMenuItem(menu, IDM_VIEWCOLORPALETTE, CHECKED_IF(::IsWindowVisible(paletteWindow)));
752     CheckMenuItem(menu, IDM_VIEWSTATUSBAR,    CHECKED_IF(::IsWindowVisible(g_hStatusBar)));
753     CheckMenuItem(menu, IDM_FORMATICONBAR, CHECKED_IF(::IsWindowVisible(fontsDialog)));
754     EnableMenuItem(menu, IDM_FORMATICONBAR, ENABLED_IF(toolsModel.GetActiveTool() == TOOL_TEXT));
755     CheckMenuItem(menu, IDM_VIEWZOOM125, CHECKED_IF(toolsModel.GetZoom() == 125));
756     CheckMenuItem(menu, IDM_VIEWZOOM25,  CHECKED_IF(toolsModel.GetZoom() == 250));
757     CheckMenuItem(menu, IDM_VIEWZOOM50,  CHECKED_IF(toolsModel.GetZoom() == 500));
758     CheckMenuItem(menu, IDM_VIEWZOOM100, CHECKED_IF(toolsModel.GetZoom() == 1000));
759     CheckMenuItem(menu, IDM_VIEWZOOM200, CHECKED_IF(toolsModel.GetZoom() == 2000));
760     CheckMenuItem(menu, IDM_VIEWZOOM400, CHECKED_IF(toolsModel.GetZoom() == 4000));
761     CheckMenuItem(menu, IDM_VIEWZOOM800, CHECKED_IF(toolsModel.GetZoom() == 8000));
762     CheckMenuItem(menu, IDM_VIEWSHOWGRID,      CHECKED_IF(g_showGrid));
763     CheckMenuItem(menu, IDM_VIEWSHOWMINIATURE, CHECKED_IF(registrySettings.ShowThumbnail));
764 
765     //
766     // Image menu
767     //
768     EnableMenuItem(menu, IDM_IMAGECROP, ENABLED_IF(selectionModel.m_bShow));
769     EnableMenuItem(menu, IDM_IMAGEDELETEIMAGE, ENABLED_IF(!selectionModel.m_bShow));
770     CheckMenuItem(menu, IDM_IMAGEDRAWOPAQUE, CHECKED_IF(!toolsModel.IsBackgroundTransparent()));
771 
772     //
773     // Palette menu
774     //
775     CheckMenuItem(menu, IDM_COLORSMODERNPALETTE, CHECKED_IF(paletteModel.SelectedPalette() == PAL_MODERN));
776     CheckMenuItem(menu, IDM_COLORSOLDPALETTE,    CHECKED_IF(paletteModel.SelectedPalette() == PAL_OLDTYPE));
777     return 0;
778 }
779 
780 LRESULT CMainWindow::OnSize(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
781 {
782     int test[] = { LOWORD(lParam) - 260, LOWORD(lParam) - 140, LOWORD(lParam) - 20 };
783     if (::IsWindow(g_hStatusBar))
784     {
785         ::SendMessageW(g_hStatusBar, WM_SIZE, 0, 0);
786         ::SendMessageW(g_hStatusBar, SB_SETPARTS, 3, (LPARAM)&test);
787     }
788     alignChildrenToMainWindow();
789     return 0;
790 }
791 
792 LRESULT CMainWindow::OnGetMinMaxInfo(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
793 {
794     MINMAXINFO *mm = (MINMAXINFO*)lParam;
795     mm->ptMinTrackSize = { 330, 360 };
796     return 0;
797 }
798 
799 LRESULT CMainWindow::OnKeyDown(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
800 {
801     switch (wParam)
802     {
803         case VK_ESCAPE:
804             canvasWindow.PostMessage(nMsg, wParam, lParam);
805             break;
806         case VK_LEFT:
807             selectionModel.moveSelection(-1, 0);
808             break;
809         case VK_RIGHT:
810             selectionModel.moveSelection(+1, 0);
811             break;
812         case VK_UP:
813             selectionModel.moveSelection(0, -1);
814             break;
815         case VK_DOWN:
816             selectionModel.moveSelection(0, +1);
817             break;
818         default:
819             break;
820     }
821     return 0;
822 }
823 
824 LRESULT CMainWindow::OnSysColorChange(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
825 {
826     /* Redirect message to common controls */
827     HWND hToolbar = FindWindowEx(toolBoxContainer.m_hWnd, NULL, TOOLBARCLASSNAME, NULL);
828     ::SendMessageW(hToolbar, WM_SYSCOLORCHANGE, 0, 0);
829     return 0;
830 }
831 
832 LRESULT CMainWindow::OnCommand(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
833 {
834     // Disable commands while dragging mouse
835     if (canvasWindow.m_drawing && ::GetCapture())
836     {
837         ATLTRACE("locking!\n");
838         return 0;
839     }
840 
841     BOOL textShown = (toolsModel.GetActiveTool() == TOOL_TEXT && ::IsWindowVisible(textEditWindow));
842     switch (LOWORD(wParam))
843     {
844         case IDM_HELPINFO:
845         {
846             WCHAR infotitle[100], infotext[200];
847             ::LoadStringW(g_hinstExe, IDS_INFOTITLE, infotitle, _countof(infotitle));
848             ::LoadStringW(g_hinstExe, IDS_INFOTEXT, infotext, _countof(infotext));
849             ::ShellAboutW(m_hWnd, infotitle, infotext,
850                           LoadIconW(g_hinstExe, MAKEINTRESOURCEW(IDI_APPICON)));
851             break;
852         }
853         case IDM_HELPHELPTOPICS:
854             DoHtmlHelpW(m_hWnd, L"%SystemRoot%\\Help\\mspaint.chm", HH_DISPLAY_TOPIC, 0);
855             break;
856         case IDM_FILEEXIT:
857             SendMessage(WM_CLOSE, wParam, lParam);
858             break;
859         case IDM_FILENEW:
860             if (ConfirmSave())
861             {
862                 InitializeImage(NULL, NULL, FALSE);
863             }
864             break;
865         case IDM_FILEOPEN:
866             {
867                 WCHAR szFileName[MAX_LONG_PATH] = L"";
868                 if (ConfirmSave() && GetOpenFileName(szFileName, _countof(szFileName)))
869                 {
870                     DoLoadImageFile(m_hWnd, szFileName, TRUE);
871                 }
872                 break;
873             }
874         case IDM_FILESAVE:
875             saveImage(TRUE);
876             break;
877         case IDM_FILESAVEAS:
878             saveImage(FALSE);
879             break;
880         case IDM_FILEPAGESETUP:
881             // DUMMY: Shows the dialog only, no functionality
882             PAGESETUPDLG psd;
883             ZeroMemory(&psd, sizeof(psd));
884             psd.lStructSize = sizeof(psd);
885             psd.hwndOwner = m_hWnd;
886             PageSetupDlg(&psd);
887             break;
888         case IDM_FILEPRINT:
889             // TODO: Test whether it actually works
890             PRINTDLG pd;
891             ZeroMemory(&pd, sizeof(pd));
892             pd.lStructSize = sizeof(pd);
893             pd.hwndOwner = m_hWnd;
894             pd.hDevMode = NULL;  // freed by user
895             pd.hDevNames = NULL;  // freed by user
896             pd.Flags = PD_USEDEVMODECOPIESANDCOLLATE | PD_RETURNDC;
897             pd.nCopies = 1;
898             pd.nFromPage = 0xffff;
899             pd.nToPage = 0xffff;
900             pd.nMinPage = 1;
901             pd.nMaxPage = 0xffff;
902             if (PrintDlg(&pd) == TRUE)
903             {
904                 ::BitBlt(pd.hDC, 0, 0, imageModel.GetWidth(), imageModel.GetHeight(), imageModel.GetDC(), 0, 0, SRCCOPY);
905                 DeleteDC(pd.hDC);
906             }
907             if (pd.hDevMode)
908                 GlobalFree(pd.hDevMode);
909             if (pd.hDevNames)
910                 GlobalFree(pd.hDevNames);
911             break;
912         case IDM_FILESEND:
913             canvasWindow.OnEndDraw(FALSE);
914             if (!OpenMailer(m_hWnd, g_szFileName))
915             {
916                 ShowError(IDS_CANTSENDMAIL);
917             }
918             break;
919         case IDM_FILEASWALLPAPERPLANE:
920             RegistrySettings::SetWallpaper(g_szFileName, RegistrySettings::TILED);
921             break;
922         case IDM_FILEASWALLPAPERCENTERED:
923             RegistrySettings::SetWallpaper(g_szFileName, RegistrySettings::CENTERED);
924             break;
925         case IDM_FILEASWALLPAPERSTRETCHED:
926             RegistrySettings::SetWallpaper(g_szFileName, RegistrySettings::STRETCHED);
927             break;
928         case IDM_FILE1:
929         case IDM_FILE2:
930         case IDM_FILE3:
931         case IDM_FILE4:
932         {
933             INT iFile = LOWORD(wParam) - IDM_FILE1;
934             if (ConfirmSave())
935                 DoLoadImageFile(m_hWnd, registrySettings.strFiles[iFile], TRUE);
936             break;
937         }
938         case IDM_EDITUNDO:
939             if (textShown)
940             {
941                 textEditWindow.PostMessage(WM_UNDO, 0, 0);
942                 break;
943             }
944             canvasWindow.OnEndDraw(FALSE);
945             imageModel.Undo();
946             break;
947         case IDM_EDITREDO:
948             if (textShown)
949             {
950                 // There is no "WM_REDO" in EDIT control
951                 break;
952             }
953             canvasWindow.OnEndDraw(FALSE);
954             imageModel.Redo();
955             break;
956         case IDM_EDITCOPY:
957             if (textShown)
958             {
959                 textEditWindow.SendMessage(WM_COPY);
960                 break;
961             }
962             if (!selectionModel.m_bShow || !OpenClipboard())
963                 break;
964 
965             EmptyClipboard();
966 
967             selectionModel.TakeOff();
968 
969             {
970                 HBITMAP hbmCopy = selectionModel.GetSelectionContents();
971                 HGLOBAL hGlobal = BitmapToClipboardDIB(hbmCopy);
972                 if (hGlobal)
973                     ::SetClipboardData(CF_DIB, hGlobal);
974                 else
975                     ShowOutOfMemory();
976                 ::DeleteObject(hbmCopy);
977             }
978 
979             CloseClipboard();
980             break;
981         case IDM_EDITCUT:
982             if (textShown)
983             {
984                 textEditWindow.SendMessage(WM_CUT);
985                 break;
986             }
987             /* Copy */
988             SendMessage(WM_COMMAND, IDM_EDITCOPY, 0);
989             /* Delete selection */
990             SendMessage(WM_COMMAND, IDM_EDITDELETESELECTION, 0);
991             break;
992         case IDM_EDITPASTE:
993             if (textShown)
994             {
995                 textEditWindow.SendMessage(WM_PASTE);
996                 break;
997             }
998 
999             if (!OpenClipboard())
1000                 break;
1001 
1002             // In many cases, CF_ENHMETAFILE provides a better image than CF_DIB
1003             if (::IsClipboardFormatAvailable(CF_ENHMETAFILE))
1004             {
1005                 HENHMETAFILE hEMF = (HENHMETAFILE)::GetClipboardData(CF_ENHMETAFILE);
1006                 if (hEMF)
1007                 {
1008                     HBITMAP hbm = BitmapFromHEMF(hEMF);
1009                     ::DeleteEnhMetaFile(hEMF);
1010                     if (hbm)
1011                     {
1012                         InsertSelectionFromHBITMAP(hbm, m_hWnd);
1013                         CloseClipboard();
1014                         break;
1015                     }
1016                 }
1017             }
1018 
1019             // In many cases, CF_DIB provides a better image than CF_BITMAP
1020             if (::IsClipboardFormatAvailable(CF_DIB))
1021             {
1022                 HBITMAP hbm = BitmapFromClipboardDIB(::GetClipboardData(CF_DIB));
1023                 if (hbm)
1024                 {
1025                     InsertSelectionFromHBITMAP(hbm, m_hWnd);
1026                     CloseClipboard();
1027                     break;
1028                 }
1029             }
1030 
1031             // The last resort
1032             if (::IsClipboardFormatAvailable(CF_BITMAP))
1033             {
1034                 HBITMAP hbm = (HBITMAP)::GetClipboardData(CF_BITMAP);
1035                 if (hbm)
1036                 {
1037                     InsertSelectionFromHBITMAP(hbm, m_hWnd);
1038                     CloseClipboard();
1039                     break;
1040                 }
1041             }
1042 
1043             // Failed to paste
1044             {
1045                 CStringW strText, strTitle;
1046                 strText.LoadString(IDS_CANTPASTE);
1047                 strTitle.LoadString(IDS_PROGRAMNAME);
1048                 MessageBox(strText, strTitle, MB_ICONINFORMATION);
1049             }
1050 
1051             CloseClipboard();
1052             break;
1053         case IDM_EDITDELETESELECTION:
1054         {
1055             if (textShown)
1056             {
1057                 textEditWindow.SendMessage(WM_CLEAR);
1058                 break;
1059             }
1060             switch (toolsModel.GetActiveTool())
1061             {
1062                 case TOOL_FREESEL:
1063                 case TOOL_RECTSEL:
1064                     selectionModel.DeleteSelection();
1065                     break;
1066 
1067                 case TOOL_TEXT:
1068                     canvasWindow.OnEndDraw(TRUE);
1069                     break;
1070                 default:
1071                     break;
1072             }
1073             break;
1074         }
1075         case IDM_EDITSELECTALL:
1076         {
1077             if (textShown)
1078             {
1079                 textEditWindow.SendMessage(EM_SETSEL, 0, -1);
1080                 break;
1081             }
1082             HWND hToolbar = FindWindowEx(toolBoxContainer.m_hWnd, NULL, TOOLBARCLASSNAME, NULL);
1083             ::SendMessageW(hToolbar, TB_CHECKBUTTON, ID_RECTSEL, MAKELPARAM(TRUE, 0));
1084             toolsModel.selectAll();
1085             canvasWindow.Invalidate(TRUE);
1086             break;
1087         }
1088         case IDM_EDITCOPYTO:
1089         {
1090             WCHAR szFileName[MAX_LONG_PATH];
1091             ::LoadStringW(g_hinstExe, IDS_DEFAULTFILENAME, szFileName, _countof(szFileName));
1092             if (GetSaveFileName(szFileName, _countof(szFileName)))
1093             {
1094                 HBITMAP hbmSelection = selectionModel.GetSelectionContents();
1095                 if (!hbmSelection)
1096                 {
1097                     ShowOutOfMemory();
1098                     break;
1099                 }
1100                 SaveDIBToFile(hbmSelection, szFileName, FALSE);
1101                 DeleteObject(hbmSelection);
1102             }
1103             break;
1104         }
1105         case IDM_EDITPASTEFROM:
1106         {
1107             WCHAR szFileName[MAX_LONG_PATH] = L"";
1108             if (GetOpenFileName(szFileName, _countof(szFileName)))
1109             {
1110                 HBITMAP hbmNew = DoLoadImageFile(m_hWnd, szFileName, FALSE);
1111                 if (hbmNew)
1112                     InsertSelectionFromHBITMAP(hbmNew, m_hWnd);
1113             }
1114             break;
1115         }
1116         case IDM_COLORSEDITPALETTE:
1117         {
1118             COLORREF rgbColor = paletteModel.GetFgColor();
1119             if (ChooseColor(&rgbColor))
1120                 paletteModel.SetFgColor(rgbColor);
1121             break;
1122         }
1123         case IDM_COLORSMODERNPALETTE:
1124             paletteModel.SelectPalette(PAL_MODERN);
1125             break;
1126         case IDM_COLORSOLDPALETTE:
1127             paletteModel.SelectPalette(PAL_OLDTYPE);
1128             break;
1129         case IDM_IMAGEINVERTCOLORS:
1130         {
1131             if (selectionModel.m_bShow)
1132                 selectionModel.InvertSelection();
1133             else
1134                 imageModel.InvertColors();
1135             break;
1136         }
1137         case IDM_IMAGEDELETEIMAGE:
1138             imageModel.PushImageForUndo();
1139             Rect(imageModel.GetDC(), 0, 0, imageModel.GetWidth(), imageModel.GetHeight(), paletteModel.GetBgColor(), paletteModel.GetBgColor(), 0, TRUE);
1140             imageModel.NotifyImageChanged();
1141             break;
1142         case IDM_IMAGEROTATEMIRROR:
1143             {
1144                 CWaitCursor waitCursor;
1145                 canvasWindow.updateScrollPos();
1146                 switch (mirrorRotateDialog.DoModal(mainWindow.m_hWnd))
1147                 {
1148                     case 1: /* flip horizontally */
1149                     {
1150                         if (selectionModel.m_bShow)
1151                             selectionModel.FlipHorizontally();
1152                         else
1153                             imageModel.FlipHorizontally();
1154                         break;
1155                     }
1156                     case 2: /* flip vertically */
1157                     {
1158                         if (selectionModel.m_bShow)
1159                             selectionModel.FlipVertically();
1160                         else
1161                             imageModel.FlipVertically();
1162                         break;
1163                     }
1164                     case 3: /* rotate 90 degrees */
1165                     {
1166                         if (selectionModel.m_bShow)
1167                             selectionModel.RotateNTimes90Degrees(1);
1168                         else
1169                             imageModel.RotateNTimes90Degrees(1);
1170                         break;
1171                     }
1172                     case 4: /* rotate 180 degrees */
1173                     {
1174                         if (selectionModel.m_bShow)
1175                             selectionModel.RotateNTimes90Degrees(2);
1176                         else
1177                             imageModel.RotateNTimes90Degrees(2);
1178                         break;
1179                     }
1180                     case 5: /* rotate 270 degrees */
1181                     {
1182                         if (selectionModel.m_bShow)
1183                             selectionModel.RotateNTimes90Degrees(3);
1184                         else
1185                             imageModel.RotateNTimes90Degrees(3);
1186                         break;
1187                     }
1188                 }
1189             }
1190             break;
1191         case IDM_IMAGEATTRIBUTES:
1192         {
1193             if (attributesDialog.DoModal(mainWindow.m_hWnd))
1194             {
1195                 CWaitCursor waitCursor;
1196                 if (attributesDialog.m_bBlackAndWhite && !imageModel.IsBlackAndWhite())
1197                 {
1198                     CStringW strText(MAKEINTRESOURCEW(IDS_LOSECOLOR));
1199                     CStringW strTitle(MAKEINTRESOURCEW(IDS_PROGRAMNAME));
1200                     INT id = MessageBox(strText, strTitle, MB_ICONINFORMATION | MB_YESNOCANCEL);
1201                     if (id != IDYES)
1202                         break;
1203 
1204                     imageModel.PushBlackAndWhite();
1205                 }
1206 
1207                 if (imageModel.GetWidth() != attributesDialog.newWidth ||
1208                     imageModel.GetHeight() != attributesDialog.newHeight)
1209                 {
1210                     imageModel.Crop(attributesDialog.newWidth, attributesDialog.newHeight);
1211                 }
1212             }
1213             break;
1214         }
1215         case IDM_IMAGESTRETCHSKEW:
1216         {
1217             if (stretchSkewDialog.DoModal(mainWindow.m_hWnd))
1218             {
1219                 CWaitCursor waitCursor;
1220                 if (selectionModel.m_bShow)
1221                 {
1222                     selectionModel.StretchSkew(stretchSkewDialog.percentage.x, stretchSkewDialog.percentage.y,
1223                                                stretchSkewDialog.angle.x, stretchSkewDialog.angle.y);
1224                 }
1225                 else
1226                 {
1227                     imageModel.StretchSkew(stretchSkewDialog.percentage.x, stretchSkewDialog.percentage.y,
1228                                            stretchSkewDialog.angle.x, stretchSkewDialog.angle.y);
1229                 }
1230             }
1231             break;
1232         }
1233         case IDM_IMAGEDRAWOPAQUE:
1234             toolsModel.SetBackgroundTransparent(!toolsModel.IsBackgroundTransparent());
1235             break;
1236         case IDM_IMAGECROP:
1237         {
1238             HBITMAP hbmCopy = selectionModel.GetSelectionContents();
1239             imageModel.PushImageForUndo(hbmCopy);
1240             selectionModel.HideSelection();
1241             break;
1242         }
1243         case IDM_VIEWTOOLBOX:
1244             registrySettings.ShowToolBox = !toolBoxContainer.IsWindowVisible();
1245             toolBoxContainer.ShowWindow(registrySettings.ShowToolBox ? SW_SHOWNOACTIVATE : SW_HIDE);
1246             alignChildrenToMainWindow();
1247             break;
1248         case IDM_VIEWCOLORPALETTE:
1249             registrySettings.ShowPalette = !paletteWindow.IsWindowVisible();
1250             paletteWindow.ShowWindow(registrySettings.ShowPalette ? SW_SHOWNOACTIVATE : SW_HIDE);
1251             alignChildrenToMainWindow();
1252             break;
1253         case IDM_VIEWSTATUSBAR:
1254             registrySettings.ShowStatusBar = !::IsWindowVisible(g_hStatusBar);
1255             ::ShowWindow(g_hStatusBar, (registrySettings.ShowStatusBar ? SW_SHOWNOACTIVATE : SW_HIDE));
1256             alignChildrenToMainWindow();
1257             break;
1258         case IDM_FORMATICONBAR:
1259             if (toolsModel.GetActiveTool() == TOOL_TEXT)
1260             {
1261                 if (!fontsDialog.IsWindow())
1262                 {
1263                     fontsDialog.Create(mainWindow);
1264                 }
1265                 registrySettings.ShowTextTool = !::IsWindowVisible(fontsDialog);
1266                 fontsDialog.ShowWindow(registrySettings.ShowTextTool ? SW_SHOW : SW_HIDE);
1267                 fontsDialog.SendMessage(DM_REPOSITION, 0, 0);
1268             }
1269             break;
1270         case IDM_VIEWSHOWGRID:
1271             g_showGrid = !g_showGrid;
1272             canvasWindow.Invalidate(FALSE);
1273             break;
1274         case IDM_VIEWSHOWMINIATURE:
1275             registrySettings.ShowThumbnail = !::IsWindowVisible(miniature);
1276             miniature.DoCreate(m_hWnd);
1277             miniature.ShowWindow(registrySettings.ShowThumbnail ? SW_SHOWNOACTIVATE : SW_HIDE);
1278             break;
1279 
1280         case IDM_VIEWZOOM125:
1281             canvasWindow.zoomTo(125);
1282             break;
1283         case IDM_VIEWZOOM25:
1284             canvasWindow.zoomTo(250);
1285             break;
1286         case IDM_VIEWZOOM50:
1287             canvasWindow.zoomTo(500);
1288             break;
1289         case IDM_VIEWZOOM100:
1290             canvasWindow.zoomTo(1000);
1291             break;
1292         case IDM_VIEWZOOM200:
1293             canvasWindow.zoomTo(2000);
1294             break;
1295         case IDM_VIEWZOOM400:
1296             canvasWindow.zoomTo(4000);
1297             break;
1298         case IDM_VIEWZOOM800:
1299             canvasWindow.zoomTo(8000);
1300             break;
1301 
1302         case IDM_VIEWFULLSCREEN:
1303             // Create and show the fullscreen window
1304             fullscreenWindow.DoCreate();
1305             fullscreenWindow.ShowWindow(SW_SHOWMAXIMIZED);
1306             break;
1307 
1308         case IDM_CTRL_PLUS:
1309             toolsModel.SpecialTweak(FALSE);
1310             break;
1311         case IDM_CTRL_MINUS:
1312             toolsModel.SpecialTweak(TRUE);
1313             break;
1314     }
1315     return 0;
1316 }
1317 
1318 VOID CMainWindow::TrackPopupMenu(POINT ptScreen, INT iSubMenu)
1319 {
1320     HMENU hMenu = ::LoadMenuW(g_hinstExe, MAKEINTRESOURCEW(ID_POPUPMENU));
1321     HMENU hSubMenu = ::GetSubMenu(hMenu, iSubMenu);
1322 
1323     ::SetForegroundWindow(m_hWnd);
1324     INT_PTR id = ::TrackPopupMenu(hSubMenu, TPM_RIGHTBUTTON | TPM_RETURNCMD,
1325                                   ptScreen.x, ptScreen.y, 0, m_hWnd, NULL);
1326     PostMessage(WM_NULL);
1327     if (id != 0)
1328         PostMessage(WM_COMMAND, id);
1329 
1330     ::DestroyMenu(hMenu);
1331 }
1332 
1333 // entry point
1334 INT WINAPI
1335 wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, INT nCmdShow)
1336 {
1337     g_hinstExe = hInstance;
1338 
1339     // Initialize common controls library
1340     INITCOMMONCONTROLSEX iccx;
1341     iccx.dwSize = sizeof(iccx);
1342     iccx.dwICC = ICC_STANDARD_CLASSES | ICC_USEREX_CLASSES | ICC_BAR_CLASSES;
1343     InitCommonControlsEx(&iccx);
1344 
1345     // Load settings from registry
1346     registrySettings.Load(nCmdShow);
1347 
1348     // Create the main window
1349     if (!mainWindow.DoCreate())
1350     {
1351         MessageBox(NULL, L"Failed to create main window.", NULL, MB_ICONERROR);
1352         return 1;
1353     }
1354 
1355     // Initialize imageModel
1356     if (__argc < 2 || !DoLoadImageFile(mainWindow, __targv[1], TRUE))
1357         InitializeImage(NULL, NULL, FALSE);
1358 
1359     // Make the window visible on the screen
1360     mainWindow.ShowWindow(registrySettings.WindowPlacement.showCmd);
1361 
1362     // Load the access keys
1363     HACCEL hAccel = ::LoadAcceleratorsW(hInstance, MAKEINTRESOURCEW(800));
1364 
1365     // The message loop
1366     MSG msg;
1367     while (::GetMessage(&msg, NULL, 0, 0))
1368     {
1369         if (fontsDialog.IsWindow() && fontsDialog.IsDialogMessage(&msg))
1370             continue;
1371 
1372         if (::TranslateAcceleratorW(mainWindow, hAccel, &msg))
1373             continue;
1374 
1375         ::TranslateMessage(&msg);
1376         ::DispatchMessage(&msg);
1377     }
1378 
1379     // Unload the access keys
1380     ::DestroyAcceleratorTable(hAccel);
1381 
1382     // Write back settings to registry
1383     registrySettings.Store();
1384 
1385     if (g_szMailTempFile[0])
1386         ::DeleteFileW(g_szMailTempFile);
1387 
1388     // Return the value that PostQuitMessage() gave
1389     return (INT)msg.wParam;
1390 }
1391