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