Merge branch 'scintilla-551'
[TortoiseGit.git] / src / TortoiseIDiff / MainWindow.cpp
blob6c00a0dcb584eb12f82172a522aff795907836ee
1 // TortoiseGitIDiff - an image diff viewer in TortoiseSVN
3 // Copyright (C) 2015-2019, 2023 - TortoiseGit
4 // Copyright (C) 2006-2015, 2018, 2020-2021, 2023 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include <CommCtrl.h>
22 #include <Commdlg.h>
23 #include "TortoiseIDiff.h"
24 #include "MainWindow.h"
25 #include "AboutDlg.h"
26 #include "TaskbarUUID.h"
27 #include "PathUtils.h"
28 #include "DPIAware.h"
29 #include "LoadIconEx.h"
30 #include "registry.h"
31 #include "Theme.h"
32 #include "DarkModeHelper.h"
34 #pragma comment(lib, "comctl32.lib")
36 std::wstring CMainWindow::leftpicpath;
37 std::wstring CMainWindow::leftpictitle;
39 std::wstring CMainWindow::rightpicpath;
40 std::wstring CMainWindow::rightpictitle;
42 const UINT TaskBarButtonCreated = RegisterWindowMessage(L"TaskbarButtonCreated");
44 bool CMainWindow::RegisterAndCreateWindow()
46 WNDCLASSEX wcx;
48 // Fill in the window class structure with default parameters
49 wcx.cbSize = sizeof(WNDCLASSEX);
50 wcx.style = CS_HREDRAW | CS_VREDRAW;
51 wcx.lpfnWndProc = CWindow::stWinMsgHandler;
52 wcx.cbClsExtra = 0;
53 wcx.cbWndExtra = 0;
54 wcx.hInstance = hResource;
55 wcx.hCursor = LoadCursor(nullptr, IDC_SIZEWE);
56 ResString clsname(hResource, IDS_APP_TITLE);
57 wcx.lpszClassName = clsname;
58 wcx.hIcon = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_TORTOISEIDIFF), GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON));
59 wcx.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_3DFACE + 1);
60 if (selectionPaths.empty())
61 wcx.lpszMenuName = MAKEINTRESOURCE(IDC_TORTOISEIDIFF);
62 else
63 wcx.lpszMenuName = MAKEINTRESOURCE(IDC_TORTOISEIDIFF2);
64 wcx.hIconSm = LoadIconEx(wcx.hInstance, MAKEINTRESOURCE(IDI_TORTOISEIDIFF));
65 if (RegisterWindow(&wcx))
67 if (Create(WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_VISIBLE, nullptr))
69 UpdateWindow(m_hwnd);
70 return true;
73 return false;
76 void CMainWindow::PositionChildren(RECT * clientrect /* = nullptr */)
78 RECT tbRect;
79 if (!clientrect)
80 return;
81 const auto splitter_border = CDPIAware::Instance().ScaleX(*this, SPLITTER_BORDER);
82 SendMessage(hwndTB, TB_AUTOSIZE, 0, 0);
83 GetWindowRect(hwndTB, &tbRect);
84 LONG tbHeight = tbRect.bottom-tbRect.top-1;
85 HDWP hdwp = BeginDeferWindowPos(3);
86 if (bOverlap && selectionPaths.empty())
88 SetWindowPos(picWindow1, nullptr, clientrect->left, clientrect->top + tbHeight, clientrect->right - clientrect->left, clientrect->bottom - clientrect->top - tbHeight, SWP_SHOWWINDOW);
90 else
92 if (bVertical)
94 if (selectionPaths.size() != 3)
96 // two image windows
97 RECT child;
98 child.left = clientrect->left;
99 child.top = clientrect->top+tbHeight;
100 child.right = clientrect->right;
101 child.bottom = nSplitterPos - (splitter_border / 2);
102 if (hdwp) hdwp = DeferWindowPos(hdwp, picWindow1, nullptr, child.left, child.top, child.right - child.left, child.bottom - child.top, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
103 child.top = nSplitterPos + (splitter_border / 2);
104 child.bottom = clientrect->bottom;
105 if (hdwp) hdwp = DeferWindowPos(hdwp, picWindow2, nullptr, child.left, child.top, child.right - child.left, child.bottom - child.top, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
107 else
109 // three image windows
110 RECT child;
111 child.left = clientrect->left;
112 child.top = clientrect->top+tbHeight;
113 child.right = clientrect->right;
114 child.bottom = nSplitterPos - (splitter_border / 2);
115 if (hdwp) hdwp = DeferWindowPos(hdwp, picWindow1, nullptr, child.left, child.top, child.right - child.left, child.bottom - child.top, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
116 child.top = nSplitterPos + (splitter_border / 2);
117 child.bottom = nSplitterPos2 - (splitter_border / 2);
118 if (hdwp) hdwp = DeferWindowPos(hdwp, picWindow2, nullptr, child.left, child.top, child.right - child.left, child.bottom - child.top, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
119 child.top = nSplitterPos2 + (splitter_border / 2);
120 child.bottom = clientrect->bottom;
121 if (hdwp) hdwp = DeferWindowPos(hdwp, picWindow3, nullptr, child.left, child.top, child.right - child.left, child.bottom - child.top, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
124 else
126 if (selectionPaths.size() != 3)
128 // two image windows
129 RECT child;
130 child.left = clientrect->left;
131 child.top = clientrect->top+tbHeight;
132 child.right = nSplitterPos - (splitter_border / 2);
133 child.bottom = clientrect->bottom;
134 if (hdwp) hdwp = DeferWindowPos(hdwp, picWindow1, nullptr, child.left, child.top, child.right - child.left, child.bottom - child.top, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
135 child.left = nSplitterPos + (splitter_border / 2);
136 child.right = clientrect->right;
137 if (hdwp) hdwp = DeferWindowPos(hdwp, picWindow2, nullptr, child.left, child.top, child.right - child.left, child.bottom - child.top, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
139 else
141 // three image windows
142 RECT child;
143 child.left = clientrect->left;
144 child.top = clientrect->top+tbHeight;
145 child.right = nSplitterPos - (splitter_border / 2);
146 child.bottom = clientrect->bottom;
147 if (hdwp) hdwp = DeferWindowPos(hdwp, picWindow1, nullptr, child.left, child.top, child.right - child.left, child.bottom - child.top, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
148 child.left = nSplitterPos + (splitter_border / 2);
149 child.right = nSplitterPos2 - (splitter_border / 2);
150 if (hdwp) hdwp = DeferWindowPos(hdwp, picWindow2, nullptr, child.left, child.top, child.right - child.left, child.bottom - child.top, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
151 child.left = nSplitterPos2 + (splitter_border / 2);
152 child.right = clientrect->right;
153 if (hdwp) hdwp = DeferWindowPos(hdwp, picWindow3, nullptr, child.left, child.top, child.right - child.left, child.bottom - child.top, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
157 if (hdwp) EndDeferWindowPos(hdwp);
158 picWindow1.SetTransparentColor(transparentColor);
159 picWindow2.SetTransparentColor(transparentColor);
160 picWindow3.SetTransparentColor(transparentColor);
161 InvalidateRect(*this, nullptr, FALSE);
164 LRESULT CALLBACK CMainWindow::WinMsgHandler(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
166 if (uMsg == TaskBarButtonCreated)
168 SetUUIDOverlayIcon(hwnd);
170 auto optRet = CTheme::HandleMenuBar(hwnd, uMsg, wParam, lParam);
171 if (optRet.has_value())
172 return optRet.value();
173 switch (uMsg)
175 case WM_CREATE:
177 m_hwnd = hwnd;
178 picWindow1.RegisterAndCreateWindow(hwnd);
179 picWindow2.RegisterAndCreateWindow(hwnd);
180 if (selectionPaths.empty())
182 picWindow1.SetPic(leftpicpath, leftpictitle, true);
183 picWindow2.SetPic(rightpicpath, rightpictitle, false);
185 picWindow1.SetOtherPicWindow(&picWindow2);
186 picWindow2.SetOtherPicWindow(&picWindow1);
188 else
190 picWindow3.RegisterAndCreateWindow(hwnd);
192 picWindow1.SetPic(selectionPaths[FileType::Mine], selectionTitles[FileType::Mine], false);
193 picWindow2.SetPic(selectionPaths[FileType::Base], selectionTitles[FileType::Base], false);
194 picWindow3.SetPic(selectionPaths[FileType::Theirs], selectionTitles[FileType::Theirs], false);
197 picWindow1.SetSelectionMode(!selectionPaths.empty());
198 picWindow2.SetSelectionMode(!selectionPaths.empty());
199 picWindow3.SetSelectionMode(!selectionPaths.empty());
201 CreateToolbar();
202 // center the splitter
203 RECT rect;
204 GetClientRect(hwnd, &rect);
205 if (selectionPaths.size() != 3)
207 nSplitterPos = (rect.right-rect.left)/2;
208 nSplitterPos2 = 0;
210 else
212 nSplitterPos = (rect.right-rect.left)/3;
213 nSplitterPos2 = (rect.right-rect.left)*2/3;
216 PositionChildren(&rect);
217 picWindow1.FitImageInWindow();
218 picWindow2.FitImageInWindow();
219 picWindow3.FitImageInWindow();
221 m_themeCallbackId = CTheme::Instance().RegisterThemeChangeCallback([this]() { SetTheme(CTheme::Instance().IsDarkTheme()); });
222 SetTheme(CTheme::Instance().IsDarkTheme());
224 break;
225 case WM_COMMAND:
227 return DoCommand(LOWORD(wParam), lParam);
229 break;
230 case WM_PAINT:
232 PAINTSTRUCT ps;
233 HDC hdc;
234 RECT rect;
236 ::GetClientRect(*this, &rect);
237 hdc = BeginPaint(hwnd, &ps);
238 SetBkColor(hdc, GetSysColor(COLOR_3DFACE));
239 ::ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rect, nullptr, 0, nullptr);
240 EndPaint(hwnd, &ps);
242 break;
243 case WM_GETMINMAXINFO:
245 auto mmi = reinterpret_cast<MINMAXINFO*>(lParam);
246 mmi->ptMinTrackSize.x = WINDOW_MINWIDTH;
247 mmi->ptMinTrackSize.y = WINDOW_MINHEIGHT;
248 return 0;
250 break;
251 case WM_SIZE:
253 RECT rect;
254 GetClientRect(hwnd, &rect);
255 if (bVertical)
257 RECT tbRect;
258 GetWindowRect(hwndTB, &tbRect);
259 LONG tbHeight = tbRect.bottom-tbRect.top-1;
260 if (selectionPaths.size() != 3)
262 nSplitterPos = (rect.bottom-rect.top)/2+tbHeight;
263 nSplitterPos2 = 0;
265 else
267 nSplitterPos = (rect.bottom-rect.top)/3+tbHeight;
268 nSplitterPos2 = (rect.bottom-rect.top)*2/3+tbHeight;
271 else
273 if (selectionPaths.size() != 3)
275 nSplitterPos = (rect.right-rect.left)/2;
276 nSplitterPos2 = 0;
278 else
280 nSplitterPos = (rect.right-rect.left)/3;
281 nSplitterPos2 = (rect.right-rect.left)*2/3;
284 PositionChildren(&rect);
286 break;
287 case WM_SETCURSOR:
289 if (reinterpret_cast<HWND>(wParam) == *this)
291 RECT rect;
292 POINT pt;
293 GetClientRect(*this, &rect);
294 GetCursorPos(&pt);
295 ScreenToClient(*this, &pt);
296 if (PtInRect(&rect, pt))
298 if (bVertical)
300 HCURSOR hCur = LoadCursor(nullptr, IDC_SIZENS);
301 SetCursor(hCur);
303 else
305 HCURSOR hCur = LoadCursor(nullptr, IDC_SIZEWE);
306 SetCursor(hCur);
308 return TRUE;
311 return DefWindowProc(hwnd, uMsg, wParam, lParam);
313 break;
314 case WM_LBUTTONDOWN:
315 Splitter_OnLButtonDown(hwnd, uMsg, wParam, lParam);
316 break;
317 case WM_LBUTTONUP:
318 Splitter_OnLButtonUp(hwnd, uMsg, wParam, lParam);
319 break;
320 case WM_CAPTURECHANGED:
321 Splitter_CaptureChanged();
322 break;
323 case WM_MOUSEMOVE:
324 Splitter_OnMouseMove(hwnd, uMsg, wParam, lParam);
325 break;
326 case WM_MOUSEWHEEL:
328 // find out if the mouse cursor is over one of the views, and if
329 // it is, pass the mouse wheel message to that view
330 POINT pt;
331 DWORD ptW = GetMessagePos();
332 pt.x = GET_X_LPARAM(ptW);
333 pt.y = GET_Y_LPARAM(ptW);
334 RECT rect;
335 GetWindowRect(picWindow1, &rect);
336 if (PtInRect(&rect, pt))
338 picWindow1.OnMouseWheel(GET_KEYSTATE_WPARAM(wParam), GET_WHEEL_DELTA_WPARAM(wParam));
340 else
342 GetWindowRect(picWindow2, &rect);
343 if (PtInRect(&rect, pt))
345 picWindow2.OnMouseWheel(GET_KEYSTATE_WPARAM(wParam), GET_WHEEL_DELTA_WPARAM(wParam));
347 else
349 GetWindowRect(picWindow3, &rect);
350 if (PtInRect(&rect, pt))
352 picWindow3.OnMouseWheel(GET_KEYSTATE_WPARAM(wParam), GET_WHEEL_DELTA_WPARAM(wParam));
357 break;
358 case WM_MOUSEHWHEEL:
360 // find out if the mouse cursor is over one of the views, and if
361 // it is, pass the mouse wheel message to that view
362 POINT pt;
363 DWORD ptW = GetMessagePos();
364 pt.x = GET_X_LPARAM(ptW);
365 pt.y = GET_Y_LPARAM(ptW);
366 RECT rect;
367 GetWindowRect(picWindow1, &rect);
368 if (PtInRect(&rect, pt))
370 picWindow1.OnMouseWheel(GET_KEYSTATE_WPARAM(wParam)|MK_SHIFT, GET_WHEEL_DELTA_WPARAM(wParam));
372 else
374 GetWindowRect(picWindow2, &rect);
375 if (PtInRect(&rect, pt))
377 picWindow2.OnMouseWheel(GET_KEYSTATE_WPARAM(wParam)|MK_SHIFT, GET_WHEEL_DELTA_WPARAM(wParam));
379 else
381 GetWindowRect(picWindow3, &rect);
382 if (PtInRect(&rect, pt))
384 picWindow3.OnMouseWheel(GET_KEYSTATE_WPARAM(wParam)|MK_SHIFT, GET_WHEEL_DELTA_WPARAM(wParam));
389 break;
390 case WM_NOTIFY:
392 auto pNMHDR = reinterpret_cast<LPNMHDR>(lParam);
393 if (pNMHDR->code == TTN_GETDISPINFO)
395 auto lpttt = reinterpret_cast<LPTOOLTIPTEXT>(lParam);
396 lpttt->hinst = hResource;
398 // Specify the resource identifier of the descriptive
399 // text for the given button.
400 wchar_t stringbuf[MAX_PATH] = { 0 };
401 MENUITEMINFO mii;
402 mii.cbSize = sizeof(MENUITEMINFO);
403 mii.fMask = MIIM_TYPE;
404 mii.dwTypeData = stringbuf;
405 mii.cch = _countof(stringbuf);
406 GetMenuItemInfo(GetMenu(*this), static_cast<UINT>(lpttt->hdr.idFrom), FALSE, &mii);
407 wcscpy_s(lpttt->lpszText, 80, stringbuf);
410 break;
411 case WM_DESTROY:
412 bWindowClosed = TRUE;
413 PostQuitMessage(0);
414 break;
415 case WM_CLOSE:
416 CTheme::Instance().RemoveRegisteredCallback(m_themeCallbackId);
417 m_themeCallbackId = 0;
418 ImageList_Destroy(hToolbarImgList);
419 ::DestroyWindow(m_hwnd);
420 break;
421 case WM_SYSCOLORCHANGE:
422 CTheme::Instance().OnSysColorChanged();
423 CTheme::Instance().SetDarkTheme(CTheme::Instance().IsDarkTheme(), true);
424 break;
425 case WM_DPICHANGED:
426 CDPIAware::Instance().Invalidate();
427 ::RedrawWindow(*this, nullptr, nullptr, RDW_FRAME | RDW_INVALIDATE | RDW_ERASE | RDW_INTERNALPAINT | RDW_ALLCHILDREN | RDW_UPDATENOW);
428 break;
429 default:
430 return DefWindowProc(hwnd, uMsg, wParam, lParam);
433 return 0;
436 LRESULT CMainWindow::DoCommand(int id, LPARAM lParam)
438 switch (id)
440 case ID_FILE_OPEN:
442 if (OpenDialog())
444 picWindow1.SetPic(leftpicpath, L"", true);
445 picWindow2.SetPic(rightpicpath, L"", false);
446 if (bOverlap)
448 picWindow1.SetSecondPic(picWindow2.GetPic(), rightpictitle, rightpicpath);
450 else
452 picWindow1.SetSecondPic();
454 RECT rect;
455 GetClientRect(*this, &rect);
456 PositionChildren(&rect);
457 picWindow1.FitImageInWindow();
458 picWindow2.FitImageInWindow();
461 break;
462 case ID_VIEW_IMAGEINFO:
464 bShowInfo = !bShowInfo;
465 HMENU hMenu = GetMenu(*this);
466 UINT uCheck = MF_BYCOMMAND;
467 uCheck |= bShowInfo ? MF_CHECKED : MF_UNCHECKED;
468 CheckMenuItem(hMenu, ID_VIEW_IMAGEINFO, uCheck);
470 picWindow1.ShowInfo(bShowInfo);
471 picWindow2.ShowInfo(bShowInfo);
472 picWindow3.ShowInfo(bShowInfo);
474 // change the state of the toolbar button
475 TBBUTTONINFO tbi;
476 tbi.cbSize = sizeof(TBBUTTONINFO);
477 tbi.dwMask = TBIF_STATE;
478 tbi.fsState = bShowInfo ? TBSTATE_CHECKED | TBSTATE_ENABLED : TBSTATE_ENABLED;
479 SendMessage(hwndTB, TB_SETBUTTONINFO, ID_VIEW_IMAGEINFO, reinterpret_cast<LPARAM>(&tbi));
481 break;
482 case ID_VIEW_OVERLAPIMAGES:
484 bOverlap = !bOverlap;
485 HMENU hMenu = GetMenu(*this);
486 UINT uCheck = MF_BYCOMMAND;
487 uCheck |= bOverlap ? MF_CHECKED : MF_UNCHECKED;
488 CheckMenuItem(hMenu, ID_VIEW_OVERLAPIMAGES, uCheck);
489 uCheck |= ((m_BlendType == CPicWindow::BlendType::Alpha) && bOverlap) ? MF_CHECKED : MF_UNCHECKED;
490 CheckMenuItem(hMenu, ID_VIEW_BLENDALPHA, uCheck);
491 UINT uEnabled = MF_BYCOMMAND;
492 uEnabled |= bOverlap ? MF_ENABLED : MF_DISABLED | MF_GRAYED;
493 EnableMenuItem(hMenu, ID_VIEW_BLENDALPHA, uEnabled);
495 // change the state of the toolbar button
496 TBBUTTONINFO tbi;
497 tbi.cbSize = sizeof(TBBUTTONINFO);
498 tbi.dwMask = TBIF_STATE;
499 tbi.fsState = bOverlap ? TBSTATE_CHECKED | TBSTATE_ENABLED : TBSTATE_ENABLED;
500 SendMessage(hwndTB, TB_SETBUTTONINFO, ID_VIEW_OVERLAPIMAGES, reinterpret_cast<LPARAM>(&tbi));
502 tbi.fsState = ((m_BlendType == CPicWindow::BlendType::Alpha) && bOverlap) ? TBSTATE_CHECKED : 0;
503 if (bOverlap)
504 tbi.fsState |= TBSTATE_ENABLED;
505 else
506 tbi.fsState = 0;
507 SendMessage(hwndTB, TB_SETBUTTONINFO, ID_VIEW_BLENDALPHA, reinterpret_cast<LPARAM>(&tbi));
509 if (bOverlap)
510 tbi.fsState = 0;
511 else
512 tbi.fsState = bVertical ? TBSTATE_ENABLED | TBSTATE_CHECKED : TBSTATE_ENABLED;
513 SendMessage(hwndTB, TB_SETBUTTONINFO, ID_VIEW_ARRANGEVERTICAL, reinterpret_cast<LPARAM>(&tbi));
515 if (bOverlap)
517 bLinkedPositions = true;
518 picWindow1.LinkPositions(bLinkedPositions);
519 picWindow2.LinkPositions(bLinkedPositions);
520 tbi.fsState = TBSTATE_CHECKED;
522 else
523 tbi.fsState = bLinkedPositions ? TBSTATE_ENABLED | TBSTATE_CHECKED : TBSTATE_ENABLED;
524 SendMessage(hwndTB, TB_SETBUTTONINFO, ID_VIEW_LINKIMAGESTOGETHER, reinterpret_cast<LPARAM>(&tbi));
526 ShowWindow(picWindow2, bOverlap ? SW_HIDE : SW_SHOW);
528 if (bOverlap)
530 picWindow1.StopTimer();
531 picWindow2.StopTimer();
532 picWindow1.SetSecondPic(picWindow2.GetPic(), rightpictitle, rightpicpath,
533 picWindow2.GetHPos(), picWindow2.GetVPos());
534 picWindow1.SetBlendAlpha(m_BlendType, 0.5f);
536 else
538 picWindow1.SetSecondPic();
540 picWindow1.SetOverlapMode(bOverlap);
541 picWindow2.SetOverlapMode(bOverlap);
544 RECT rect;
545 GetClientRect(*this, &rect);
546 PositionChildren(&rect);
548 return 0;
550 break;
551 case ID_VIEW_BLENDALPHA:
553 if (m_BlendType == CPicWindow::BlendType::Alpha)
554 m_BlendType = CPicWindow::BlendType::Xor;
555 else
556 m_BlendType = CPicWindow::BlendType::Alpha;
558 HMENU hMenu = GetMenu(*this);
559 UINT uCheck = MF_BYCOMMAND;
560 uCheck |= ((m_BlendType == CPicWindow::BlendType::Alpha) && bOverlap) ? MF_CHECKED : MF_UNCHECKED;
561 CheckMenuItem(hMenu, ID_VIEW_BLENDALPHA, uCheck);
562 UINT uEnabled = MF_BYCOMMAND;
563 uEnabled |= bOverlap ? MF_ENABLED : MF_DISABLED | MF_GRAYED;
564 EnableMenuItem(hMenu, ID_VIEW_BLENDALPHA, uEnabled);
566 // change the state of the toolbar button
567 TBBUTTONINFO tbi;
568 tbi.cbSize = sizeof(TBBUTTONINFO);
569 tbi.dwMask = TBIF_STATE;
570 tbi.fsState = ((m_BlendType == CPicWindow::BlendType::Alpha) && bOverlap) ? TBSTATE_CHECKED | TBSTATE_ENABLED : TBSTATE_ENABLED;
571 SendMessage(hwndTB, TB_SETBUTTONINFO, ID_VIEW_BLENDALPHA, reinterpret_cast<LPARAM>(&tbi));
572 picWindow1.SetBlendAlpha(m_BlendType, picWindow1.GetBlendAlpha());
573 PositionChildren();
575 break;
576 case ID_VIEW_TRANSPARENTCOLOR:
578 static COLORREF customColors[16] = {0};
579 CHOOSECOLOR ccDlg = { 0 };
580 ccDlg.lStructSize = sizeof(ccDlg);
581 ccDlg.hwndOwner = m_hwnd;
582 ccDlg.rgbResult = transparentColor;
583 ccDlg.lpCustColors = customColors;
584 ccDlg.Flags = CC_RGBINIT | CC_FULLOPEN;
585 if(ChooseColor(&ccDlg))
587 transparentColor = ccDlg.rgbResult;
588 picWindow1.SetTransparentColor(transparentColor);
589 picWindow2.SetTransparentColor(transparentColor);
590 picWindow3.SetTransparentColor(transparentColor);
591 // The color picker takes the focus and we don't get it back.
592 ::SetFocus(picWindow1);
595 break;
596 case ID_VIEW_FITIMAGEWIDTHS:
598 bFitWidths = !bFitWidths;
599 picWindow1.FitWidths(bFitWidths);
600 picWindow2.FitWidths(bFitWidths);
601 picWindow3.FitWidths(bFitWidths);
603 HMENU hMenu = GetMenu(*this);
604 UINT uCheck = MF_BYCOMMAND;
605 uCheck |= bFitWidths ? MF_CHECKED : MF_UNCHECKED;
606 CheckMenuItem(hMenu, ID_VIEW_FITIMAGEWIDTHS, uCheck);
608 // change the state of the toolbar button
609 TBBUTTONINFO tbi;
610 tbi.cbSize = sizeof(TBBUTTONINFO);
611 tbi.dwMask = TBIF_STATE;
612 tbi.fsState = bFitWidths ? TBSTATE_CHECKED | TBSTATE_ENABLED : TBSTATE_ENABLED;
613 SendMessage(hwndTB, TB_SETBUTTONINFO, ID_VIEW_FITIMAGEWIDTHS, reinterpret_cast<LPARAM>(&tbi));
615 break;
616 case ID_VIEW_FITIMAGEHEIGHTS:
618 bFitHeights = !bFitHeights;
619 picWindow1.FitHeights(bFitHeights);
620 picWindow2.FitHeights(bFitHeights);
621 picWindow3.FitHeights(bFitHeights);
623 HMENU hMenu = GetMenu(*this);
624 UINT uCheck = MF_BYCOMMAND;
625 uCheck |= bFitHeights ? MF_CHECKED : MF_UNCHECKED;
626 CheckMenuItem(hMenu, ID_VIEW_FITIMAGEHEIGHTS, uCheck);
628 // change the state of the toolbar button
629 TBBUTTONINFO tbi;
630 tbi.cbSize = sizeof(TBBUTTONINFO);
631 tbi.dwMask = TBIF_STATE;
632 tbi.fsState = bFitHeights ? TBSTATE_CHECKED | TBSTATE_ENABLED : TBSTATE_ENABLED;
633 SendMessage(hwndTB, TB_SETBUTTONINFO, ID_VIEW_FITIMAGEHEIGHTS, reinterpret_cast<LPARAM>(&tbi));
635 break;
636 case ID_VIEW_LINKIMAGESTOGETHER:
638 bLinkedPositions = !bLinkedPositions;
639 picWindow1.LinkPositions(bLinkedPositions);
640 picWindow2.LinkPositions(bLinkedPositions);
641 picWindow3.LinkPositions(bLinkedPositions);
643 HMENU hMenu = GetMenu(*this);
644 UINT uCheck = MF_BYCOMMAND;
645 uCheck |= bLinkedPositions ? MF_CHECKED : MF_UNCHECKED;
646 CheckMenuItem(hMenu, ID_VIEW_LINKIMAGESTOGETHER, uCheck);
648 // change the state of the toolbar button
649 TBBUTTONINFO tbi;
650 tbi.cbSize = sizeof(TBBUTTONINFO);
651 tbi.dwMask = TBIF_STATE;
652 tbi.fsState = bLinkedPositions ? TBSTATE_CHECKED | TBSTATE_ENABLED : TBSTATE_ENABLED;
653 SendMessage(hwndTB, TB_SETBUTTONINFO, ID_VIEW_LINKIMAGESTOGETHER, reinterpret_cast<LPARAM>(&tbi));
655 break;
656 case ID_VIEW_ALPHA0:
657 picWindow1.SetBlendAlpha(m_BlendType, 0.0f);
658 break;
659 case ID_VIEW_ALPHA255:
660 picWindow1.SetBlendAlpha(m_BlendType, 1.0f);
661 break;
662 case ID_VIEW_ALPHA127:
663 picWindow1.SetBlendAlpha(m_BlendType, 0.5f);
664 break;
665 case ID_VIEW_ALPHATOGGLE:
666 picWindow1.ToggleAlpha();
667 break;
668 case ID_VIEW_FITIMAGESINWINDOW:
670 picWindow1.FitImageInWindow();
671 picWindow2.FitImageInWindow();
672 picWindow3.FitImageInWindow();
674 break;
675 case ID_VIEW_ORININALSIZE:
677 picWindow1.SetZoom(100, false);
678 picWindow2.SetZoom(100, false);
679 picWindow3.SetZoom(100, false);
680 picWindow1.CenterImage();
681 picWindow2.CenterImage();
682 picWindow3.CenterImage();
684 break;
685 case ID_VIEW_ZOOMIN:
687 picWindow1.Zoom(true, false);
688 if ((!(bFitWidths || bFitHeights))&&(!bOverlap))
690 picWindow2.Zoom(true, false);
691 picWindow3.Zoom(true, false);
694 break;
695 case ID_VIEW_ZOOMOUT:
697 picWindow1.Zoom(false, false);
698 if ((!(bFitWidths || bFitHeights))&&(!bOverlap))
700 picWindow2.Zoom(false, false);
701 picWindow3.Zoom(false, false);
704 break;
705 case ID_VIEW_ARRANGEVERTICAL:
707 bVertical = !bVertical;
708 RECT rect;
709 GetClientRect(*this, &rect);
710 if (bVertical)
712 RECT tbRect;
713 GetWindowRect(hwndTB, &tbRect);
714 LONG tbHeight = tbRect.bottom-tbRect.top-1;
715 if (selectionPaths.size() != 3)
717 nSplitterPos = (rect.bottom-rect.top)/2+tbHeight;
718 nSplitterPos2 = 0;
720 else
722 nSplitterPos = (rect.bottom-rect.top)/3+tbHeight;
723 nSplitterPos2 = (rect.bottom-rect.top)*2/3+tbHeight;
726 else
728 if (selectionPaths.size() != 3)
730 nSplitterPos = (rect.right-rect.left)/2;
731 nSplitterPos2 = 0;
733 else
735 nSplitterPos = (rect.right-rect.left)/3;
736 nSplitterPos2 = (rect.right-rect.left)*2/3;
739 HMENU hMenu = GetMenu(*this);
740 UINT uCheck = MF_BYCOMMAND;
741 uCheck |= bVertical ? MF_CHECKED : MF_UNCHECKED;
742 CheckMenuItem(hMenu, ID_VIEW_ARRANGEVERTICAL, uCheck);
743 // change the state of the toolbar button
744 TBBUTTONINFO tbi;
745 tbi.cbSize = sizeof(TBBUTTONINFO);
746 tbi.dwMask = TBIF_STATE;
747 tbi.fsState = bVertical ? TBSTATE_CHECKED | TBSTATE_ENABLED : TBSTATE_ENABLED;
748 SendMessage(hwndTB, TB_SETBUTTONINFO, ID_VIEW_ARRANGEVERTICAL, reinterpret_cast<LPARAM>(&tbi));
750 PositionChildren(&rect);
752 break;
753 case ID_ABOUT:
755 CAboutDlg dlg(*this);
756 dlg.DoModal(hInst, IDD_ABOUT, *this);
758 break;
759 case SELECTBUTTON_ID:
761 auto hSource = reinterpret_cast<HWND>(lParam);
762 FileType resolveWith;
763 if (picWindow1 == hSource)
764 resolveWith = FileType::Mine;
765 else if (picWindow2 == hSource)
766 resolveWith = FileType::Base;
767 else if (picWindow3 == hSource)
768 resolveWith = FileType::Theirs;
769 else
770 break;
772 if (selectionResult.empty())
774 PostQuitMessage(static_cast<int>(resolveWith));
775 break;
778 CopyFile(selectionPaths[resolveWith].c_str(), selectionResult.c_str(), FALSE);
780 CAutoBuf projectRoot;
781 if (git_repository_discover(projectRoot, CUnicodeUtils::GetUTF8(selectionResult.c_str()), FALSE, nullptr) < 0 && strstr(projectRoot->ptr, "/.git/"))
783 PostQuitMessage(static_cast<int>(resolveWith));
784 break;
787 CAutoRepository repository(projectRoot->ptr);
788 if (!repository)
790 PostQuitMessage(static_cast<int>(resolveWith));
791 break;
794 CStringA subpath = CUnicodeUtils::GetUTF8(selectionResult.c_str()).Mid(static_cast<int>(strlen(git_repository_workdir(repository))));
796 CAutoIndex index;
797 if (git_repository_index(index.GetPointer(), repository) || (git_index_get_bypath(index, subpath, 1) == nullptr && git_index_get_bypath(index, subpath, 2) == nullptr))
799 PostQuitMessage(static_cast<int>(resolveWith));
800 break;
803 CString sTemp;
804 sTemp.Format(ResString(hResource, IDS_MARKASRESOLVED), static_cast<LPCWSTR>(CPathUtils::GetFileNameFromPath(selectionResult.c_str())));
805 if (MessageBox(m_hwnd, sTemp, L"TortoiseGitMerge", MB_YESNO | MB_ICONQUESTION) != IDYES)
806 break;
808 CString cmd;
809 cmd.Format(L"\"%sTortoiseGitProc.exe\" /command:resolve /path:\"%s\" /closeonend:1 /noquestion /skipcheck /silent", static_cast<LPCWSTR>(CPathUtils::GetAppDirectory()), selectionResult.c_str());
810 if (resolveMsgWnd)
811 cmd.AppendFormat(L" /resolvemsghwnd:%I64d /resolvemsgwparam:%I64d /resolvemsglparam:%I64d", reinterpret_cast<__int64>(resolveMsgWnd), static_cast<__int64>(resolveMsgWParam), static_cast<__int64>(resolveMsgLParam));
813 STARTUPINFO startup = { 0 };
814 PROCESS_INFORMATION process = { 0 };
815 startup.cb = sizeof(startup);
817 if (!CreateProcess(nullptr, cmd.GetBuffer(), nullptr, nullptr, FALSE, CREATE_UNICODE_ENVIRONMENT, nullptr, nullptr, &startup, &process))
819 cmd.ReleaseBuffer();
820 PostQuitMessage(static_cast<int>(resolveWith));
821 break;
823 cmd.ReleaseBuffer();
825 AllowSetForegroundWindow(process.dwProcessId);
827 CloseHandle(process.hThread);
828 CloseHandle(process.hProcess);
830 PostQuitMessage(static_cast<int>(resolveWith));
832 break;
833 case ID_VIEW_DARKMODE:
834 CTheme::Instance().SetDarkTheme(!CTheme::Instance().IsDarkTheme());
835 break;
836 case IDM_EXIT:
837 ::PostQuitMessage(0);
838 return 0;
839 break;
840 default:
841 break;
843 return 1;
846 // splitter stuff
847 void CMainWindow::DrawXorBar(HDC hdc, int x1, int y1, int width, int height)
849 static WORD _dotPatternBmp[8] =
851 0x0055, 0x00aa, 0x0055, 0x00aa,
852 0x0055, 0x00aa, 0x0055, 0x00aa
855 HBITMAP hbm;
856 HBRUSH hbr, hbrushOld;
858 hbm = CreateBitmap(8, 8, 1, 1, _dotPatternBmp);
859 hbr = CreatePatternBrush(hbm);
861 SetBrushOrgEx(hdc, x1, y1, 0);
862 hbrushOld = static_cast<HBRUSH>(SelectObject(hdc, hbr));
864 PatBlt(hdc, x1, y1, width, height, PATINVERT);
866 SelectObject(hdc, hbrushOld);
868 DeleteObject(hbr);
869 DeleteObject(hbm);
872 LRESULT CMainWindow::Splitter_OnLButtonDown(HWND hwnd, UINT /*iMsg*/, WPARAM /*wParam*/, LPARAM lParam)
874 POINT pt;
875 HDC hdc;
876 RECT rect;
877 RECT clientrect;
879 pt.x = static_cast<short>(LOWORD(lParam)); // horizontal position of cursor
880 pt.y = static_cast<short>(HIWORD(lParam));
882 GetClientRect(hwnd, &clientrect);
883 GetWindowRect(hwnd, &rect);
884 POINT zero = {0,0};
885 ClientToScreen(hwnd, &zero);
886 OffsetRect(&clientrect, zero.x-rect.left, zero.y-rect.top);
888 ClientToScreen(hwnd, &pt);
889 // find out which drag bar is used
890 bDrag2 = false;
891 if (!selectionPaths.empty())
893 RECT pic2Rect;
894 GetWindowRect(picWindow2, &pic2Rect);
895 if (bVertical)
897 if (pic2Rect.bottom <= pt.y)
898 bDrag2 = true;
900 else
902 if (pic2Rect.right <= pt.x)
903 bDrag2 = true;
907 //convert the mouse coordinates relative to the top-left of
908 //the window
909 pt.x -= rect.left;
910 pt.y -= rect.top;
912 //same for the window coordinates - make them relative to 0,0
913 OffsetRect(&rect, -rect.left, -rect.top);
915 if (pt.x < 0)
916 pt.x = 0;
917 if (pt.x > rect.right-4)
918 pt.x = rect.right-4;
919 if (pt.y < 0)
920 pt.y = 0;
921 if (pt.y > rect.bottom-4)
922 pt.y = rect.bottom-4;
924 bDragMode = true;
926 SetCapture(hwnd);
928 hdc = GetWindowDC(hwnd);
929 if (bVertical)
930 DrawXorBar(hdc, clientrect.left, pt.y+2, clientrect.right-clientrect.left-2, 4);
931 else
932 DrawXorBar(hdc, pt.x+2, clientrect.top, 4, clientrect.bottom-clientrect.top-2);
933 ReleaseDC(hwnd, hdc);
935 oldx = pt.x;
936 oldy = pt.y;
938 return 0;
941 void CMainWindow::Splitter_CaptureChanged()
943 bDragMode = false;
946 void CMainWindow::SetTheme(bool bDark)
948 transparentColor = ::GetSysColor(COLOR_WINDOW);
949 picWindow1.SetTransparentColor(transparentColor);
950 picWindow2.SetTransparentColor(transparentColor);
951 picWindow3.SetTransparentColor(transparentColor);
952 if (bDark)
954 DarkModeHelper::Instance().AllowDarkModeForApp(TRUE);
956 DarkModeHelper::Instance().AllowDarkModeForWindow(*this, TRUE);
957 SetClassLongPtr(*this, GCLP_HBRBACKGROUND, reinterpret_cast<LONG_PTR>(GetStockObject(BLACK_BRUSH)));
958 if (FAILED(SetWindowTheme(*this, L"DarkMode_Explorer", nullptr)))
959 SetWindowTheme(*this, L"Explorer", nullptr);
960 DarkModeHelper::Instance().AllowDarkModeForWindow(hwndTB, TRUE);
961 if (FAILED(SetWindowTheme(hwndTB, L"DarkMode_Explorer", nullptr)))
962 SetWindowTheme(hwndTB, L"Explorer", nullptr);
963 BOOL darkFlag = TRUE;
964 DarkModeHelper::WINDOWCOMPOSITIONATTRIBDATA data = { DarkModeHelper::WINDOWCOMPOSITIONATTRIB::WCA_USEDARKMODECOLORS, &darkFlag, sizeof(darkFlag) };
965 DarkModeHelper::Instance().SetWindowCompositionAttribute(*this, &data);
966 DarkModeHelper::Instance().FlushMenuThemes();
967 DarkModeHelper::Instance().RefreshImmersiveColorPolicyState();
969 else
971 DarkModeHelper::Instance().AllowDarkModeForApp(FALSE);
972 DarkModeHelper::Instance().AllowDarkModeForWindow(*this, FALSE);
973 DarkModeHelper::Instance().AllowDarkModeForWindow(hwndTB, FALSE);
974 SetClassLongPtr(*this, GCLP_HBRBACKGROUND, reinterpret_cast<LONG_PTR>(GetSysColorBrush(COLOR_3DFACE)));
975 SetWindowTheme(*this, L"Explorer", nullptr);
976 SetWindowTheme(hwndTB, L"Explorer", nullptr);
977 DarkModeHelper::Instance().AllowDarkModeForWindow(*this, FALSE);
978 DarkModeHelper::Instance().AllowDarkModeForWindow(hwndTB, FALSE);
979 BOOL darkFlag = FALSE;
980 DarkModeHelper::WINDOWCOMPOSITIONATTRIBDATA data = { DarkModeHelper::WINDOWCOMPOSITIONATTRIB::WCA_USEDARKMODECOLORS, &darkFlag, sizeof(darkFlag) };
981 DarkModeHelper::Instance().SetWindowCompositionAttribute(*this, &data);
982 DarkModeHelper::Instance().FlushMenuThemes();
983 DarkModeHelper::Instance().RefreshImmersiveColorPolicyState();
984 DarkModeHelper::Instance().AllowDarkModeForApp(FALSE);
986 DarkModeHelper::Instance().RefreshTitleBarThemeColor(*this, bDark);
988 HMENU hMenu = GetMenu(*this);
989 UINT uCheck = MF_BYCOMMAND;
990 uCheck |= CTheme::Instance().IsDarkTheme() ? MF_CHECKED : MF_UNCHECKED;
991 CheckMenuItem(hMenu, ID_VIEW_DARKMODE, uCheck);
992 UINT uEnabled = MF_BYCOMMAND;
993 uEnabled |= CTheme::Instance().IsDarkModeAllowed() ? MF_ENABLED : MF_DISABLED;
994 EnableMenuItem(hMenu, ID_VIEW_DARKMODE, uEnabled);
996 ::RedrawWindow(*this, nullptr, nullptr, RDW_FRAME | RDW_INVALIDATE | RDW_ERASE | RDW_INTERNALPAINT | RDW_ALLCHILDREN | RDW_UPDATENOW);
999 LRESULT CMainWindow::Splitter_OnLButtonUp(HWND hwnd, UINT /*iMsg*/, WPARAM /*wParam*/, LPARAM lParam)
1001 HDC hdc;
1002 RECT rect;
1003 RECT clientrect;
1005 POINT pt;
1006 pt.x = static_cast<short>(LOWORD(lParam)); // horizontal position of cursor
1007 pt.y = static_cast<short>(HIWORD(lParam));
1009 if (bDragMode == FALSE)
1010 return 0;
1012 const auto bordersm = CDPIAware::Instance().ScaleX(*this, 2);
1013 const auto borderl = CDPIAware::Instance().ScaleY(*this, 4);
1015 GetClientRect(hwnd, &clientrect);
1016 GetWindowRect(hwnd, &rect);
1017 POINT zero = {0,0};
1018 ClientToScreen(hwnd, &zero);
1019 OffsetRect(&clientrect, zero.x-rect.left, zero.y-rect.top);
1021 ClientToScreen(hwnd, &pt);
1022 pt.x -= rect.left;
1023 pt.y -= rect.top;
1025 OffsetRect(&rect, -rect.left, -rect.top);
1027 if (pt.x < 0)
1028 pt.x = 0;
1029 if (pt.x > rect.right - borderl)
1030 pt.x = rect.right - borderl;
1031 if (pt.y < 0)
1032 pt.y = 0;
1033 if (pt.y > rect.bottom - borderl)
1034 pt.y = rect.bottom - borderl;
1036 hdc = GetWindowDC(hwnd);
1037 if (bVertical)
1038 DrawXorBar(hdc, clientrect.left, oldy + bordersm, clientrect.right - clientrect.left - bordersm, borderl);
1039 else
1040 DrawXorBar(hdc, oldx + bordersm, clientrect.top, borderl, clientrect.bottom - clientrect.top - bordersm);
1041 ReleaseDC(hwnd, hdc);
1043 oldx = pt.x;
1044 oldy = pt.y;
1046 bDragMode = false;
1048 //convert the splitter position back to screen coords.
1049 GetWindowRect(hwnd, &rect);
1050 pt.x += rect.left;
1051 pt.y += rect.top;
1053 //now convert into CLIENT coordinates
1054 ScreenToClient(hwnd, &pt);
1055 GetClientRect(hwnd, &rect);
1056 #define MINWINSIZE 10
1057 if (bVertical)
1059 if (selectionPaths.size() != 3)
1061 nSplitterPos = pt.y;
1063 else
1065 if (bDrag2)
1067 if (pt.y < (nSplitterPos+MINWINSIZE))
1068 pt.y = nSplitterPos+MINWINSIZE;
1069 nSplitterPos2 = pt.y;
1071 else
1073 if (pt.y > (nSplitterPos2-MINWINSIZE))
1074 pt.y = nSplitterPos2-MINWINSIZE;
1075 nSplitterPos = pt.y;
1079 else
1081 if (selectionPaths.size() != 3)
1083 nSplitterPos = pt.x;
1085 else
1087 if (bDrag2)
1089 if (pt.x < (nSplitterPos+MINWINSIZE))
1090 pt.x = nSplitterPos+MINWINSIZE;
1091 nSplitterPos2 = pt.x;
1093 else
1095 if (pt.x > (nSplitterPos2-MINWINSIZE))
1096 pt.x = nSplitterPos2-MINWINSIZE;
1097 nSplitterPos = pt.x;
1102 ReleaseCapture();
1104 //position the child controls
1105 PositionChildren(&rect);
1106 return 0;
1109 LRESULT CMainWindow::Splitter_OnMouseMove(HWND hwnd, UINT /*iMsg*/, WPARAM wParam, LPARAM lParam)
1111 RECT rect;
1112 RECT clientrect;
1114 POINT pt;
1116 if (bDragMode == FALSE)
1117 return 0;
1119 const auto bordersm = CDPIAware::Instance().ScaleX(*this, 2);
1120 const auto borderl = CDPIAware::Instance().ScaleY(*this, 4);
1122 pt.x = static_cast<short>(LOWORD(lParam)); // horizontal position of cursor
1123 pt.y = static_cast<short>(HIWORD(lParam));
1125 GetClientRect(hwnd, &clientrect);
1126 GetWindowRect(hwnd, &rect);
1127 POINT zero = {0,0};
1128 ClientToScreen(hwnd, &zero);
1129 OffsetRect(&clientrect, zero.x-rect.left, zero.y-rect.top);
1131 //convert the mouse coordinates relative to the top-left of
1132 //the window
1133 ClientToScreen(hwnd, &pt);
1134 pt.x -= rect.left;
1135 pt.y -= rect.top;
1137 //same for the window coordinates - make them relative to 0,0
1138 OffsetRect(&rect, -rect.left, -rect.top);
1140 if (pt.x < 0)
1141 pt.x = 0;
1142 if (pt.x > rect.right - borderl)
1143 pt.x = rect.right - borderl;
1144 if (pt.y < 0)
1145 pt.y = 0;
1146 if (pt.y > rect.bottom - borderl)
1147 pt.y = rect.bottom - borderl;
1149 if ((wParam & MK_LBUTTON) && ((bVertical && (pt.y != oldy)) || (!bVertical && (pt.x != oldx))))
1151 HDC hdc = GetWindowDC(hwnd);
1153 if (bVertical)
1155 DrawXorBar(hdc, clientrect.left, oldy + bordersm, clientrect.right - clientrect.left - bordersm, borderl);
1156 DrawXorBar(hdc, clientrect.left, pt.y + bordersm, clientrect.right - clientrect.left - bordersm, borderl);
1158 else
1160 DrawXorBar(hdc, oldx + bordersm, clientrect.top, borderl, clientrect.bottom - clientrect.top - bordersm);
1161 DrawXorBar(hdc, pt.x + bordersm, clientrect.top, borderl, clientrect.bottom - clientrect.top - bordersm);
1164 ReleaseDC(hwnd, hdc);
1166 oldx = pt.x;
1167 oldy = pt.y;
1170 return 0;
1173 bool CMainWindow::OpenDialog()
1175 return (DialogBox(hResource, MAKEINTRESOURCE(IDD_OPEN), *this, reinterpret_cast<DLGPROC>(OpenDlgProc)) == IDOK);
1178 BOOL CALLBACK CMainWindow::OpenDlgProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM /*lParam*/)
1180 switch (message)
1182 case WM_INITDIALOG:
1184 CTheme::Instance().SetThemeForDialog(hwndDlg, CTheme::Instance().IsDarkTheme());
1185 // center on the parent window
1186 HWND hParentWnd = ::GetParent(hwndDlg);
1187 RECT parentrect, childrect, centeredrect;
1188 GetWindowRect(hParentWnd, &parentrect);
1189 GetWindowRect(hwndDlg, &childrect);
1190 centeredrect.left = parentrect.left + ((parentrect.right-parentrect.left-childrect.right+childrect.left)/2);
1191 centeredrect.right = centeredrect.left + (childrect.right-childrect.left);
1192 centeredrect.top = parentrect.top + ((parentrect.bottom-parentrect.top-childrect.bottom+childrect.top)/2);
1193 centeredrect.bottom = centeredrect.top + (childrect.bottom-childrect.top);
1194 SetWindowPos(hwndDlg, nullptr, centeredrect.left, centeredrect.top, centeredrect.right - centeredrect.left, centeredrect.bottom - centeredrect.top, SWP_SHOWWINDOW);
1196 if (!leftpicpath.empty())
1197 SetDlgItemText(hwndDlg, IDC_LEFTIMAGE, leftpicpath.c_str());
1198 SetFocus(hwndDlg);
1200 break;
1201 case WM_COMMAND:
1202 switch (LOWORD(wParam))
1204 case IDC_LEFTBROWSE:
1206 wchar_t path[MAX_PATH] = { 0 };
1207 if (AskForFile(hwndDlg, path))
1209 SetDlgItemText(hwndDlg, IDC_LEFTIMAGE, path);
1212 break;
1213 case IDC_RIGHTBROWSE:
1215 wchar_t path[MAX_PATH] = { 0 };
1216 if (AskForFile(hwndDlg, path))
1218 SetDlgItemText(hwndDlg, IDC_RIGHTIMAGE, path);
1221 break;
1222 case IDOK:
1224 wchar_t path[MAX_PATH] = { 0 };
1225 if (!GetDlgItemText(hwndDlg, IDC_LEFTIMAGE, path, _countof(path)))
1226 *path = 0;
1227 leftpicpath = path;
1228 if (!GetDlgItemText(hwndDlg, IDC_RIGHTIMAGE, path, _countof(path)))
1229 *path = 0;
1230 rightpicpath = path;
1232 [[fallthrough]];
1233 case IDCANCEL:
1234 CTheme::Instance().SetThemeForDialog(hwndDlg, false);
1235 EndDialog(hwndDlg, wParam);
1236 return TRUE;
1239 return FALSE;
1242 bool CMainWindow::AskForFile(HWND owner, wchar_t* path)
1244 OPENFILENAME ofn = {0}; // common dialog box structure
1245 // Initialize OPENFILENAME
1246 ofn.lStructSize = sizeof(OPENFILENAME);
1247 ofn.hwndOwner = owner;
1248 ofn.lpstrFile = path;
1249 ofn.nMaxFile = MAX_PATH;
1250 ResString sTitle(::hResource, IDS_OPENIMAGEFILE);
1251 ofn.lpstrTitle = sTitle;
1252 ofn.Flags = OFN_DONTADDTORECENT | OFN_FILEMUSTEXIST | OFN_EXPLORER;
1253 ofn.hInstance = ::hResource;
1254 constexpr wchar_t filters[] = L"Images\0*.wmf;*.jpg;*jpeg;*.bmp;*.gif;*.png;*.ico;*.dib;*.emf;*.webp;*.svg;*.svgz\0All (*.*)\0*.*\0\0";
1255 ofn.lpstrFilter = filters;
1256 ofn.nFilterIndex = 1;
1257 // Display the Open dialog box.
1258 if (GetOpenFileName(&ofn)==FALSE)
1260 return false;
1262 return true;
1265 bool CMainWindow::CreateToolbar()
1267 // Ensure that the common control DLL is loaded.
1268 INITCOMMONCONTROLSEX icex;
1269 icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
1270 icex.dwICC = ICC_BAR_CLASSES | ICC_WIN95_CLASSES;
1271 InitCommonControlsEx(&icex);
1273 hwndTB = CreateWindowEx(TBSTYLE_EX_DOUBLEBUFFER,
1274 TOOLBARCLASSNAME,
1275 static_cast<LPCWSTR>(nullptr),
1276 WS_CHILD | WS_BORDER | WS_VISIBLE | TBSTYLE_FLAT | TBSTYLE_TOOLTIPS,
1277 0, 0, 0, 0,
1278 *this,
1279 reinterpret_cast<HMENU>(IDC_TORTOISEIDIFF),
1280 hResource,
1281 nullptr);
1282 if (hwndTB == INVALID_HANDLE_VALUE)
1283 return false;
1285 SendMessage(hwndTB, TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON), 0);
1287 TBBUTTON tbb[14];
1288 // create an imagelist containing the icons for the toolbar
1289 auto imgSizeX = CDPIAware::Instance().ScaleX(*this, 24);
1290 auto imgSizeY = CDPIAware::Instance().ScaleY(*this, 24);
1291 hToolbarImgList = ImageList_Create(imgSizeX, imgSizeY, ILC_COLOR32 | ILC_MASK | ILC_HIGHQUALITYSCALE, 12, 4);
1292 if (!hToolbarImgList)
1293 return false;
1294 int index = 0;
1295 HICON hIcon = nullptr;
1296 if (selectionPaths.empty())
1298 hIcon = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_OVERLAP), imgSizeX, imgSizeY);
1299 tbb[index].iBitmap = ImageList_AddIcon(hToolbarImgList, hIcon);
1300 tbb[index].idCommand = ID_VIEW_OVERLAPIMAGES;
1301 tbb[index].fsState = TBSTATE_ENABLED;
1302 tbb[index].fsStyle = BTNS_BUTTON;
1303 tbb[index].dwData = 0;
1304 tbb[index++].iString = 0;
1306 hIcon = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_BLEND), imgSizeX, imgSizeY);
1307 tbb[index].iBitmap = ImageList_AddIcon(hToolbarImgList, hIcon);
1308 tbb[index].idCommand = ID_VIEW_BLENDALPHA;
1309 tbb[index].fsState = 0;
1310 tbb[index].fsStyle = BTNS_BUTTON;
1311 tbb[index].dwData = 0;
1312 tbb[index++].iString = 0;
1314 tbb[index].iBitmap = 0;
1315 tbb[index].idCommand = 0;
1316 tbb[index].fsState = TBSTATE_ENABLED;
1317 tbb[index].fsStyle = BTNS_SEP;
1318 tbb[index].dwData = 0;
1319 tbb[index++].iString = 0;
1321 hIcon = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_LINK), imgSizeX, imgSizeY);
1322 tbb[index].iBitmap = ImageList_AddIcon(hToolbarImgList, hIcon);
1323 tbb[index].idCommand = ID_VIEW_LINKIMAGESTOGETHER;
1324 tbb[index].fsState = TBSTATE_ENABLED | TBSTATE_CHECKED;
1325 tbb[index].fsStyle = BTNS_BUTTON;
1326 tbb[index].dwData = 0;
1327 tbb[index++].iString = 0;
1329 hIcon = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_FITWIDTHS), imgSizeX, imgSizeY);
1330 tbb[index].iBitmap = ImageList_AddIcon(hToolbarImgList, hIcon);
1331 tbb[index].idCommand = ID_VIEW_FITIMAGEWIDTHS;
1332 tbb[index].fsState = TBSTATE_ENABLED;
1333 tbb[index].fsStyle = BTNS_BUTTON;
1334 tbb[index].dwData = 0;
1335 tbb[index++].iString = 0;
1337 hIcon = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_FITHEIGHTS), imgSizeX, imgSizeY);
1338 tbb[index].iBitmap = ImageList_AddIcon(hToolbarImgList, hIcon);
1339 tbb[index].idCommand = ID_VIEW_FITIMAGEHEIGHTS;
1340 tbb[index].fsState = TBSTATE_ENABLED;
1341 tbb[index].fsStyle = BTNS_BUTTON;
1342 tbb[index].dwData = 0;
1343 tbb[index++].iString = 0;
1345 tbb[index].iBitmap = 0;
1346 tbb[index].idCommand = 0;
1347 tbb[index].fsState = TBSTATE_ENABLED;
1348 tbb[index].fsStyle = BTNS_SEP;
1349 tbb[index].dwData = 0;
1350 tbb[index++].iString = 0;
1353 hIcon = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_FITINWINDOW), imgSizeX, imgSizeY);
1354 tbb[index].iBitmap = ImageList_AddIcon(hToolbarImgList, hIcon);
1355 tbb[index].idCommand = ID_VIEW_FITIMAGESINWINDOW;
1356 tbb[index].fsState = TBSTATE_ENABLED;
1357 tbb[index].fsStyle = BTNS_BUTTON;
1358 tbb[index].dwData = 0;
1359 tbb[index++].iString = 0;
1361 hIcon = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_ORIGSIZE), imgSizeX, imgSizeY);
1362 tbb[index].iBitmap = ImageList_AddIcon(hToolbarImgList, hIcon);
1363 tbb[index].idCommand = ID_VIEW_ORININALSIZE;
1364 tbb[index].fsState = TBSTATE_ENABLED;
1365 tbb[index].fsStyle = BTNS_BUTTON;
1366 tbb[index].dwData = 0;
1367 tbb[index++].iString = 0;
1369 hIcon = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_ZOOMIN), imgSizeX, imgSizeY);
1370 tbb[index].iBitmap = ImageList_AddIcon(hToolbarImgList, hIcon);
1371 tbb[index].idCommand = ID_VIEW_ZOOMIN;
1372 tbb[index].fsState = TBSTATE_ENABLED;
1373 tbb[index].fsStyle = BTNS_BUTTON;
1374 tbb[index].dwData = 0;
1375 tbb[index++].iString = 0;
1377 hIcon = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_ZOOMOUT), imgSizeX, imgSizeY);
1378 tbb[index].iBitmap = ImageList_AddIcon(hToolbarImgList, hIcon);
1379 tbb[index].idCommand = ID_VIEW_ZOOMOUT;
1380 tbb[index].fsState = TBSTATE_ENABLED;
1381 tbb[index].fsStyle = BTNS_BUTTON;
1382 tbb[index].dwData = 0;
1383 tbb[index++].iString = 0;
1385 tbb[index].iBitmap = 0;
1386 tbb[index].idCommand = 0;
1387 tbb[index].fsState = TBSTATE_ENABLED;
1388 tbb[index].fsStyle = BTNS_SEP;
1389 tbb[index].dwData = 0;
1390 tbb[index++].iString = 0;
1392 hIcon = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_IMGINFO), imgSizeX, imgSizeY);
1393 tbb[index].iBitmap = ImageList_AddIcon(hToolbarImgList, hIcon);
1394 tbb[index].idCommand = ID_VIEW_IMAGEINFO;
1395 tbb[index].fsState = TBSTATE_ENABLED;
1396 tbb[index].fsStyle = BTNS_BUTTON;
1397 tbb[index].dwData = 0;
1398 tbb[index++].iString = 0;
1400 hIcon = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_VERTICAL), imgSizeX, imgSizeY);
1401 tbb[index].iBitmap = ImageList_AddIcon(hToolbarImgList, hIcon);
1402 tbb[index].idCommand = ID_VIEW_ARRANGEVERTICAL;
1403 tbb[index].fsState = TBSTATE_ENABLED;
1404 tbb[index].fsStyle = BTNS_BUTTON;
1405 tbb[index].dwData = 0;
1406 tbb[index++].iString = 0;
1408 SendMessage(hwndTB, TB_SETIMAGELIST, 0, reinterpret_cast<LPARAM>(hToolbarImgList));
1409 SendMessage(hwndTB, TB_ADDBUTTONS, index, reinterpret_cast<LPARAM>(&tbb));
1410 SendMessage(hwndTB, TB_AUTOSIZE, 0, 0);
1411 ShowWindow(hwndTB, SW_SHOW);
1412 return true;
1415 void CMainWindow::SetSelectionImage( FileType ft, const std::wstring& path, const std::wstring& title )
1417 selectionPaths[ft] = path;
1418 selectionTitles[ft] = title;