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.
23 #include "PicWindow.h"
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
)
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
;
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
));
52 if (CreateEx(WS_EX_ACCEPTFILES
| WS_EX_CLIENTEDGE
, WS_CHILD
| WS_HSCROLL
| WS_VSCROLL
| WS_VISIBLE
, hParent
))
54 ShowWindow(m_hwnd
, SW_SHOW
);
57 SetTheme(CTheme::Instance().IsDarkTheme());
63 void CPicWindow::PositionTrackBar()
65 const auto slider_width
= CDPIAware::Instance().ScaleX(*this, SLIDER_WIDTH
);
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
);
78 ShowWindow(slider
, SW_HIDE
);
79 ShowWindow(hwndAlphaToggleBtn
, SW_HIDE
);
83 LRESULT CALLBACK
CPicWindow::WinMsgHandler(HWND hwnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
90 // create a slider control
92 ShowWindow(m_AlphaSlider
.GetWindow(), SW_HIDE
);
95 RECT rect
; // for client area coordinates
97 hwndTT
= CreateWindowEx(0,
100 WS_POPUP
| TTS_NOPREFIX
| TTS_ALWAYSTIP
,
117 SWP_NOMOVE
| SWP_NOSIZE
| SWP_NOACTIVATE
);
119 ::GetClientRect(hwnd
, &rect
);
121 ti
.cbSize
= sizeof(TOOLINFO
);
122 ti
.uFlags
= TTF_TRACK
| TTF_ABSOLUTE
;
124 ti
.hinst
= hResource
;
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(
139 SetTheme(CTheme::Instance().IsDarkTheme());
145 InvalidateRect(*this, nullptr, FALSE
);
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);
166 SetBlendAlpha(m_blend
, SendMessage(m_AlphaSlider
.GetWindow(), TBM_GETPOS
, 0, 0) / 16.0f
);
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
176 si
.cbSize
= sizeof(SCROLLINFO
);
177 si
.fMask
= SIF_TRACKPOS
;
178 GetScrollInfo(*this, SB_VERT
, &si
);
183 OnVScroll(LOWORD(wParam
), nPos
);
184 if (bLinkedPositions
&& pTheOtherPic
)
186 pTheOtherPic
->OnVScroll(LOWORD(wParam
), nPos
);
188 ::UpdateWindow(*pTheOtherPic
);
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
200 si
.cbSize
= sizeof(SCROLLINFO
);
201 si
.fMask
= SIF_TRACKPOS
;
202 GetScrollInfo(*this, SB_VERT
, &si
);
207 OnHScroll(LOWORD(wParam
), nPos
);
208 if (bLinkedPositions
&& pTheOtherPic
)
210 pTheOtherPic
->OnHScroll(LOWORD(wParam
), nPos
);
212 ::UpdateWindow(*pTheOtherPic
);
218 OnMouseWheel(GET_KEYSTATE_WPARAM(wParam
), GET_WHEEL_DELTA_WPARAM(wParam
));
223 OnMouseWheel(GET_KEYSTATE_WPARAM(wParam
)|MK_SHIFT
, GET_WHEEL_DELTA_WPARAM(wParam
));
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
;
240 InvalidateRect(*this, nullptr, FALSE
);
247 SendMessage(hwndTT
, TTM_TRACKACTIVATE
, FALSE
, 0);
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))
265 SendMessage(hwndTT
, TTM_TRACKPOSITION
, 0, MAKELONG(pt
.x
, pt
.y
));
267 ti
.cbSize
= sizeof(TOOLINFO
);
270 SendMessage(hwndTT
, TTM_TRACKACTIVATE
, TRUE
, reinterpret_cast<LPARAM
>(&ti
));
275 SendMessage(hwndTT
, TTM_TRACKACTIVATE
, FALSE
, 0);
279 if ((wParam
& MK_LBUTTON
) &&
280 (ptPanStart
.x
>= 0) &&
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
);
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
;
313 InvalidateRect(*this, nullptr, TRUE
);
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
);
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
)
333 GetClientRect(&rect
);
334 LONG width
= picture
.m_Width
;
335 LONG height
= picture
.m_Height
;
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
);
352 return DefWindowProc(hwnd
, uMsg
, wParam
, lParam
);
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
);
364 InvalidateRect(*this, nullptr, TRUE
);
370 switch (LOWORD(wParam
))
375 if (bLinkedPositions
&& pTheOtherPic
)
376 pTheOtherPic
->PrevImage();
383 if (bLinkedPositions
&& pTheOtherPic
)
384 pTheOtherPic
->NextImage();
390 bPlaying
= !bPlaying
;
392 if (bLinkedPositions
&& pTheOtherPic
)
393 pTheOtherPic
->Animate(bPlaying
);
397 case ALPHATOGGLEBUTTON_ID
:
399 WORD msg
= HIWORD(wParam
);
402 case BN_DOUBLECLICKED
:
404 SendMessage(hwndAlphaToggleBtn
, BM_SETSTATE
, 1, 0);
405 SetTimer(*this, ID_ALPHATOGGLETIMER
, 1000, nullptr);
409 KillTimer(*this, ID_ALPHATOGGLETIMER
);
418 m_blend
= BlendType::Alpha
;
420 InvalidateRect(*this, nullptr, TRUE
);
425 m_blend
= BlendType::Xor
;
427 InvalidateRect(*this, nullptr, TRUE
);
430 case SELECTBUTTON_ID
:
432 SendMessage(GetParent(m_hwnd
), WM_COMMAND
, MAKEWPARAM(SELECTBUTTON_ID
, SELECTBUTTON_ID
), reinterpret_cast<LPARAM
>(m_hwnd
));
442 case ID_ANIMATIONTIMER
:
445 if (nCurrentFrame
> picture
.GetNumberOfFrames(0))
447 long delay
= picture
.SetActiveFrame(nCurrentFrame
);
448 delay
= max(100l, delay
);
449 SetTimer(*this, ID_ANIMATIONTIMER
, delay
, nullptr);
450 InvalidateRect(*this, nullptr, FALSE
);
453 case TIMER_ALPHASLIDER
:
455 SetBlendAlpha(m_blend
, SendMessage(m_AlphaSlider
.GetWindow(), TBM_GETPOS
, 0, 0)/16.0f
);
456 KillTimer(*this, TIMER_ALPHASLIDER
);
459 case ID_ALPHATOGGLETIMER
:
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
;
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);
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
;
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);
514 bWindowClosed
= TRUE
;
517 return DefWindowProc(hwnd
, uMsg
, wParam
, lParam
);
523 void CPicWindow::NextImage()
526 if (nCurrentDimension
> picture
.GetNumberOfDimensions())
527 nCurrentDimension
= picture
.GetNumberOfDimensions();
529 if (nCurrentFrame
> picture
.GetNumberOfFrames(0))
530 nCurrentFrame
= picture
.GetNumberOfFrames(0);
531 picture
.SetActiveFrame(nCurrentFrame
>= nCurrentDimension
? nCurrentFrame
: nCurrentDimension
);
532 InvalidateRect(*this, nullptr, FALSE
);
536 void CPicWindow::PrevImage()
539 if (nCurrentDimension
< 1)
540 nCurrentDimension
= 1;
542 if (nCurrentFrame
< 1)
544 picture
.SetActiveFrame(nCurrentFrame
>= nCurrentDimension
? nCurrentFrame
: nCurrentDimension
);
545 InvalidateRect(*this, nullptr, FALSE
);
549 void CPicWindow::Animate(bool bStart
)
553 SendMessage(hwndPlayBtn
, BM_SETIMAGE
, IMAGE_ICON
, reinterpret_cast<LPARAM
>(static_cast<HICON
>(hStop
)));
554 SetTimer(*this, ID_ANIMATIONTIMER
, 0, nullptr);
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
)
566 picpath
=path
;pictitle
=title
;
567 picture
.SetInterpolationMode(InterpolationModeHighQualityBicubic
);
568 bValid
= picture
.Load(picpath
);
569 nDimensions
= picture
.GetNumberOfDimensions();
571 nFrames
= picture
.GetNumberOfFrames(0);
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
));
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
;
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
);
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 };
617 swprintf_s(buf
, static_cast<const wchar_t*>(ResString(hResource
, IDS_DIMENSIONSANDFRAMES
)), nCurrentFrame
, nFrames
);
619 swprintf_s(buf
, static_cast<const wchar_t*>(ResString(hResource
, IDS_DIMENSIONSANDFRAMES
)), nCurrentDimension
, nDimensions
);
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
);
635 realtitle
= (pictitle2
.empty() ? picpath2
: pictitle2
);
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
);
658 void CPicWindow::SetupScrollBars()
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
);
688 si
.nPos
= nVScrollPos
;
689 si
.nPage
= rect
.bottom
-rect
.top
;
690 if (height
< rect
.bottom
-rect
.top
)
695 si
.nMax
= rect
.bottom
+nVScrollPos
-rect
.top
;
699 si
.nMin
= nVScrollPos
;
700 si
.nMax
= int(height
);
708 si
.nMax
= int(max(height
, rect
.bottom
+nVScrollPos
-rect
.top
));
713 si
.nMax
= int(height
-nVScrollPos
);
716 SetScrollInfo(*this, SB_VERT
, &si
, TRUE
);
721 si
.nPos
= nHScrollPos
;
722 si
.nPage
= rect
.right
-rect
.left
;
723 if (width
< rect
.right
-rect
.left
)
728 si
.nMax
= rect
.right
+nHScrollPos
-rect
.left
;
732 si
.nMin
= nHScrollPos
;
733 si
.nMax
= int(width
);
741 si
.nMax
= int(max(width
, rect
.right
+nHScrollPos
-rect
.left
));
746 si
.nMax
= int(width
-nHScrollPos
);
749 SetScrollInfo(*this, SB_HORZ
, &si
, TRUE
);
755 void CPicWindow::OnVScroll(UINT nSBCode
, UINT nPos
)
758 GetClientRect(&rect
);
763 nVScrollPos
= LONG(picture
.GetHeight()*picscale
/100);
775 nVScrollPos
+= (rect
.bottom
-rect
.top
);
778 nVScrollPos
-= (rect
.bottom
-rect
.top
);
780 case SB_THUMBPOSITION
:
789 LONG height
= LONG(picture
.GetHeight()*picscale
/100);
792 height
= max(height
, LONG(pSecondPic
->GetHeight()*picscale
/100));
793 nVSecondScrollPos
= nVScrollPos
;
797 InvalidateRect(*this, nullptr, TRUE
);
800 void CPicWindow::OnHScroll(UINT nSBCode
, UINT nPos
)
803 GetClientRect(&rect
);
808 nHScrollPos
= LONG(picture
.GetWidth()*picscale
/100);
820 nHScrollPos
+= (rect
.right
-rect
.left
);
823 nHScrollPos
-= (rect
.right
-rect
.left
);
825 case SB_THUMBPOSITION
:
834 LONG width
= LONG(picture
.GetWidth()*picscale
/100);
837 width
= max(width
, LONG(pSecondPic
->GetWidth()*picscale
/100));
838 nHSecondScrollPos
= nHScrollPos
;
842 InvalidateRect(*this, nullptr, TRUE
);
845 void CPicWindow::OnMouseWheel(short fwKeys
, short zDelta
)
848 GetClientRect(&rect
);
849 LONG width
= long(picture
.m_Width
*picscale
/100);
850 LONG height
= long(picture
.m_Height
*picscale
/100);
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
;
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);
881 InvalidateRect(*this, nullptr, FALSE
);
882 SetWindowPos(*this, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED
| SWP_NOSIZE
| SWP_NOREPOSITION
| SWP_NOMOVE
);
884 if ((bLinkedPositions
|| bOverlap
) && pTheOtherPic
)
886 pTheOtherPic
->nHScrollPos
= nHScrollPos
;
887 pTheOtherPic
->nVScrollPos
= nVScrollPos
;
888 pTheOtherPic
->SetupScrollBars();
889 InvalidateRect(*pTheOtherPic
, nullptr, TRUE
);
890 UpdateWindow(*pTheOtherPic
);
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
);
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
;
923 pRect
->top
+= CDPIAware::Instance().ScaleY(*this, HEADER_HEIGHT
);
924 if (HasMultipleImages())
926 pRect
->top
+= CDPIAware::Instance().ScaleY(*this, HEADER_HEIGHT
);
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
);
941 pSecondPic
->SetInterpolationMode(InterpolationModeNearestNeighbor
);
943 if ((oldPicscale
== 0) || (Zoom
== 0))
948 if (pTheOtherPic
&& !inzoom
)
952 pTheOtherPic
->SetZoom(Zoom
, centermouse
, true);
957 pTheOtherPic
->SetZoomToHeight(picture
.m_Height
*Zoom
/100);
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
972 DWORD ptW
= GetMessagePos();
973 cpos
.x
= GET_X_LPARAM(ptW
);
974 cpos
.y
= GET_Y_LPARAM(ptW
);
975 ScreenToClient(*this, &cpos
);
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
;
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);
1005 InvalidateRect(*this, nullptr, TRUE
);
1008 void CPicWindow::Zoom(bool in
, bool centermouse
)
1012 // Find correct zoom factor and quantize picscale
1021 if (!in
&& picscale
<= 20)
1026 else if ((in
&& picscale
< 100) || (!in
&& picscale
<= 100))
1030 else if ((in
&& picscale
< 200) || (!in
&& picscale
<= 200))
1042 SetZoom(picscale
+zoomFactor
, centermouse
);
1046 SetZoom(picscale
-zoomFactor
, centermouse
);
1050 void CPicWindow::FitImageInWindow()
1054 GetClientRectWithScrollbars(&rect
);
1056 const auto border
= CDPIAware::Instance().ScaleX(*this, 2);
1057 if (rect
.right
-rect
.left
)
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
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
);
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
1081 pTheOtherPic
->SetZoom(min(100, Zoom
), false);
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
;
1089 pTheOtherPic
->SetZoom(min(yscale
, xscale
), false);
1091 nHSecondScrollPos
= 0;
1092 nVSecondScrollPos
= 0;
1094 SetZoom(Zoom
, false);
1098 InvalidateRect(*this, nullptr, TRUE
);
1101 void CPicWindow::CenterImage()
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
;
1131 void CPicWindow::FitWidths(bool bFit
)
1135 SetZoom(GetZoom(), false);
1138 void CPicWindow::FitHeights(bool 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);
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);
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
);
1187 void CPicWindow::Paint(HWND hwnd
)
1191 RECT rect
, fullrect
;
1193 GetUpdateRect(hwnd
, &rect
, FALSE
);
1194 if (IsRectEmpty(&rect
))
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
);
1218 ShowPicWithBorder(memDC
, rect
, picture
, picscale
);
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);
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);
1246 rect
.right
-rect
.left
,
1247 rect
.bottom
-rect
.top
,
1251 rect
.right
-rect
.left
,
1252 rect
.bottom
-rect
.top
,
1255 else if (m_blend
== BlendType::Xor
)
1260 rect
.right
-rect
.left
,
1261 rect
.bottom
-rect
.top
,
1265 //rect.right-rect.left,
1266 //rect.bottom-rect.top,
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
);
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());
1312 auto infostring
= std::make_unique
<wchar_t[]>(8192);
1313 BuildInfoString(infostring
.get(), 8192, false);
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
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
);
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);
1348 ResString str
= ResString(hResource
, IDS_INVALIDIMAGEINFO
);
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
;
1362 max(rect
.left
+ ((rect
.right
- rect
.left
) - nStringLength
) / 2, 1l),
1363 rect
.top
+ ((rect
.bottom
-rect
.top
) - stringsize
.cy
)/2,
1367 static_cast<UINT
>(wcslen(str
)),
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,
1388 static_cast<LPCWSTR
>(nullptr),
1389 WS_CHILD
| WS_VISIBLE
| BS_PUSHBUTTON
| BS_ICON
| BS_FLAT
,
1392 reinterpret_cast<HMENU
>(LEFTBUTTON_ID
),
1395 if (hwndLeftBtn
== INVALID_HANDLE_VALUE
)
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,
1403 static_cast<LPCWSTR
>(nullptr),
1404 WS_CHILD
| WS_VISIBLE
| BS_PUSHBUTTON
| BS_ICON
| BS_FLAT
,
1407 reinterpret_cast<HMENU
>(RIGHTBUTTON_ID
),
1410 if (hwndRightBtn
== INVALID_HANDLE_VALUE
)
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,
1416 static_cast<LPCWSTR
>(nullptr),
1417 WS_CHILD
| WS_VISIBLE
| BS_PUSHBUTTON
| BS_ICON
| BS_FLAT
,
1420 reinterpret_cast<HMENU
>(PLAYBUTTON_ID
),
1423 if (hwndPlayBtn
== INVALID_HANDLE_VALUE
)
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,
1430 static_cast<LPCWSTR
>(nullptr),
1431 WS_CHILD
| WS_VISIBLE
| BS_PUSHBUTTON
| BS_ICON
| BS_FLAT
| BS_NOTIFY
| BS_PUSHLIKE
,
1434 reinterpret_cast<HMENU
>(ALPHATOGGLEBUTTON_ID
),
1437 if (hwndAlphaToggleBtn
== INVALID_HANDLE_VALUE
)
1439 hAlphaToggle
= LoadIconEx(hResource
, MAKEINTRESOURCE(IDI_ALPHATOGGLE
), iconWidth
, iconHeight
);
1440 SendMessage(hwndAlphaToggleBtn
, BM_SETIMAGE
, IMAGE_ICON
, reinterpret_cast<LPARAM
>(static_cast<HICON
>(hAlphaToggle
)));
1443 ti
.cbSize
= sizeof(TOOLINFO
);
1444 ti
.uFlags
= TTF_IDISHWND
|TTF_SUBCLASS
;
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
1454 SendMessage(hwndTT
, TTM_ADDTOOL
, 0, reinterpret_cast<LPARAM
>(&ti
));
1455 ResString
sSelect(hResource
, IDS_SELECT
);
1456 hwndSelectBtn
= CreateWindowEx(0,
1459 WS_CHILD
| WS_VISIBLE
| BS_PUSHBUTTON
,
1462 reinterpret_cast<HMENU
>(SELECTBUTTON_ID
),
1465 if (hwndPlayBtn
== INVALID_HANDLE_VALUE
)
1471 void CPicWindow::PositionChildren()
1473 const auto header_height
= CDPIAware::Instance().ScaleY(*this, HEADER_HEIGHT
);
1474 const auto selBorder
= CDPIAware::Instance().ScaleX(*this, 100);
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
);
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
);
1486 ShowWindow(hwndPlayBtn
, SW_HIDE
);
1490 ShowWindow(hwndLeftBtn
, SW_HIDE
);
1491 ShowWindow(hwndRightBtn
, SW_HIDE
);
1492 ShowWindow(hwndPlayBtn
, SW_HIDE
);
1495 SetWindowPos(hwndSelectBtn
, HWND_TOP
, rect
.right
- selBorder
, rect
.bottom
- header_height
, selBorder
, header_height
, SWP_FRAMECHANGED
| SWP_SHOWWINDOW
);
1497 ShowWindow(hwndSelectBtn
, SW_HIDE
);
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
1515 hwndParent
, // parent window
1516 reinterpret_cast<HMENU
>(TRACKBAR_ID
),// control identifier
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
,
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()));
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
)
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);
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
);