Upgrade libgit2
[TortoiseGit.git] / src / TortoiseIDiff / PicWindow.cpp
blob619d04660fd4edaa7b5c1071b248a90e1afce06b
1 // TortoiseGitIDiff - an image diff viewer in TortoiseSVN
3 // Copyright (C) 2006-2016, 2018-2020 - TortoiseSVN
4 // Copyright (C) 2016, 2018-2020, 2023 - TortoiseGit
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 <shellapi.h>
22 #include <CommCtrl.h>
23 #include "PicWindow.h"
24 #include <math.h>
25 #include <memory>
26 #include "../Utils/DPIAware.h"
27 #include "../Utils/LoadIconEx.h"
28 #include "../Utils/Theme.h"
29 #include "../Utils/DarkModeHelper.h"
31 #pragma comment(lib, "Msimg32.lib")
32 #pragma comment(lib, "shell32.lib")
34 bool CPicWindow::RegisterAndCreateWindow(HWND hParent)
36 WNDCLASSEX wcx;
38 // Fill in the window class structure with default parameters
39 wcx.cbSize = sizeof(WNDCLASSEX);
40 wcx.style = CS_HREDRAW | CS_VREDRAW;
41 wcx.lpfnWndProc = CWindow::stWinMsgHandler;
42 wcx.cbClsExtra = 0;
43 wcx.cbWndExtra = 0;
44 wcx.hInstance = hResource;
45 wcx.hCursor = LoadCursor(nullptr, IDC_ARROW);
46 wcx.lpszClassName = L"TortoiseGitIDiffPicWindow";
47 wcx.hIcon = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_TORTOISEIDIFF), GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON));
48 wcx.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
49 wcx.lpszMenuName = MAKEINTRESOURCE(IDC_TORTOISEIDIFF);
50 wcx.hIconSm = LoadIconEx(wcx.hInstance, MAKEINTRESOURCE(IDI_TORTOISEIDIFF));
51 RegisterWindow(&wcx);
52 if (CreateEx(WS_EX_ACCEPTFILES | WS_EX_CLIENTEDGE, WS_CHILD | WS_HSCROLL | WS_VSCROLL | WS_VISIBLE, hParent))
54 ShowWindow(m_hwnd, SW_SHOW);
55 UpdateWindow(m_hwnd);
56 CreateButtons();
57 SetTheme(CTheme::Instance().IsDarkTheme());
58 return true;
60 return false;
63 void CPicWindow::PositionTrackBar()
65 const auto slider_width = CDPIAware::Instance().ScaleX(*this, SLIDER_WIDTH);
66 RECT rc;
67 GetClientRect(&rc);
68 HWND slider = m_AlphaSlider.GetWindow();
69 if (pSecondPic && m_blend == BlendType::Alpha)
71 MoveWindow(slider, 0, rc.top - CDPIAware::Instance().ScaleY(*this, 4) + slider_width, slider_width, rc.bottom - rc.top - slider_width + CDPIAware::Instance().ScaleX(*this, 8), true);
72 ShowWindow(slider, SW_SHOW);
73 MoveWindow(hwndAlphaToggleBtn, 0, rc.top - CDPIAware::Instance().ScaleY(*this, 4), slider_width, slider_width, true);
74 ShowWindow(hwndAlphaToggleBtn, SW_SHOW);
76 else
78 ShowWindow(slider, SW_HIDE);
79 ShowWindow(hwndAlphaToggleBtn, SW_HIDE);
83 LRESULT CALLBACK CPicWindow::WinMsgHandler(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
85 TRACKMOUSEEVENT mevt;
86 switch (uMsg)
88 case WM_CREATE:
90 // create a slider control
91 CreateTrackbar(hwnd);
92 ShowWindow(m_AlphaSlider.GetWindow(), SW_HIDE);
93 //Create the tooltips
94 TOOLINFO ti;
95 RECT rect; // for client area coordinates
97 hwndTT = CreateWindowEx(0,
98 TOOLTIPS_CLASS,
99 nullptr,
100 WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
101 CW_USEDEFAULT,
102 CW_USEDEFAULT,
103 CW_USEDEFAULT,
104 CW_USEDEFAULT,
105 hwnd,
106 nullptr,
107 hResource,
108 nullptr
111 SetWindowPos(hwndTT,
112 HWND_TOP,
117 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
119 ::GetClientRect(hwnd, &rect);
121 ti.cbSize = sizeof(TOOLINFO);
122 ti.uFlags = TTF_TRACK | TTF_ABSOLUTE;
123 ti.hwnd = hwnd;
124 ti.hinst = hResource;
125 ti.uId = 0;
126 ti.lpszText = LPSTR_TEXTCALLBACK;
127 // ToolTip control will cover the whole window
128 ti.rect.left = rect.left;
129 ti.rect.top = rect.top;
130 ti.rect.right = rect.right;
131 ti.rect.bottom = rect.bottom;
133 SendMessage(hwndTT, TTM_ADDTOOL, 0, reinterpret_cast<LPARAM>(&ti));
134 SendMessage(hwndTT, TTM_SETMAXTIPWIDTH, 0, 600);
135 nHSecondScrollPos = 0;
136 nVSecondScrollPos = 0;
137 m_themeCallbackId = CTheme::Instance().RegisterThemeChangeCallback(
138 [this]() {
139 SetTheme(CTheme::Instance().IsDarkTheme());
142 break;
143 case WM_SETFOCUS:
144 case WM_KILLFOCUS:
145 InvalidateRect(*this, nullptr, FALSE);
146 break;
147 case WM_ERASEBKGND:
148 return 1;
149 break;
150 case WM_PAINT:
151 Paint(hwnd);
152 break;
153 case WM_SIZE:
154 PositionTrackBar();
155 SetupScrollBars();
156 break;
157 case WM_VSCROLL:
158 if (pSecondPic && (reinterpret_cast<HWND>(lParam) == m_AlphaSlider.GetWindow()))
160 if (LOWORD(wParam) == TB_THUMBTRACK)
162 // while tracking, only redraw after 50 milliseconds
163 ::SetTimer(*this, TIMER_ALPHASLIDER, 50, nullptr);
165 else
166 SetBlendAlpha(m_blend, SendMessage(m_AlphaSlider.GetWindow(), TBM_GETPOS, 0, 0) / 16.0f);
168 else
170 UINT nPos = HIWORD(wParam);
171 bool bForceUpdate = false;
172 if (LOWORD(wParam) == SB_THUMBTRACK || LOWORD(wParam) == SB_THUMBPOSITION)
174 // Get true 32-bit scroll position
175 SCROLLINFO si;
176 si.cbSize = sizeof(SCROLLINFO);
177 si.fMask = SIF_TRACKPOS;
178 GetScrollInfo(*this, SB_VERT, &si);
179 nPos = si.nTrackPos;
180 bForceUpdate = true;
183 OnVScroll(LOWORD(wParam), nPos);
184 if (bLinkedPositions && pTheOtherPic)
186 pTheOtherPic->OnVScroll(LOWORD(wParam), nPos);
187 if (bForceUpdate)
188 ::UpdateWindow(*pTheOtherPic);
191 break;
192 case WM_HSCROLL:
194 UINT nPos = HIWORD(wParam);
195 bool bForceUpdate = false;
196 if (LOWORD(wParam) == SB_THUMBTRACK || LOWORD(wParam) == SB_THUMBPOSITION)
198 // Get true 32-bit scroll position
199 SCROLLINFO si;
200 si.cbSize = sizeof(SCROLLINFO);
201 si.fMask = SIF_TRACKPOS;
202 GetScrollInfo(*this, SB_VERT, &si);
203 nPos = si.nTrackPos;
204 bForceUpdate = true;
207 OnHScroll(LOWORD(wParam), nPos);
208 if (bLinkedPositions && pTheOtherPic)
210 pTheOtherPic->OnHScroll(LOWORD(wParam), nPos);
211 if (bForceUpdate)
212 ::UpdateWindow(*pTheOtherPic);
215 break;
216 case WM_MOUSEWHEEL:
218 OnMouseWheel(GET_KEYSTATE_WPARAM(wParam), GET_WHEEL_DELTA_WPARAM(wParam));
220 break;
221 case WM_MOUSEHWHEEL:
223 OnMouseWheel(GET_KEYSTATE_WPARAM(wParam)|MK_SHIFT, GET_WHEEL_DELTA_WPARAM(wParam));
225 break;
226 case WM_LBUTTONDOWN:
227 SetFocus(*this);
228 ptPanStart.x = GET_X_LPARAM(lParam);
229 ptPanStart.y = GET_Y_LPARAM(lParam);
230 startVScrollPos = nVScrollPos;
231 startHScrollPos = nHScrollPos;
232 startVSecondScrollPos = nVSecondScrollPos;
233 startHSecondScrollPos = nHSecondScrollPos;
234 bDragging = true;
235 SetCapture(*this);
236 break;
237 case WM_LBUTTONUP:
238 bDragging = false;
239 ReleaseCapture();
240 InvalidateRect(*this, nullptr, FALSE);
241 break;
242 case WM_MOUSELEAVE:
243 ptPanStart.x = -1;
244 ptPanStart.y = -1;
245 m_lastTTPos.x = 0;
246 m_lastTTPos.y = 0;
247 SendMessage(hwndTT, TTM_TRACKACTIVATE, FALSE, 0);
248 break;
249 case WM_MOUSEMOVE:
251 mevt.cbSize = sizeof(TRACKMOUSEEVENT);
252 mevt.dwFlags = TME_LEAVE;
253 mevt.dwHoverTime = HOVER_DEFAULT;
254 mevt.hwndTrack = *this;
255 ::TrackMouseEvent(&mevt);
256 POINT pt = { static_cast<int>(static_cast<short>(LOWORD(lParam))), static_cast<int>(static_cast<short>(HIWORD(lParam))) };
257 if (pt.y < CDPIAware::Instance().ScaleY(*this, HEADER_HEIGHT))
259 ClientToScreen(*this, &pt);
260 if ((abs(m_lastTTPos.x - pt.x) > 20)||(abs(m_lastTTPos.y - pt.y) > 10))
262 m_lastTTPos = pt;
263 pt.x += 15;
264 pt.y += 15;
265 SendMessage(hwndTT, TTM_TRACKPOSITION, 0, MAKELONG(pt.x, pt.y));
266 TOOLINFO ti = {0};
267 ti.cbSize = sizeof(TOOLINFO);
268 ti.hwnd = *this;
269 ti.uId = 0;
270 SendMessage(hwndTT, TTM_TRACKACTIVATE, TRUE, reinterpret_cast<LPARAM>(&ti));
273 else
275 SendMessage(hwndTT, TTM_TRACKACTIVATE, FALSE, 0);
276 m_lastTTPos.x = 0;
277 m_lastTTPos.y = 0;
279 if ((wParam & MK_LBUTTON) &&
280 (ptPanStart.x >= 0) &&
281 (ptPanStart.y >= 0))
283 // pan the image
284 int xPos = GET_X_LPARAM(lParam);
285 int yPos = GET_Y_LPARAM(lParam);
287 if (wParam & MK_CONTROL)
289 nHSecondScrollPos = startHSecondScrollPos + (ptPanStart.x - xPos);
290 nVSecondScrollPos = startVSecondScrollPos + (ptPanStart.y - yPos);
292 else if (wParam & MK_SHIFT)
294 nHScrollPos = startHScrollPos + (ptPanStart.x - xPos);
295 nVScrollPos = startVScrollPos + (ptPanStart.y - yPos);
297 else
299 nHSecondScrollPos = startHSecondScrollPos + (ptPanStart.x - xPos);
300 nVSecondScrollPos = startVSecondScrollPos + (ptPanStart.y - yPos);
301 nHScrollPos = startHScrollPos + (ptPanStart.x - xPos);
302 nVScrollPos = startVScrollPos + (ptPanStart.y - yPos);
303 if (!bLinkedPositions && pTheOtherPic)
305 // snap to the other picture borders
306 if (abs(nVScrollPos-pTheOtherPic->nVScrollPos) < 10)
307 nVScrollPos = pTheOtherPic->nVScrollPos;
308 if (abs(nHScrollPos-pTheOtherPic->nHScrollPos) < 10)
309 nHScrollPos = pTheOtherPic->nHScrollPos;
312 SetupScrollBars();
313 InvalidateRect(*this, nullptr, TRUE);
314 UpdateWindow(*this);
315 if (pTheOtherPic && (bLinkedPositions) && ((wParam & MK_SHIFT)==0))
317 pTheOtherPic->nHScrollPos = nHScrollPos;
318 pTheOtherPic->nVScrollPos = nVScrollPos;
319 pTheOtherPic->SetupScrollBars();
320 InvalidateRect(*pTheOtherPic, nullptr, TRUE);
321 UpdateWindow(*pTheOtherPic);
325 break;
326 case WM_SETCURSOR:
328 // we show a hand cursor if the image can be dragged,
329 // and a hand-down cursor if the image is currently dragged
330 if (*this == reinterpret_cast<HWND>(wParam) && LOWORD(lParam) == HTCLIENT)
332 RECT rect;
333 GetClientRect(&rect);
334 LONG width = picture.m_Width;
335 LONG height = picture.m_Height;
336 if (pSecondPic)
338 width = max(width, pSecondPic->m_Width);
339 height = max(height, pSecondPic->m_Height);
342 if ((GetKeyState(VK_LBUTTON)&0x8000)||(HIWORD(lParam) == WM_LBUTTONDOWN))
344 SetCursor(curHandDown);
346 else
348 SetCursor(curHand);
350 return TRUE;
352 return DefWindowProc(hwnd, uMsg, wParam, lParam);
354 break;
355 case WM_DROPFILES:
357 auto hDrop = reinterpret_cast<HDROP>(wParam);
358 wchar_t szFileName[MAX_PATH] = { 0 };
359 // we only use the first file dropped (if multiple files are dropped)
360 if (DragQueryFile(hDrop, 0, szFileName, _countof(szFileName)))
362 SetPic(szFileName, L"", bMainPic);
363 FitImageInWindow();
364 InvalidateRect(*this, nullptr, TRUE);
367 break;
368 case WM_COMMAND:
370 switch (LOWORD(wParam))
372 case LEFTBUTTON_ID:
374 PrevImage();
375 if (bLinkedPositions && pTheOtherPic)
376 pTheOtherPic->PrevImage();
377 return 0;
379 break;
380 case RIGHTBUTTON_ID:
382 NextImage();
383 if (bLinkedPositions && pTheOtherPic)
384 pTheOtherPic->NextImage();
385 return 0;
387 break;
388 case PLAYBUTTON_ID:
390 bPlaying = !bPlaying;
391 Animate(bPlaying);
392 if (bLinkedPositions && pTheOtherPic)
393 pTheOtherPic->Animate(bPlaying);
394 return 0;
396 break;
397 case ALPHATOGGLEBUTTON_ID:
399 WORD msg = HIWORD(wParam);
400 switch (msg)
402 case BN_DOUBLECLICKED:
404 SendMessage(hwndAlphaToggleBtn, BM_SETSTATE, 1, 0);
405 SetTimer(*this, ID_ALPHATOGGLETIMER, 1000, nullptr);
407 break;
408 case BN_CLICKED:
409 KillTimer(*this, ID_ALPHATOGGLETIMER);
410 ToggleAlpha();
411 break;
413 return 0;
415 break;
416 case BLENDALPHA_ID:
418 m_blend = BlendType::Alpha;
419 PositionTrackBar();
420 InvalidateRect(*this, nullptr, TRUE);
422 break;
423 case BLENDXOR_ID:
425 m_blend = BlendType::Xor;
426 PositionTrackBar();
427 InvalidateRect(*this, nullptr, TRUE);
429 break;
430 case SELECTBUTTON_ID:
432 SendMessage(GetParent(m_hwnd), WM_COMMAND, MAKEWPARAM(SELECTBUTTON_ID, SELECTBUTTON_ID), reinterpret_cast<LPARAM>(m_hwnd));
434 break;
437 break;
438 case WM_TIMER:
440 switch (wParam)
442 case ID_ANIMATIONTIMER:
444 nCurrentFrame++;
445 if (nCurrentFrame > picture.GetNumberOfFrames(0))
446 nCurrentFrame = 1;
447 long delay = picture.SetActiveFrame(nCurrentFrame);
448 delay = max(100l, delay);
449 SetTimer(*this, ID_ANIMATIONTIMER, delay, nullptr);
450 InvalidateRect(*this, nullptr, FALSE);
452 break;
453 case TIMER_ALPHASLIDER:
455 SetBlendAlpha(m_blend, SendMessage(m_AlphaSlider.GetWindow(), TBM_GETPOS, 0, 0)/16.0f);
456 KillTimer(*this, TIMER_ALPHASLIDER);
458 break;
459 case ID_ALPHATOGGLETIMER:
461 ToggleAlpha();
463 break;
466 break;
467 case WM_NOTIFY:
469 auto pNMHDR = reinterpret_cast<LPNMHDR>(lParam);
470 if (pNMHDR->code == TTN_GETDISPINFO)
472 if (pNMHDR->hwndFrom == m_AlphaSlider.GetWindow())
474 auto lpttt = reinterpret_cast<LPTOOLTIPTEXT>(lParam);
475 lpttt->hinst = hResource;
476 wchar_t stringbuf[MAX_PATH] = { 0 };
477 swprintf_s(stringbuf, L"%i%% alpha", static_cast<int>(SendMessage(m_AlphaSlider.GetWindow(),TBM_GETPOS, 0, 0) / 16.0f * 100.0f));
478 wcscpy_s(lpttt->lpszText, 80, stringbuf);
480 else if (pNMHDR->idFrom == reinterpret_cast<UINT_PTR>(hwndAlphaToggleBtn))
482 swprintf_s(m_wszTip, static_cast<const wchar_t*>(ResString(hResource, IDS_ALPHABUTTONTT)), static_cast<int>(SendMessage(m_AlphaSlider.GetWindow(),TBM_GETPOS, 0, 0) / 16.0f * 100.0f));
483 if (pNMHDR->code == TTN_NEEDTEXTW)
485 auto pTTTW = reinterpret_cast<NMTTDISPINFOW*>(pNMHDR);
486 pTTTW->lpszText = m_wszTip;
488 else
490 auto pTTTA = reinterpret_cast<NMTTDISPINFOA*>(pNMHDR);
491 pTTTA->lpszText = m_szTip;
492 ::WideCharToMultiByte(CP_ACP, 0, m_wszTip, -1, m_szTip, 8192, nullptr, nullptr);
495 else
497 BuildInfoString(m_wszTip, _countof(m_wszTip), true);
498 if (pNMHDR->code == TTN_NEEDTEXTW)
500 auto pTTTW = reinterpret_cast<NMTTDISPINFOW*>(pNMHDR);
501 pTTTW->lpszText = m_wszTip;
503 else
505 auto pTTTA = reinterpret_cast<NMTTDISPINFOA*>(pNMHDR);
506 pTTTA->lpszText = m_szTip;
507 ::WideCharToMultiByte(CP_ACP, 0, m_wszTip, -1, m_szTip, 8192, nullptr, nullptr);
512 break;
513 case WM_DESTROY:
514 bWindowClosed = TRUE;
515 break;
516 default:
517 return DefWindowProc(hwnd, uMsg, wParam, lParam);
520 return 0;
523 void CPicWindow::NextImage()
525 nCurrentDimension++;
526 if (nCurrentDimension > picture.GetNumberOfDimensions())
527 nCurrentDimension = picture.GetNumberOfDimensions();
528 nCurrentFrame++;
529 if (nCurrentFrame > picture.GetNumberOfFrames(0))
530 nCurrentFrame = picture.GetNumberOfFrames(0);
531 picture.SetActiveFrame(nCurrentFrame >= nCurrentDimension ? nCurrentFrame : nCurrentDimension);
532 InvalidateRect(*this, nullptr, FALSE);
533 PositionChildren();
536 void CPicWindow::PrevImage()
538 nCurrentDimension--;
539 if (nCurrentDimension < 1)
540 nCurrentDimension = 1;
541 nCurrentFrame--;
542 if (nCurrentFrame < 1)
543 nCurrentFrame = 1;
544 picture.SetActiveFrame(nCurrentFrame >= nCurrentDimension ? nCurrentFrame : nCurrentDimension);
545 InvalidateRect(*this, nullptr, FALSE);
546 PositionChildren();
549 void CPicWindow::Animate(bool bStart)
551 if (bStart)
553 SendMessage(hwndPlayBtn, BM_SETIMAGE, IMAGE_ICON, reinterpret_cast<LPARAM>(static_cast<HICON>(hStop)));
554 SetTimer(*this, ID_ANIMATIONTIMER, 0, nullptr);
556 else
558 SendMessage(hwndPlayBtn, BM_SETIMAGE, IMAGE_ICON, reinterpret_cast<LPARAM>(static_cast<HICON>(hPlay)));
559 KillTimer(*this, ID_ANIMATIONTIMER);
563 void CPicWindow::SetPic(const std::wstring& path, const std::wstring& title, bool bFirst)
565 bMainPic = bFirst;
566 picpath=path;pictitle=title;
567 picture.SetInterpolationMode(InterpolationModeHighQualityBicubic);
568 bValid = picture.Load(picpath);
569 nDimensions = picture.GetNumberOfDimensions();
570 if (nDimensions)
571 nFrames = picture.GetNumberOfFrames(0);
572 if (bValid)
574 picscale = 100;
575 PositionChildren();
576 InvalidateRect(*this, nullptr, FALSE);
580 void CPicWindow::DrawViewTitle(HDC hDC, RECT * rect)
582 const auto header_height = CDPIAware::Instance().ScaleY(*this, HEADER_HEIGHT);
583 auto hFont = CreateFont(-CDPIAware::Instance().PointsToPixelsY(*this, pSecondPic ? 8 : 10), 0, 0, 0, FW_DONTCARE, false, false, false, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH, L"MS Shell Dlg");
584 auto hFontOld = static_cast<HFONT>(SelectObject(hDC, hFont));
586 RECT textrect;
587 textrect.left = rect->left;
588 textrect.top = rect->top;
589 textrect.right = rect->right;
590 textrect.bottom = rect->top + header_height;
591 if (HasMultipleImages())
592 textrect.bottom += header_height;
594 COLORREF crBk, crFg;
595 crBk = CTheme::Instance().GetThemeColor(::GetSysColor(COLOR_SCROLLBAR));
596 crFg = CTheme::Instance().IsDarkTheme() ? CTheme::darkTextColor : GetSysColor(COLOR_WINDOWTEXT);
597 SetBkColor(hDC, crBk);
598 ::ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &textrect, nullptr, 0, nullptr);
600 if (GetFocus() == *this)
601 DrawEdge(hDC, &textrect, EDGE_BUMP, BF_RECT);
602 else
603 DrawEdge(hDC, &textrect, EDGE_ETCHED, BF_RECT);
605 SetTextColor(hDC, crFg);
607 // use the path if no title is set.
608 std::wstring* title = pictitle.empty() ? &picpath : &pictitle;
610 std::wstring realtitle = *title;
611 std::wstring imgnumstring;
613 if (HasMultipleImages())
615 wchar_t buf[MAX_PATH] = { 0 };
616 if (nFrames > 1)
617 swprintf_s(buf, static_cast<const wchar_t*>(ResString(hResource, IDS_DIMENSIONSANDFRAMES)), nCurrentFrame, nFrames);
618 else
619 swprintf_s(buf, static_cast<const wchar_t*>(ResString(hResource, IDS_DIMENSIONSANDFRAMES)), nCurrentDimension, nDimensions);
620 imgnumstring = buf;
623 SIZE stringsize;
624 if (GetTextExtentPoint32(hDC, realtitle.c_str(), static_cast<int>(realtitle.size()), &stringsize))
626 int nStringLength = stringsize.cx;
627 int texttop = pSecondPic ? textrect.top + (header_height /2) - stringsize.cy : textrect.top + (header_height /2) - stringsize.cy/2;
629 RECT drawRC = textrect;
630 drawRC.left = max(textrect.left + ((textrect.right - textrect.left) - nStringLength) / 2, 1l);
631 drawRC.top = texttop;
632 ::DrawText(hDC, realtitle.c_str(), (int)realtitle.size(), &drawRC, DT_HIDEPREFIX | DT_NOPREFIX | DT_SINGLELINE);
633 if (pSecondPic)
635 realtitle = (pictitle2.empty() ? picpath2 : pictitle2);
636 drawRC = textrect;
637 drawRC.left = max(textrect.left + ((textrect.right - textrect.left) - nStringLength) / 2, 1l);
638 drawRC.top = texttop + stringsize.cy;
639 ::DrawText(hDC, realtitle.c_str(), (int)realtitle.size(), &drawRC, DT_HIDEPREFIX | DT_NOPREFIX | DT_SINGLELINE);
642 if (HasMultipleImages())
644 if (GetTextExtentPoint32(hDC, imgnumstring.c_str(), static_cast<int>(imgnumstring.size()), &stringsize))
646 int nStringLength = stringsize.cx;
648 RECT drawRC = textrect;
649 drawRC.left = max(textrect.left + ((textrect.right - textrect.left) - nStringLength) / 2, 1l);
650 drawRC.top = textrect.top + header_height + (header_height / 2) - stringsize.cy / 2;
651 ::DrawText(hDC, imgnumstring.c_str(), (int)imgnumstring.size(), &drawRC, DT_HIDEPREFIX | DT_NOPREFIX | DT_SINGLELINE);
654 SelectObject(hDC, hFontOld);
655 DeleteObject(hFont);
658 void CPicWindow::SetupScrollBars()
660 RECT rect;
661 GetClientRect(&rect);
663 SCROLLINFO si = {sizeof(si)};
665 si.fMask = SIF_POS | SIF_PAGE | SIF_RANGE | SIF_DISABLENOSCROLL;
667 long width = picture.m_Width*picscale/100;
668 long height = picture.m_Height*picscale/100;
669 if (pSecondPic && pTheOtherPic)
671 width = max(width, pSecondPic->m_Width*pTheOtherPic->GetZoom()/100);
672 height = max(height, pSecondPic->m_Height*pTheOtherPic->GetZoom()/100);
675 bool bShowHScrollBar = (nHScrollPos > 0); // left of pic is left of window
676 bShowHScrollBar = bShowHScrollBar || (width-nHScrollPos > rect.right); // right of pic is outside right of window
677 bShowHScrollBar = bShowHScrollBar || (width+nHScrollPos > rect.right); // right of pic is outside right of window
678 bool bShowVScrollBar = (nVScrollPos > 0); // top of pic is above window
679 bShowVScrollBar = bShowVScrollBar || (height-nVScrollPos+rect.top > rect.bottom); // bottom of pic is below window
680 bShowVScrollBar = bShowVScrollBar || (height+nVScrollPos+rect.top > rect.bottom); // bottom of pic is below window
682 // if the image is smaller than the window, we don't need the scrollbars
683 ShowScrollBar(*this, SB_HORZ, bShowHScrollBar);
684 ShowScrollBar(*this, SB_VERT, bShowVScrollBar);
686 if (bShowVScrollBar)
688 si.nPos = nVScrollPos;
689 si.nPage = rect.bottom-rect.top;
690 if (height < rect.bottom-rect.top)
692 if (nVScrollPos > 0)
694 si.nMin = 0;
695 si.nMax = rect.bottom+nVScrollPos-rect.top;
697 else
699 si.nMin = nVScrollPos;
700 si.nMax = int(height);
703 else
705 if (nVScrollPos > 0)
707 si.nMin = 0;
708 si.nMax = int(max(height, rect.bottom+nVScrollPos-rect.top));
710 else
712 si.nMin = 0;
713 si.nMax = int(height-nVScrollPos);
716 SetScrollInfo(*this, SB_VERT, &si, TRUE);
719 if (bShowHScrollBar)
721 si.nPos = nHScrollPos;
722 si.nPage = rect.right-rect.left;
723 if (width < rect.right-rect.left)
725 if (nHScrollPos > 0)
727 si.nMin = 0;
728 si.nMax = rect.right+nHScrollPos-rect.left;
730 else
732 si.nMin = nHScrollPos;
733 si.nMax = int(width);
736 else
738 if (nHScrollPos > 0)
740 si.nMin = 0;
741 si.nMax = int(max(width, rect.right+nHScrollPos-rect.left));
743 else
745 si.nMin = 0;
746 si.nMax = int(width-nHScrollPos);
749 SetScrollInfo(*this, SB_HORZ, &si, TRUE);
752 PositionChildren();
755 void CPicWindow::OnVScroll(UINT nSBCode, UINT nPos)
757 RECT rect;
758 GetClientRect(&rect);
760 switch (nSBCode)
762 case SB_BOTTOM:
763 nVScrollPos = LONG(picture.GetHeight()*picscale/100);
764 break;
765 case SB_TOP:
766 nVScrollPos = 0;
767 break;
768 case SB_LINEDOWN:
769 nVScrollPos++;
770 break;
771 case SB_LINEUP:
772 nVScrollPos--;
773 break;
774 case SB_PAGEDOWN:
775 nVScrollPos += (rect.bottom-rect.top);
776 break;
777 case SB_PAGEUP:
778 nVScrollPos -= (rect.bottom-rect.top);
779 break;
780 case SB_THUMBPOSITION:
781 nVScrollPos = nPos;
782 break;
783 case SB_THUMBTRACK:
784 nVScrollPos = nPos;
785 break;
786 default:
787 return;
789 LONG height = LONG(picture.GetHeight()*picscale/100);
790 if (pSecondPic)
792 height = max(height, LONG(pSecondPic->GetHeight()*picscale/100));
793 nVSecondScrollPos = nVScrollPos;
795 SetupScrollBars();
796 PositionChildren();
797 InvalidateRect(*this, nullptr, TRUE);
800 void CPicWindow::OnHScroll(UINT nSBCode, UINT nPos)
802 RECT rect;
803 GetClientRect(&rect);
805 switch (nSBCode)
807 case SB_RIGHT:
808 nHScrollPos = LONG(picture.GetWidth()*picscale/100);
809 break;
810 case SB_LEFT:
811 nHScrollPos = 0;
812 break;
813 case SB_LINERIGHT:
814 nHScrollPos++;
815 break;
816 case SB_LINELEFT:
817 nHScrollPos--;
818 break;
819 case SB_PAGERIGHT:
820 nHScrollPos += (rect.right-rect.left);
821 break;
822 case SB_PAGELEFT:
823 nHScrollPos -= (rect.right-rect.left);
824 break;
825 case SB_THUMBPOSITION:
826 nHScrollPos = nPos;
827 break;
828 case SB_THUMBTRACK:
829 nHScrollPos = nPos;
830 break;
831 default:
832 return;
834 LONG width = LONG(picture.GetWidth()*picscale/100);
835 if (pSecondPic)
837 width = max(width, LONG(pSecondPic->GetWidth()*picscale/100));
838 nHSecondScrollPos = nHScrollPos;
840 SetupScrollBars();
841 PositionChildren();
842 InvalidateRect(*this, nullptr, TRUE);
845 void CPicWindow::OnMouseWheel(short fwKeys, short zDelta)
847 RECT rect;
848 GetClientRect(&rect);
849 LONG width = long(picture.m_Width*picscale/100);
850 LONG height = long(picture.m_Height*picscale/100);
851 if (pSecondPic)
853 width = max(width, long(pSecondPic->m_Width*picscale/100));
854 height = max(height, long(pSecondPic->m_Height*picscale/100));
856 if ((fwKeys & MK_SHIFT)&&(fwKeys & MK_CONTROL)&&(pSecondPic))
858 // ctrl+shift+wheel: change the alpha channel
859 float a = blendAlpha;
860 a -= float(zDelta)/120.0f/4.0f;
861 if (a < 0.0f)
862 a = 0.0f;
863 else if (a > 1.0f)
864 a = 1.0f;
865 SetBlendAlpha(m_blend, a);
867 else if (fwKeys & MK_SHIFT)
869 // shift means scrolling sideways
870 OnHScroll(SB_THUMBPOSITION, GetHPos()-zDelta);
871 if ((bLinkedPositions)&&(pTheOtherPic))
873 pTheOtherPic->OnHScroll(SB_THUMBPOSITION, pTheOtherPic->GetHPos()-zDelta);
876 else if (fwKeys & MK_CONTROL)
878 // control means adjusting the scale factor
879 Zoom(zDelta>0, true);
880 PositionChildren();
881 InvalidateRect(*this, nullptr, FALSE);
882 SetWindowPos(*this, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOREPOSITION | SWP_NOMOVE);
883 UpdateWindow(*this);
884 if ((bLinkedPositions || bOverlap) && pTheOtherPic)
886 pTheOtherPic->nHScrollPos = nHScrollPos;
887 pTheOtherPic->nVScrollPos = nVScrollPos;
888 pTheOtherPic->SetupScrollBars();
889 InvalidateRect(*pTheOtherPic, nullptr, TRUE);
890 UpdateWindow(*pTheOtherPic);
893 else
895 OnVScroll(SB_THUMBPOSITION, GetVPos()-zDelta);
896 if ((bLinkedPositions)&&(pTheOtherPic))
898 pTheOtherPic->OnVScroll(SB_THUMBPOSITION, pTheOtherPic->GetVPos()-zDelta);
903 void CPicWindow::GetClientRect(RECT * pRect)
905 ::GetClientRect(*this, pRect);
906 pRect->top += CDPIAware::Instance().ScaleY(*this, HEADER_HEIGHT);
907 if (HasMultipleImages())
909 pRect->top += CDPIAware::Instance().ScaleY(*this, HEADER_HEIGHT);
911 if (pSecondPic)
912 pRect->left += CDPIAware::Instance().ScaleX(*this, SLIDER_WIDTH);
915 void CPicWindow::GetClientRectWithScrollbars(RECT * pRect)
917 GetClientRect(pRect);
918 ::GetWindowRect(*this, pRect);
919 pRect->right = pRect->right-pRect->left;
920 pRect->bottom = pRect->bottom-pRect->top;
921 pRect->top = 0;
922 pRect->left = 0;
923 pRect->top += CDPIAware::Instance().ScaleY(*this, HEADER_HEIGHT);
924 if (HasMultipleImages())
926 pRect->top += CDPIAware::Instance().ScaleY(*this, HEADER_HEIGHT);
928 if (pSecondPic)
929 pRect->left += CDPIAware::Instance().ScaleX(*this, SLIDER_WIDTH);
933 void CPicWindow::SetZoom(int Zoom, bool centermouse, bool inzoom)
935 // Set the interpolation mode depending on zoom
936 int oldPicscale = picscale;
937 int oldOtherPicscale = picscale;
939 picture.SetInterpolationMode(InterpolationModeNearestNeighbor);
940 if (pSecondPic)
941 pSecondPic->SetInterpolationMode(InterpolationModeNearestNeighbor);
943 if ((oldPicscale == 0) || (Zoom == 0))
944 return;
946 picscale = Zoom;
948 if (pTheOtherPic && !inzoom)
950 if (bOverlap)
952 pTheOtherPic->SetZoom(Zoom, centermouse, true);
954 if (bFitHeights)
956 m_linkedHeight = 0;
957 pTheOtherPic->SetZoomToHeight(picture.m_Height*Zoom/100);
959 if (bFitWidths)
961 m_linkedWidth = 0;
962 pTheOtherPic->SetZoomToWidth(picture.m_Width*Zoom/100);
966 // adjust the scrollbar positions according to the new zoom and the
967 // mouse position: if possible, keep the pixel where the mouse pointer
968 // is at the same position after the zoom
969 if (!inzoom)
971 POINT cpos;
972 DWORD ptW = GetMessagePos();
973 cpos.x = GET_X_LPARAM(ptW);
974 cpos.y = GET_Y_LPARAM(ptW);
975 ScreenToClient(*this, &cpos);
976 RECT clientrect;
977 GetClientRect(&clientrect);
978 if ((PtInRect(&clientrect, cpos))&&(centermouse))
980 // the mouse pointer is over our window
981 nHScrollPos = (nHScrollPos + cpos.x)*Zoom/oldPicscale-cpos.x;
982 nVScrollPos = (nVScrollPos + cpos.y)*Zoom/oldPicscale-cpos.y;
983 if (pTheOtherPic && bMainPic)
985 int otherzoom = pTheOtherPic->GetZoom();
986 nHSecondScrollPos = (nHSecondScrollPos + cpos.x)*otherzoom/oldOtherPicscale-cpos.x;
987 nVSecondScrollPos = (nVSecondScrollPos + cpos.y)*otherzoom/oldOtherPicscale-cpos.y;
990 else
992 nHScrollPos = (nHScrollPos + ((clientrect.right-clientrect.left)/2))*Zoom/oldPicscale-((clientrect.right-clientrect.left)/2);
993 nVScrollPos = (nVScrollPos + ((clientrect.bottom-clientrect.top)/2))*Zoom/oldPicscale-((clientrect.bottom-clientrect.top)/2);
994 if (pTheOtherPic && bMainPic)
996 int otherzoom = pTheOtherPic->GetZoom();
997 nHSecondScrollPos = (nHSecondScrollPos + ((clientrect.right-clientrect.left)/2))*otherzoom/oldOtherPicscale-((clientrect.right-clientrect.left)/2);
998 nVSecondScrollPos = (nVSecondScrollPos + ((clientrect.bottom-clientrect.top)/2))*otherzoom/oldOtherPicscale-((clientrect.bottom-clientrect.top)/2);
1003 SetupScrollBars();
1004 PositionChildren();
1005 InvalidateRect(*this, nullptr, TRUE);
1008 void CPicWindow::Zoom(bool in, bool centermouse)
1010 int zoomFactor;
1012 // Find correct zoom factor and quantize picscale
1013 if (picscale % 10)
1015 picscale /= 10;
1016 picscale *= 10;
1017 if (!in)
1018 picscale += 10;
1021 if (!in && picscale <= 20)
1023 picscale = 10;
1024 zoomFactor = 0;
1026 else if ((in && picscale < 100) || (!in && picscale <= 100))
1028 zoomFactor = 10;
1030 else if ((in && picscale < 200) || (!in && picscale <= 200))
1032 zoomFactor = 20;
1034 else
1036 zoomFactor = 10;
1039 // Set zoom
1040 if (in)
1042 SetZoom(picscale+zoomFactor, centermouse);
1044 else
1046 SetZoom(picscale-zoomFactor, centermouse);
1050 void CPicWindow::FitImageInWindow()
1052 RECT rect;
1054 GetClientRectWithScrollbars(&rect);
1056 const auto border = CDPIAware::Instance().ScaleX(*this, 2);
1057 if (rect.right-rect.left)
1059 int Zoom = 100;
1060 if (((rect.right - rect.left) > picture.m_Width + border) && ((rect.bottom - rect.top) > picture.m_Height + border))
1062 // image is smaller than the window
1063 Zoom = 100;
1065 else
1067 // image is bigger than the window
1068 if (picture.m_Width > 0 && picture.m_Height > 0)
1070 int xscale = (rect.right - rect.left - border) * 100 / picture.m_Width;
1071 int yscale = (rect.bottom - rect.top - border) * 100 / picture.m_Height;
1072 Zoom = min(yscale, xscale);
1075 if (pSecondPic)
1077 if (((rect.right - rect.left) > pSecondPic->m_Width + border) && ((rect.bottom - rect.top) > pSecondPic->m_Height + border))
1079 // image is smaller than the window
1080 if (pTheOtherPic)
1081 pTheOtherPic->SetZoom(min(100, Zoom), false);
1083 else
1085 // image is bigger than the window
1086 int xscale = (rect.right - rect.left - border) * 100 / pSecondPic->m_Width;
1087 int yscale = (rect.bottom - rect.top - border) * 100 / pSecondPic->m_Height;
1088 if (pTheOtherPic)
1089 pTheOtherPic->SetZoom(min(yscale, xscale), false);
1091 nHSecondScrollPos = 0;
1092 nVSecondScrollPos = 0;
1094 SetZoom(Zoom, false);
1096 CenterImage();
1097 PositionChildren();
1098 InvalidateRect(*this, nullptr, TRUE);
1101 void CPicWindow::CenterImage()
1103 RECT rect;
1104 GetClientRectWithScrollbars(&rect);
1105 const auto border = CDPIAware::Instance().ScaleX(*this, 2);
1106 long width = picture.m_Width*picscale / 100 + border;
1107 long height = picture.m_Height*picscale / 100 + border;
1108 if (pSecondPic && pTheOtherPic)
1110 width = max(width, pSecondPic->m_Width*pTheOtherPic->GetZoom() / 100 + border);
1111 height = max(height, pSecondPic->m_Height*pTheOtherPic->GetZoom() / 100 + border);
1114 bool bPicWidthBigger = (int(width) > (rect.right-rect.left));
1115 bool bPicHeightBigger = (int(height) > (rect.bottom-rect.top));
1116 // set the scroll position so that the image is drawn centered in the window
1117 // if the window is bigger than the image
1118 if (!bPicWidthBigger)
1120 nHScrollPos = -((rect.right-rect.left+4)-int(width))/2;
1121 nHSecondScrollPos = nHScrollPos;
1123 if (!bPicHeightBigger)
1125 nVScrollPos = -((rect.bottom-rect.top+4)-int(height))/2;
1126 nVSecondScrollPos = nVScrollPos;
1128 SetupScrollBars();
1131 void CPicWindow::FitWidths(bool bFit)
1133 bFitWidths = bFit;
1135 SetZoom(GetZoom(), false);
1138 void CPicWindow::FitHeights(bool bFit)
1140 bFitHeights = bFit;
1142 SetZoom(GetZoom(), false);
1145 void CPicWindow::ShowPicWithBorder(HDC hdc, const RECT &bounds, CPicture &pic, int scale)
1147 ::SetBkColor(hdc, GetTransparentThemedColor());
1148 ::ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &bounds, nullptr, 0, nullptr);
1150 RECT picrect;
1151 picrect.left = bounds.left - nHScrollPos;
1152 picrect.top = bounds.top - nVScrollPos;
1153 if ((!bLinkedPositions || bOverlap) && (pTheOtherPic) && (&pic != &picture))
1155 picrect.left = bounds.left - nHSecondScrollPos;
1156 picrect.top = bounds.top - nVSecondScrollPos;
1158 picrect.right = (picrect.left + pic.m_Width * scale / 100);
1159 picrect.bottom = (picrect.top + pic.m_Height * scale / 100);
1161 if (bFitWidths && m_linkedWidth)
1162 picrect.right = picrect.left + m_linkedWidth;
1163 if (bFitHeights && m_linkedHeight)
1164 picrect.bottom = picrect.top + m_linkedHeight;
1166 pic.Show(hdc, picrect);
1168 const auto bordersize = CDPIAware::Instance().ScaleX(*this, 1);
1170 RECT border;
1171 border.left = picrect.left - bordersize;
1172 border.top = picrect.top - bordersize;
1173 border.right = picrect.right + bordersize;
1174 border.bottom = picrect.bottom + bordersize;
1176 HPEN hPen = CreatePen(PS_SOLID, 1, CTheme::Instance().GetThemeColor(GetSysColor(COLOR_3DDKSHADOW)));
1177 HPEN hOldPen = static_cast<HPEN>(SelectObject(hdc, hPen));
1178 MoveToEx(hdc, border.left, border.top, nullptr);
1179 LineTo(hdc, border.left, border.bottom);
1180 LineTo(hdc, border.right, border.bottom);
1181 LineTo(hdc, border.right, border.top);
1182 LineTo(hdc, border.left, border.top);
1183 SelectObject(hdc, hOldPen);
1184 DeleteObject(hPen);
1187 void CPicWindow::Paint(HWND hwnd)
1189 PAINTSTRUCT ps;
1190 HDC hdc;
1191 RECT rect, fullrect;
1193 GetUpdateRect(hwnd, &rect, FALSE);
1194 if (IsRectEmpty(&rect))
1195 return;
1197 const auto slider_width = CDPIAware::Instance().ScaleX(*this, SLIDER_WIDTH);
1198 const auto border = CDPIAware::Instance().ScaleX(*this, 4);
1199 ::GetClientRect(*this, &fullrect);
1200 hdc = BeginPaint(hwnd, &ps);
1202 // Exclude the alpha control and button
1203 if (pSecondPic && m_blend == BlendType::Alpha)
1204 ExcludeClipRect(hdc, 0, m_inforect.top - border, slider_width, m_inforect.bottom + border);
1206 CMyMemDC memDC(hdc);
1207 if (pSecondPic && m_blend != BlendType::Alpha)
1209 // erase the place where the alpha slider would be
1210 ::SetBkColor(memDC, GetTransparentThemedColor());
1211 RECT bounds = { 0, m_inforect.top - border, slider_width, m_inforect.bottom + border };
1212 ::ExtTextOut(memDC, 0, 0, ETO_OPAQUE, &bounds, nullptr, 0, nullptr);
1215 GetClientRect(&rect);
1216 if (bValid)
1218 ShowPicWithBorder(memDC, rect, picture, picscale);
1219 if (pSecondPic)
1221 HDC secondhdc = CreateCompatibleDC(hdc);
1222 HBITMAP hBitmap = CreateCompatibleBitmap(hdc, rect.right - rect.left, rect.bottom - rect.top);
1223 HBITMAP hOldBitmap = static_cast<HBITMAP>(SelectObject(secondhdc, hBitmap));
1224 SetWindowOrgEx(secondhdc, rect.left, rect.top, nullptr);
1226 if (pSecondPic && m_blend != BlendType::Alpha)
1228 // erase the place where the alpha slider would be
1229 ::SetBkColor(secondhdc, GetTransparentThemedColor());
1230 RECT bounds = { 0, m_inforect.top - border, slider_width, m_inforect.bottom + border };
1231 ::ExtTextOut(secondhdc, 0, 0, ETO_OPAQUE, &bounds, nullptr, 0, nullptr);
1233 if (pTheOtherPic)
1234 ShowPicWithBorder(secondhdc, rect, *pSecondPic, pTheOtherPic->GetZoom());
1236 if (m_blend == BlendType::Alpha)
1238 BLENDFUNCTION blender;
1239 blender.AlphaFormat = 0;
1240 blender.BlendFlags = 0;
1241 blender.BlendOp = AC_SRC_OVER;
1242 blender.SourceConstantAlpha = static_cast<BYTE>(blendAlpha * 255);
1243 AlphaBlend(memDC,
1244 rect.left,
1245 rect.top,
1246 rect.right-rect.left,
1247 rect.bottom-rect.top,
1248 secondhdc,
1249 rect.left,
1250 rect.top,
1251 rect.right-rect.left,
1252 rect.bottom-rect.top,
1253 blender);
1255 else if (m_blend == BlendType::Xor)
1257 BitBlt(memDC,
1258 rect.left,
1259 rect.top,
1260 rect.right-rect.left,
1261 rect.bottom-rect.top,
1262 secondhdc,
1263 rect.left,
1264 rect.top,
1265 //rect.right-rect.left,
1266 //rect.bottom-rect.top,
1267 SRCINVERT);
1268 InvertRect(memDC, &rect);
1270 SelectObject(secondhdc, hOldBitmap);
1271 DeleteObject(hBitmap);
1272 DeleteDC(secondhdc);
1274 else if (bDragging && pTheOtherPic && !bLinkedPositions)
1276 // when dragging, show lines indicating the position of the other image
1277 HPEN hPen = CreatePen(PS_SOLID, 1, CTheme::Instance().GetThemeColor(GetSysColor(/*COLOR_ACTIVEBORDER*/ COLOR_HIGHLIGHT)));
1278 HPEN hOldPen = static_cast<HPEN>(SelectObject(memDC, hPen));
1279 int xpos = rect.left - pTheOtherPic->nHScrollPos - 1;
1280 MoveToEx(memDC, xpos, rect.top, nullptr);
1281 LineTo(memDC, xpos, rect.bottom);
1282 xpos = rect.left - pTheOtherPic->nHScrollPos + pTheOtherPic->picture.m_Width*pTheOtherPic->GetZoom()/100 + 1;
1283 if (bFitWidths && m_linkedWidth)
1284 xpos = rect.left + pTheOtherPic->m_linkedWidth + 1;
1285 MoveToEx(memDC, xpos, rect.top, nullptr);
1286 LineTo(memDC, xpos, rect.bottom);
1288 int ypos = rect.top - pTheOtherPic->nVScrollPos - 1;
1289 MoveToEx(memDC, rect.left, ypos, nullptr);
1290 LineTo(memDC, rect.right, ypos);
1291 ypos = rect.top - pTheOtherPic->nVScrollPos + pTheOtherPic->picture.m_Height*pTheOtherPic->GetZoom()/100 + 1;
1292 if (bFitHeights && m_linkedHeight)
1293 ypos = rect.top - pTheOtherPic->m_linkedHeight + 1;
1294 MoveToEx(memDC, rect.left, ypos, nullptr);
1295 LineTo(memDC, rect.right, ypos);
1297 SelectObject(memDC, hOldPen);
1298 DeleteObject(hPen);
1301 int sliderwidth = 0;
1302 if (pSecondPic && m_blend == BlendType::Alpha)
1303 sliderwidth = slider_width;
1304 m_inforect.left = rect.left + border + sliderwidth;
1305 m_inforect.top = rect.top;
1306 m_inforect.right = rect.right+sliderwidth;
1307 m_inforect.bottom = rect.bottom;
1309 SetBkColor(memDC, GetTransparentThemedColor());
1310 if (bShowInfo)
1312 auto infostring = std::make_unique<wchar_t[]>(8192);
1313 BuildInfoString(infostring.get(), 8192, false);
1314 // set the font
1315 NONCLIENTMETRICS metrics = {0};
1316 metrics.cbSize = sizeof(NONCLIENTMETRICS);
1317 SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &metrics, FALSE);
1318 HFONT hFont = CreateFontIndirect(&metrics.lfStatusFont);
1319 HFONT hFontOld = static_cast<HFONT>(SelectObject(memDC, hFont));
1320 // find out how big the rectangle for the text has to be
1321 DrawText(memDC, infostring.get(), -1, &m_inforect, DT_EDITCONTROL | DT_EXPANDTABS | DT_LEFT | DT_VCENTER | DT_CALCRECT);
1323 // the text should be drawn with a four pixel offset to the window borders
1324 m_inforect.top = rect.bottom - (m_inforect.bottom-m_inforect.top) - border;
1325 m_inforect.bottom = rect.bottom-4;
1327 // first draw an edge rectangle
1328 RECT edgerect;
1329 edgerect.left = m_inforect.left - border;
1330 edgerect.top = m_inforect.top - border;
1331 edgerect.right = m_inforect.right + border;
1332 edgerect.bottom = m_inforect.bottom + border;
1333 ::ExtTextOut(memDC, 0, 0, ETO_OPAQUE, &edgerect, nullptr, 0, nullptr);
1334 DrawEdge(memDC, &edgerect, EDGE_BUMP, BF_RECT | BF_SOFT);
1336 SetTextColor(memDC, CTheme::Instance().IsDarkTheme() ? CTheme::darkTextColor : GetSysColor(COLOR_WINDOWTEXT));
1337 DrawText(memDC, infostring.get(), -1, &m_inforect, DT_EDITCONTROL | DT_EXPANDTABS | DT_LEFT | DT_VCENTER);
1338 SelectObject(memDC, hFontOld);
1339 DeleteObject(hFont);
1342 else
1344 SetBkColor(memDC, CTheme::Instance().IsDarkTheme() ? CTheme::darkBkColor : GetSysColor(COLOR_WINDOW));
1345 SetTextColor(memDC, CTheme::Instance().IsDarkTheme() ? CTheme::darkTextColor : GetSysColor(COLOR_WINDOWTEXT));
1346 ::ExtTextOut(memDC, 0, 0, ETO_OPAQUE, &rect, nullptr, 0, nullptr);
1347 SIZE stringsize;
1348 ResString str = ResString(hResource, IDS_INVALIDIMAGEINFO);
1350 // set the font
1351 NONCLIENTMETRICS metrics = {0};
1352 metrics.cbSize = sizeof(NONCLIENTMETRICS);
1353 SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &metrics, FALSE);
1354 HFONT hFont = CreateFontIndirect(&metrics.lfStatusFont);
1355 HFONT hFontOld = static_cast<HFONT>(SelectObject(memDC, hFont));
1357 if (GetTextExtentPoint32(memDC, str, static_cast<int>(wcslen(str)), &stringsize))
1359 int nStringLength = stringsize.cx;
1361 ExtTextOut(memDC,//
1362 max(rect.left + ((rect.right - rect.left) - nStringLength) / 2, 1l),
1363 rect.top + ((rect.bottom-rect.top) - stringsize.cy)/2,
1364 ETO_CLIPPED,
1365 &rect,
1366 str,
1367 static_cast<UINT>(wcslen(str)),
1368 nullptr);
1370 SelectObject(memDC, hFontOld);
1371 DeleteObject(hFont);
1373 DrawViewTitle(memDC, &fullrect);
1375 EndPaint(hwnd, &ps);
1378 bool CPicWindow::CreateButtons()
1380 // Ensure that the common control DLL is loaded.
1381 INITCOMMONCONTROLSEX icex;
1382 icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
1383 icex.dwICC = ICC_BAR_CLASSES | ICC_WIN95_CLASSES;
1384 InitCommonControlsEx(&icex);
1386 hwndLeftBtn = CreateWindowEx(0,
1387 L"BUTTON",
1388 static_cast<LPCWSTR>(nullptr),
1389 WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_ICON | BS_FLAT,
1390 0, 0, 0, 0,
1391 *this,
1392 reinterpret_cast<HMENU>(LEFTBUTTON_ID),
1393 hResource,
1394 nullptr);
1395 if (hwndLeftBtn == INVALID_HANDLE_VALUE)
1396 return false;
1397 int iconWidth = GetSystemMetrics(SM_CXSMICON);
1398 int iconHeight = GetSystemMetrics(SM_CYSMICON);
1399 hLeft = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_BACKWARD), iconWidth, iconHeight);
1400 SendMessage(hwndLeftBtn, BM_SETIMAGE, IMAGE_ICON, reinterpret_cast<LPARAM>(static_cast<HICON>(hLeft)));
1401 hwndRightBtn = CreateWindowEx(0,
1402 L"BUTTON",
1403 static_cast<LPCWSTR>(nullptr),
1404 WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_ICON | BS_FLAT,
1405 0, 0, 0, 0,
1406 *this,
1407 reinterpret_cast<HMENU>(RIGHTBUTTON_ID),
1408 hResource,
1409 nullptr);
1410 if (hwndRightBtn == INVALID_HANDLE_VALUE)
1411 return false;
1412 hRight = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_FORWARD), iconWidth, iconHeight);
1413 SendMessage(hwndRightBtn, BM_SETIMAGE, IMAGE_ICON, reinterpret_cast<LPARAM>(static_cast<HICON>(hRight)));
1414 hwndPlayBtn = CreateWindowEx(0,
1415 L"BUTTON",
1416 static_cast<LPCWSTR>(nullptr),
1417 WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_ICON | BS_FLAT,
1418 0, 0, 0, 0,
1419 *this,
1420 reinterpret_cast<HMENU>(PLAYBUTTON_ID),
1421 hResource,
1422 nullptr);
1423 if (hwndPlayBtn == INVALID_HANDLE_VALUE)
1424 return false;
1425 hPlay = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_START), iconWidth, iconHeight);
1426 hStop = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_STOP), iconWidth, iconHeight);
1427 SendMessage(hwndPlayBtn, BM_SETIMAGE, IMAGE_ICON, reinterpret_cast<LPARAM>(static_cast<HICON>(hPlay)));
1428 hwndAlphaToggleBtn = CreateWindowEx(0,
1429 L"BUTTON",
1430 static_cast<LPCWSTR>(nullptr),
1431 WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_ICON | BS_FLAT | BS_NOTIFY | BS_PUSHLIKE,
1432 0, 0, 0, 0,
1433 *this,
1434 reinterpret_cast<HMENU>(ALPHATOGGLEBUTTON_ID),
1435 hResource,
1436 nullptr);
1437 if (hwndAlphaToggleBtn == INVALID_HANDLE_VALUE)
1438 return false;
1439 hAlphaToggle = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_ALPHATOGGLE), iconWidth, iconHeight);
1440 SendMessage(hwndAlphaToggleBtn, BM_SETIMAGE, IMAGE_ICON, reinterpret_cast<LPARAM>(static_cast<HICON>(hAlphaToggle)));
1442 TOOLINFO ti = {0};
1443 ti.cbSize = sizeof(TOOLINFO);
1444 ti.uFlags = TTF_IDISHWND|TTF_SUBCLASS;
1445 ti.hwnd = *this;
1446 ti.hinst = hResource;
1447 ti.uId = reinterpret_cast<UINT_PTR>(hwndAlphaToggleBtn);
1448 ti.lpszText = LPSTR_TEXTCALLBACK;
1449 // ToolTip control will cover the whole window
1450 ti.rect.left = 0;
1451 ti.rect.top = 0;
1452 ti.rect.right = 0;
1453 ti.rect.bottom = 0;
1454 SendMessage(hwndTT, TTM_ADDTOOL, 0, reinterpret_cast<LPARAM>(&ti));
1455 ResString sSelect(hResource, IDS_SELECT);
1456 hwndSelectBtn = CreateWindowEx(0,
1457 L"BUTTON",
1458 sSelect,
1459 WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
1460 0, 0, 0, 0,
1461 *this,
1462 reinterpret_cast<HMENU>(SELECTBUTTON_ID),
1463 hResource,
1464 nullptr);
1465 if (hwndPlayBtn == INVALID_HANDLE_VALUE)
1466 return false;
1468 return true;
1471 void CPicWindow::PositionChildren()
1473 const auto header_height = CDPIAware::Instance().ScaleY(*this, HEADER_HEIGHT);
1474 const auto selBorder = CDPIAware::Instance().ScaleX(*this, 100);
1475 RECT rect;
1476 ::GetClientRect(*this, &rect);
1477 if (HasMultipleImages())
1479 int iconWidth = GetSystemMetrics(SM_CXSMICON);
1480 int iconHeight = GetSystemMetrics(SM_CYSMICON);
1481 SetWindowPos(hwndLeftBtn, HWND_TOP, rect.left + iconWidth / 4, rect.top + header_height + (header_height -iconHeight)/2, iconWidth, iconHeight, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
1482 SetWindowPos(hwndRightBtn, HWND_TOP, rect.left + iconWidth + iconWidth / 2, rect.top + header_height + (header_height - iconHeight) / 2, iconWidth, iconHeight, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
1483 if (nFrames > 1)
1484 SetWindowPos(hwndPlayBtn, HWND_TOP, rect.left + iconWidth * 2 + iconWidth / 2, rect.top + header_height + (header_height - iconHeight) / 2, iconWidth, iconHeight, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
1485 else
1486 ShowWindow(hwndPlayBtn, SW_HIDE);
1488 else
1490 ShowWindow(hwndLeftBtn, SW_HIDE);
1491 ShowWindow(hwndRightBtn, SW_HIDE);
1492 ShowWindow(hwndPlayBtn, SW_HIDE);
1494 if (bSelectionMode)
1495 SetWindowPos(hwndSelectBtn, HWND_TOP, rect.right - selBorder, rect.bottom - header_height, selBorder, header_height, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
1496 else
1497 ShowWindow(hwndSelectBtn, SW_HIDE);
1498 PositionTrackBar();
1501 bool CPicWindow::HasMultipleImages()
1503 return (((nDimensions > 1) || (nFrames > 1)) && (!pSecondPic));
1506 void CPicWindow::CreateTrackbar(HWND hwndParent)
1508 hwndTrack = CreateWindowEx(
1509 0, // no extended styles
1510 TRACKBAR_CLASS, // class name
1511 L"Trackbar Control", // title (caption)
1512 WS_CHILD | WS_VISIBLE | TBS_VERT | TBS_TOOLTIPS | TBS_AUTOTICKS, // style
1513 10, 10, // position
1514 200, 30, // size
1515 hwndParent, // parent window
1516 reinterpret_cast<HMENU>(TRACKBAR_ID),// control identifier
1517 hInst, // instance
1518 nullptr // no WM_CREATE parameter
1521 SendMessage(hwndTrack, TBM_SETRANGE,
1522 TRUE, // redraw flag
1523 MAKELONG(0, 16)); // min. & max. positions
1524 SendMessage(hwndTrack, TBM_SETTIPSIDE,
1525 TBTS_TOP,
1528 m_AlphaSlider.ConvertTrackbarToNice(hwndTrack);
1531 void CPicWindow::BuildInfoString(wchar_t* buf, int size, bool bTooltip)
1533 // Unfortunately, we need two different strings for the tooltip
1534 // and the info box. Because the tooltips use a different tab size
1535 // than ExtTextOut(), and to keep the output aligned we therefore
1536 // need two different strings.
1537 // Note: some translations could end up with two identical strings, but
1538 // in English we need two - even if we wouldn't need two in English, some
1539 // translation might then need two again.
1540 if (pSecondPic && pTheOtherPic)
1542 swprintf_s(buf, size,
1543 static_cast<const wchar_t*>(ResString(hResource, bTooltip ? IDS_DUALIMAGEINFOTT : IDS_DUALIMAGEINFO)),
1544 picture.GetFileSizeAsText().c_str(), picture.GetFileSizeAsText(false).c_str(),
1545 picture.m_Width, picture.m_Height,
1546 picture.GetHorizontalResolution(), picture.GetVerticalResolution(),
1547 picture.m_ColorDepth,
1548 static_cast<UINT>(GetZoom()),
1549 pSecondPic->GetFileSizeAsText().c_str(), pSecondPic->GetFileSizeAsText(false).c_str(),
1550 pSecondPic->m_Width, pSecondPic->m_Height,
1551 pSecondPic->GetHorizontalResolution(), pSecondPic->GetVerticalResolution(),
1552 pSecondPic->m_ColorDepth,
1553 static_cast<UINT>(pTheOtherPic->GetZoom()));
1555 else
1557 swprintf_s(buf, size,
1558 static_cast<const wchar_t*>(ResString(hResource, bTooltip ? IDS_IMAGEINFOTT : IDS_IMAGEINFO)),
1559 picture.GetFileSizeAsText().c_str(), picture.GetFileSizeAsText(false).c_str(),
1560 picture.m_Width, picture.m_Height,
1561 picture.GetHorizontalResolution(), picture.GetVerticalResolution(),
1562 picture.m_ColorDepth,
1563 static_cast<UINT>(GetZoom()));
1567 void CPicWindow::SetZoomToWidth( long width )
1569 m_linkedWidth = width;
1570 if (picture.m_Width)
1572 int zoom = width*100/picture.m_Width;
1573 SetZoom(zoom, false, true);
1577 void CPicWindow::SetZoomToHeight( long height )
1579 m_linkedHeight = height;
1580 if (picture.m_Height)
1582 int zoom = height*100/picture.m_Height;
1583 SetZoom(zoom, false, true);
1587 void CPicWindow::SetTheme(bool bDark)
1589 if (bDark)
1591 DarkModeHelper::Instance().AllowDarkModeForWindow(*this, TRUE);
1592 CTheme::Instance().SetThemeForDialog(*this, true);
1593 SetClassLongPtr(*this, GCLP_HBRBACKGROUND, reinterpret_cast<LONG_PTR>(GetStockObject(BLACK_BRUSH)));
1594 if (FAILED(SetWindowTheme(*this, L"DarkMode_Explorer", nullptr)))
1595 SetWindowTheme(*this, L"Explorer", nullptr);
1596 DarkModeHelper::Instance().AllowDarkModeForWindow(hwndTrack, TRUE);
1597 SetClassLongPtr(hwndTrack, GCLP_HBRBACKGROUND, reinterpret_cast<LONG_PTR>(GetStockObject(BLACK_BRUSH)));
1598 if (FAILED(SetWindowTheme(hwndTrack, L"DarkMode_Explorer", nullptr)))
1599 SetWindowTheme(hwndTrack, L"Explorer", nullptr);
1600 if (FAILED(SetWindowTheme(hwndTT, L"DarkMode_Explorer", nullptr)))
1601 SetWindowTheme(hwndTT, L"Explorer", nullptr);
1603 if (FAILED(SetWindowTheme(hwndLeftBtn, L"DarkMode_Explorer", nullptr)))
1604 SetWindowTheme(hwndLeftBtn, L"Explorer", nullptr);
1605 if (FAILED(SetWindowTheme(hwndRightBtn, L"DarkMode_Explorer", nullptr)))
1606 SetWindowTheme(hwndRightBtn, L"Explorer", nullptr);
1607 if (FAILED(SetWindowTheme(hwndPlayBtn, L"DarkMode_Explorer", nullptr)))
1608 SetWindowTheme(hwndPlayBtn, L"Explorer", nullptr);
1609 if (FAILED(SetWindowTheme(hwndSelectBtn, L"DarkMode_Explorer", nullptr)))
1610 SetWindowTheme(hwndSelectBtn, L"Explorer", nullptr);
1611 if (FAILED(SetWindowTheme(hwndAlphaToggleBtn, L"DarkMode_Explorer", nullptr)))
1612 SetWindowTheme(hwndAlphaToggleBtn, L"Explorer", nullptr);
1614 else
1616 DarkModeHelper::Instance().AllowDarkModeForWindow(*this, FALSE);
1617 CTheme::Instance().SetThemeForDialog(*this, false);
1618 SetClassLongPtr(*this, GCLP_HBRBACKGROUND, reinterpret_cast<LONG_PTR>(GetSysColorBrush(COLOR_3DFACE)));
1619 SetWindowTheme(*this, L"Explorer", nullptr);
1620 DarkModeHelper::Instance().AllowDarkModeForWindow(hwndTrack, FALSE);
1621 SetClassLongPtr(hwndTrack, GCLP_HBRBACKGROUND, reinterpret_cast<LONG_PTR>(GetSysColorBrush(COLOR_3DFACE)));
1622 SetWindowTheme(hwndTrack, L"Explorer", nullptr);
1623 SetWindowTheme(hwndTT, L"Explorer", nullptr);
1625 SetWindowTheme(hwndLeftBtn, L"Explorer", nullptr);
1626 SetWindowTheme(hwndRightBtn, L"Explorer", nullptr);
1627 SetWindowTheme(hwndPlayBtn, L"Explorer", nullptr);
1628 SetWindowTheme(hwndSelectBtn, L"Explorer", nullptr);
1629 SetWindowTheme(hwndAlphaToggleBtn, L"Explorer", nullptr);
1632 InvalidateRect(*this, nullptr, true);
1635 COLORREF CPicWindow::GetTransparentThemedColor()
1637 if (CTheme::Instance().IsDarkTheme())
1639 if (transparentColor == 0xFFFFFF)
1640 return CTheme::darkBkColor;
1642 return CTheme::Instance().GetThemeColor(transparentColor);