Initialize variables once
[TortoiseGit.git] / src / TortoiseIDiff / PicWindow.cpp
blob6546654e346dbae4cfb32e1966defcdd509e76e5
1 // TortoiseGitIDiff - an image diff viewer in TortoiseSVN
3 // Copyright (C) 2006-2013, 2018 - TortoiseSVN
4 // Copyright (C) 2016, 2018 - 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"
29 #pragma comment(lib, "Msimg32.lib")
30 #pragma comment(lib, "shell32.lib")
32 bool CPicWindow::RegisterAndCreateWindow(HWND hParent)
34 WNDCLASSEX wcx;
36 // Fill in the window class structure with default parameters
37 wcx.cbSize = sizeof(WNDCLASSEX);
38 wcx.style = CS_HREDRAW | CS_VREDRAW;
39 wcx.lpfnWndProc = CWindow::stWinMsgHandler;
40 wcx.cbClsExtra = 0;
41 wcx.cbWndExtra = 0;
42 wcx.hInstance = hResource;
43 wcx.hCursor = LoadCursor(nullptr, IDC_ARROW);
44 wcx.lpszClassName = L"TortoiseGitIDiffPicWindow";
45 wcx.hIcon = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_TORTOISEIDIFF), GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON));
46 wcx.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
47 wcx.lpszMenuName = MAKEINTRESOURCE(IDC_TORTOISEIDIFF);
48 wcx.hIconSm = LoadIconEx(wcx.hInstance, MAKEINTRESOURCE(IDI_TORTOISEIDIFF));
49 RegisterWindow(&wcx);
50 if (CreateEx(WS_EX_ACCEPTFILES | WS_EX_CLIENTEDGE, WS_CHILD | WS_HSCROLL | WS_VSCROLL | WS_VISIBLE, hParent))
52 ShowWindow(m_hwnd, SW_SHOW);
53 UpdateWindow(m_hwnd);
54 CreateButtons();
55 return true;
57 return false;
60 void CPicWindow::PositionTrackBar()
62 const auto slider_width = CDPIAware::Instance().ScaleX(SLIDER_WIDTH);
63 RECT rc;
64 GetClientRect(&rc);
65 HWND slider = m_AlphaSlider.GetWindow();
66 if ((pSecondPic)&&(m_blend == BLEND_ALPHA))
68 MoveWindow(slider, 0, rc.top - CDPIAware::Instance().ScaleY(4) + slider_width, slider_width, rc.bottom - rc.top - slider_width + CDPIAware::Instance().ScaleX(8), true);
69 ShowWindow(slider, SW_SHOW);
70 MoveWindow(hwndAlphaToggleBtn, 0, rc.top - CDPIAware::Instance().ScaleY(4), slider_width, slider_width, true);
71 ShowWindow(hwndAlphaToggleBtn, SW_SHOW);
73 else
75 ShowWindow(slider, SW_HIDE);
76 ShowWindow(hwndAlphaToggleBtn, SW_HIDE);
80 LRESULT CALLBACK CPicWindow::WinMsgHandler(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
82 TRACKMOUSEEVENT mevt;
83 switch (uMsg)
85 case WM_CREATE:
87 // create a slider control
88 CreateTrackbar(hwnd);
89 ShowWindow(m_AlphaSlider.GetWindow(), SW_HIDE);
90 //Create the tooltips
91 TOOLINFO ti;
92 RECT rect; // for client area coordinates
94 hwndTT = CreateWindowEx(0,
95 TOOLTIPS_CLASS,
96 nullptr,
97 WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
98 CW_USEDEFAULT,
99 CW_USEDEFAULT,
100 CW_USEDEFAULT,
101 CW_USEDEFAULT,
102 hwnd,
103 nullptr,
104 hResource,
105 nullptr
108 SetWindowPos(hwndTT,
109 HWND_TOP,
114 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
116 ::GetClientRect(hwnd, &rect);
118 ti.cbSize = sizeof(TOOLINFO);
119 ti.uFlags = TTF_TRACK | TTF_ABSOLUTE;
120 ti.hwnd = hwnd;
121 ti.hinst = hResource;
122 ti.uId = 0;
123 ti.lpszText = LPSTR_TEXTCALLBACK;
124 // ToolTip control will cover the whole window
125 ti.rect.left = rect.left;
126 ti.rect.top = rect.top;
127 ti.rect.right = rect.right;
128 ti.rect.bottom = rect.bottom;
130 SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &ti);
131 SendMessage(hwndTT, TTM_SETMAXTIPWIDTH, 0, 600);
132 nHSecondScrollPos = 0;
133 nVSecondScrollPos = 0;
135 break;
136 case WM_SETFOCUS:
137 case WM_KILLFOCUS:
138 InvalidateRect(*this, nullptr, FALSE);
139 break;
140 case WM_ERASEBKGND:
141 return 1;
142 break;
143 case WM_PAINT:
144 Paint(hwnd);
145 break;
146 case WM_SIZE:
147 PositionTrackBar();
148 SetupScrollBars();
149 break;
150 case WM_VSCROLL:
151 if ((pSecondPic)&&((HWND)lParam == m_AlphaSlider.GetWindow()))
153 if (LOWORD(wParam) == TB_THUMBTRACK)
155 // while tracking, only redraw after 50 milliseconds
156 ::SetTimer(*this, TIMER_ALPHASLIDER, 50, nullptr);
158 else
159 SetBlendAlpha(m_blend, SendMessage(m_AlphaSlider.GetWindow(), TBM_GETPOS, 0, 0) / 16.0f);
161 else
163 UINT nPos = HIWORD(wParam);
164 bool bForceUpdate = false;
165 if (LOWORD(wParam) == SB_THUMBTRACK || LOWORD(wParam) == SB_THUMBPOSITION)
167 // Get true 32-bit scroll position
168 SCROLLINFO si;
169 si.cbSize = sizeof(SCROLLINFO);
170 si.fMask = SIF_TRACKPOS;
171 GetScrollInfo(*this, SB_VERT, &si);
172 nPos = si.nTrackPos;
173 bForceUpdate = true;
176 OnVScroll(LOWORD(wParam), nPos);
177 if (bLinkedPositions && pTheOtherPic)
179 pTheOtherPic->OnVScroll(LOWORD(wParam), nPos);
180 if (bForceUpdate)
181 ::UpdateWindow(*pTheOtherPic);
184 break;
185 case WM_HSCROLL:
187 UINT nPos = HIWORD(wParam);
188 bool bForceUpdate = false;
189 if (LOWORD(wParam) == SB_THUMBTRACK || LOWORD(wParam) == SB_THUMBPOSITION)
191 // Get true 32-bit scroll position
192 SCROLLINFO si;
193 si.cbSize = sizeof(SCROLLINFO);
194 si.fMask = SIF_TRACKPOS;
195 GetScrollInfo(*this, SB_VERT, &si);
196 nPos = si.nTrackPos;
197 bForceUpdate = true;
200 OnHScroll(LOWORD(wParam), nPos);
201 if (bLinkedPositions && pTheOtherPic)
203 pTheOtherPic->OnHScroll(LOWORD(wParam), nPos);
204 if (bForceUpdate)
205 ::UpdateWindow(*pTheOtherPic);
208 break;
209 case WM_MOUSEWHEEL:
211 OnMouseWheel(GET_KEYSTATE_WPARAM(wParam), GET_WHEEL_DELTA_WPARAM(wParam));
213 break;
214 case WM_MOUSEHWHEEL:
216 OnMouseWheel(GET_KEYSTATE_WPARAM(wParam)|MK_SHIFT, GET_WHEEL_DELTA_WPARAM(wParam));
218 break;
219 case WM_LBUTTONDOWN:
220 SetFocus(*this);
221 ptPanStart.x = GET_X_LPARAM(lParam);
222 ptPanStart.y = GET_Y_LPARAM(lParam);
223 startVScrollPos = nVScrollPos;
224 startHScrollPos = nHScrollPos;
225 startVSecondScrollPos = nVSecondScrollPos;
226 startHSecondScrollPos = nHSecondScrollPos;
227 bDragging = true;
228 SetCapture(*this);
229 break;
230 case WM_LBUTTONUP:
231 bDragging = false;
232 ReleaseCapture();
233 InvalidateRect(*this, nullptr, FALSE);
234 break;
235 case WM_MOUSELEAVE:
236 ptPanStart.x = -1;
237 ptPanStart.y = -1;
238 m_lastTTPos.x = 0;
239 m_lastTTPos.y = 0;
240 SendMessage(hwndTT, TTM_TRACKACTIVATE, FALSE, 0);
241 break;
242 case WM_MOUSEMOVE:
244 mevt.cbSize = sizeof(TRACKMOUSEEVENT);
245 mevt.dwFlags = TME_LEAVE;
246 mevt.dwHoverTime = HOVER_DEFAULT;
247 mevt.hwndTrack = *this;
248 ::TrackMouseEvent(&mevt);
249 POINT pt = {((int)(short)LOWORD(lParam)), ((int)(short)HIWORD(lParam))};
250 if (pt.y < CDPIAware::Instance().ScaleY(HEADER_HEIGHT))
252 ClientToScreen(*this, &pt);
253 if ((abs(m_lastTTPos.x - pt.x) > 20)||(abs(m_lastTTPos.y - pt.y) > 10))
255 m_lastTTPos = pt;
256 pt.x += 15;
257 pt.y += 15;
258 SendMessage(hwndTT, TTM_TRACKPOSITION, 0, MAKELONG(pt.x, pt.y));
259 TOOLINFO ti = {0};
260 ti.cbSize = sizeof(TOOLINFO);
261 ti.hwnd = *this;
262 ti.uId = 0;
263 SendMessage(hwndTT, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti);
266 else
268 SendMessage(hwndTT, TTM_TRACKACTIVATE, FALSE, 0);
269 m_lastTTPos.x = 0;
270 m_lastTTPos.y = 0;
272 if ((wParam & MK_LBUTTON) &&
273 (ptPanStart.x >= 0) &&
274 (ptPanStart.y >= 0))
276 // pan the image
277 int xPos = GET_X_LPARAM(lParam);
278 int yPos = GET_Y_LPARAM(lParam);
280 if (wParam & MK_CONTROL)
282 nHSecondScrollPos = startHSecondScrollPos + (ptPanStart.x - xPos);
283 nVSecondScrollPos = startVSecondScrollPos + (ptPanStart.y - yPos);
285 else if (wParam & MK_SHIFT)
287 nHScrollPos = startHScrollPos + (ptPanStart.x - xPos);
288 nVScrollPos = startVScrollPos + (ptPanStart.y - yPos);
290 else
292 nHSecondScrollPos = startHSecondScrollPos + (ptPanStart.x - xPos);
293 nVSecondScrollPos = startVSecondScrollPos + (ptPanStart.y - yPos);
294 nHScrollPos = startHScrollPos + (ptPanStart.x - xPos);
295 nVScrollPos = startVScrollPos + (ptPanStart.y - yPos);
296 if (!bLinkedPositions && pTheOtherPic)
298 // snap to the other picture borders
299 if (abs(nVScrollPos-pTheOtherPic->nVScrollPos) < 10)
300 nVScrollPos = pTheOtherPic->nVScrollPos;
301 if (abs(nHScrollPos-pTheOtherPic->nHScrollPos) < 10)
302 nHScrollPos = pTheOtherPic->nHScrollPos;
305 SetupScrollBars();
306 InvalidateRect(*this, nullptr, TRUE);
307 UpdateWindow(*this);
308 if (pTheOtherPic && (bLinkedPositions) && ((wParam & MK_SHIFT)==0))
310 pTheOtherPic->nHScrollPos = nHScrollPos;
311 pTheOtherPic->nVScrollPos = nVScrollPos;
312 pTheOtherPic->SetupScrollBars();
313 InvalidateRect(*pTheOtherPic, nullptr, TRUE);
314 UpdateWindow(*pTheOtherPic);
318 break;
319 case WM_SETCURSOR:
321 // we show a hand cursor if the image can be dragged,
322 // and a hand-down cursor if the image is currently dragged
323 if ((*this == (HWND)wParam)&&(LOWORD(lParam)==HTCLIENT))
325 RECT rect;
326 GetClientRect(&rect);
327 LONG width = picture.m_Width;
328 LONG height = picture.m_Height;
329 if (pSecondPic)
331 width = max(width, pSecondPic->m_Width);
332 height = max(height, pSecondPic->m_Height);
335 if ((GetKeyState(VK_LBUTTON)&0x8000)||(HIWORD(lParam) == WM_LBUTTONDOWN))
337 SetCursor(curHandDown);
339 else
341 SetCursor(curHand);
343 return TRUE;
345 return DefWindowProc(hwnd, uMsg, wParam, lParam);
347 break;
348 case WM_DROPFILES:
350 HDROP hDrop = (HDROP)wParam;
351 TCHAR szFileName[MAX_PATH] = {0};
352 // we only use the first file dropped (if multiple files are dropped)
353 if (DragQueryFile(hDrop, 0, szFileName, _countof(szFileName)))
355 SetPic(szFileName, L"", bMainPic);
356 FitImageInWindow();
357 InvalidateRect(*this, nullptr, TRUE);
360 break;
361 case WM_COMMAND:
363 switch (LOWORD(wParam))
365 case LEFTBUTTON_ID:
367 PrevImage();
368 if (bLinkedPositions && pTheOtherPic)
369 pTheOtherPic->PrevImage();
370 return 0;
372 break;
373 case RIGHTBUTTON_ID:
375 NextImage();
376 if (bLinkedPositions && pTheOtherPic)
377 pTheOtherPic->NextImage();
378 return 0;
380 break;
381 case PLAYBUTTON_ID:
383 bPlaying = !bPlaying;
384 Animate(bPlaying);
385 if (bLinkedPositions && pTheOtherPic)
386 pTheOtherPic->Animate(bPlaying);
387 return 0;
389 break;
390 case ALPHATOGGLEBUTTON_ID:
392 WORD msg = HIWORD(wParam);
393 switch (msg)
395 case BN_DOUBLECLICKED:
397 SendMessage(hwndAlphaToggleBtn, BM_SETSTATE, 1, 0);
398 SetTimer(*this, ID_ALPHATOGGLETIMER, 1000, nullptr);
400 break;
401 case BN_CLICKED:
402 KillTimer(*this, ID_ALPHATOGGLETIMER);
403 ToggleAlpha();
404 break;
406 return 0;
408 break;
409 case BLENDALPHA_ID:
411 m_blend = BLEND_ALPHA;
412 PositionTrackBar();
413 InvalidateRect(*this, nullptr, TRUE);
415 break;
416 case BLENDXOR_ID:
418 m_blend = BLEND_XOR;
419 PositionTrackBar();
420 InvalidateRect(*this, nullptr, TRUE);
422 break;
423 case SELECTBUTTON_ID:
425 SendMessage(GetParent(m_hwnd), WM_COMMAND, MAKEWPARAM(SELECTBUTTON_ID, SELECTBUTTON_ID), (LPARAM)m_hwnd);
427 break;
430 break;
431 case WM_TIMER:
433 switch (wParam)
435 case ID_ANIMATIONTIMER:
437 nCurrentFrame++;
438 if (nCurrentFrame > picture.GetNumberOfFrames(0))
439 nCurrentFrame = 1;
440 long delay = picture.SetActiveFrame(nCurrentFrame);
441 delay = max(100, delay);
442 SetTimer(*this, ID_ANIMATIONTIMER, delay, nullptr);
443 InvalidateRect(*this, nullptr, FALSE);
445 break;
446 case TIMER_ALPHASLIDER:
448 SetBlendAlpha(m_blend, SendMessage(m_AlphaSlider.GetWindow(), TBM_GETPOS, 0, 0)/16.0f);
449 KillTimer(*this, TIMER_ALPHASLIDER);
451 break;
452 case ID_ALPHATOGGLETIMER:
454 ToggleAlpha();
456 break;
459 break;
460 case WM_NOTIFY:
462 LPNMHDR pNMHDR = (LPNMHDR)lParam;
463 if (pNMHDR->code == TTN_GETDISPINFO)
465 if (pNMHDR->hwndFrom == m_AlphaSlider.GetWindow())
467 LPTOOLTIPTEXT lpttt = (LPTOOLTIPTEXT) lParam;
468 lpttt->hinst = hResource;
469 TCHAR stringbuf[MAX_PATH] = {0};
470 swprintf_s(stringbuf, L"%i%% alpha", (int)(SendMessage(m_AlphaSlider.GetWindow(),TBM_GETPOS, 0, 0) / 16.0f * 100.0f));
471 wcscpy_s(lpttt->lpszText, 80, stringbuf);
473 else if (pNMHDR->idFrom == (UINT_PTR)hwndAlphaToggleBtn)
475 swprintf_s(m_wszTip, (TCHAR const*)ResString(hResource, IDS_ALPHABUTTONTT), (int)(SendMessage(m_AlphaSlider.GetWindow(),TBM_GETPOS, 0, 0) / 16.0f * 100.0f));
476 if (pNMHDR->code == TTN_NEEDTEXTW)
478 NMTTDISPINFOW* pTTTW = (NMTTDISPINFOW*)pNMHDR;
479 pTTTW->lpszText = m_wszTip;
481 else
483 NMTTDISPINFOA* pTTTA = (NMTTDISPINFOA*)pNMHDR;
484 pTTTA->lpszText = m_szTip;
485 ::WideCharToMultiByte(CP_ACP, 0, m_wszTip, -1, m_szTip, 8192, nullptr, nullptr);
488 else
490 BuildInfoString(m_wszTip, _countof(m_wszTip), true);
491 if (pNMHDR->code == TTN_NEEDTEXTW)
493 NMTTDISPINFOW* pTTTW = (NMTTDISPINFOW*)pNMHDR;
494 pTTTW->lpszText = m_wszTip;
496 else
498 NMTTDISPINFOA* pTTTA = (NMTTDISPINFOA*)pNMHDR;
499 pTTTA->lpszText = m_szTip;
500 ::WideCharToMultiByte(CP_ACP, 0, m_wszTip, -1, m_szTip, 8192, nullptr, nullptr);
505 break;
506 case WM_DESTROY:
507 DestroyIcon(hLeft);
508 DestroyIcon(hRight);
509 DestroyIcon(hPlay);
510 DestroyIcon(hStop);
511 bWindowClosed = TRUE;
512 break;
513 default:
514 return DefWindowProc(hwnd, uMsg, wParam, lParam);
517 return 0;
520 void CPicWindow::NextImage()
522 nCurrentDimension++;
523 if (nCurrentDimension > picture.GetNumberOfDimensions())
524 nCurrentDimension = picture.GetNumberOfDimensions();
525 nCurrentFrame++;
526 if (nCurrentFrame > picture.GetNumberOfFrames(0))
527 nCurrentFrame = picture.GetNumberOfFrames(0);
528 picture.SetActiveFrame(nCurrentFrame >= nCurrentDimension ? nCurrentFrame : nCurrentDimension);
529 InvalidateRect(*this, nullptr, FALSE);
530 PositionChildren();
533 void CPicWindow::PrevImage()
535 nCurrentDimension--;
536 if (nCurrentDimension < 1)
537 nCurrentDimension = 1;
538 nCurrentFrame--;
539 if (nCurrentFrame < 1)
540 nCurrentFrame = 1;
541 picture.SetActiveFrame(nCurrentFrame >= nCurrentDimension ? nCurrentFrame : nCurrentDimension);
542 InvalidateRect(*this, nullptr, FALSE);
543 PositionChildren();
546 void CPicWindow::Animate(bool bStart)
548 if (bStart)
550 SendMessage(hwndPlayBtn, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hStop);
551 SetTimer(*this, ID_ANIMATIONTIMER, 0, nullptr);
553 else
555 SendMessage(hwndPlayBtn, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hPlay);
556 KillTimer(*this, ID_ANIMATIONTIMER);
560 void CPicWindow::SetPic(const tstring& path, const tstring& title, bool bFirst)
562 bMainPic = bFirst;
563 picpath=path;pictitle=title;
564 picture.SetInterpolationMode(InterpolationModeHighQualityBicubic);
565 bValid = picture.Load(picpath);
566 nDimensions = picture.GetNumberOfDimensions();
567 if (nDimensions)
568 nFrames = picture.GetNumberOfFrames(0);
569 if (bValid)
571 picscale = 100;
572 PositionChildren();
573 InvalidateRect(*this, nullptr, FALSE);
577 void CPicWindow::DrawViewTitle(HDC hDC, RECT * rect)
579 const auto header_height = CDPIAware::Instance().ScaleY(HEADER_HEIGHT);
580 auto hFont = CreateFont(-CDPIAware::Instance().PointsToPixelsY(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");
581 HFONT hFontOld = (HFONT)SelectObject(hDC, (HGDIOBJ)hFont);
583 RECT textrect;
584 textrect.left = rect->left;
585 textrect.top = rect->top;
586 textrect.right = rect->right;
587 textrect.bottom = rect->top + header_height;
588 if (HasMultipleImages())
589 textrect.bottom += header_height;
591 COLORREF crBk, crFg;
592 crBk = ::GetSysColor(COLOR_SCROLLBAR);
593 crFg = ::GetSysColor(COLOR_WINDOWTEXT);
594 SetBkColor(hDC, crBk);
595 ::ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &textrect, nullptr, 0, nullptr);
597 if (GetFocus() == *this)
598 DrawEdge(hDC, &textrect, EDGE_BUMP, BF_RECT);
599 else
600 DrawEdge(hDC, &textrect, EDGE_ETCHED, BF_RECT);
602 SetTextColor(hDC, crFg);
604 // use the path if no title is set.
605 tstring * title = pictitle.empty() ? &picpath : &pictitle;
607 tstring realtitle = *title;
608 tstring imgnumstring;
610 if (HasMultipleImages())
612 TCHAR buf[MAX_PATH] = {0};
613 if (nFrames > 1)
614 swprintf_s(buf, (const TCHAR *)ResString(hResource, IDS_DIMENSIONSANDFRAMES), nCurrentFrame, nFrames);
615 else
616 swprintf_s(buf, (const TCHAR *)ResString(hResource, IDS_DIMENSIONSANDFRAMES), nCurrentDimension, nDimensions);
617 imgnumstring = buf;
620 SIZE stringsize;
621 if (GetTextExtentPoint32(hDC, realtitle.c_str(), (int)realtitle.size(), &stringsize))
623 int nStringLength = stringsize.cx;
624 int texttop = pSecondPic ? textrect.top + (header_height /2) - stringsize.cy : textrect.top + (header_height /2) - stringsize.cy/2;
625 ExtTextOut(hDC,
626 max(textrect.left + ((textrect.right-textrect.left)-nStringLength)/2, 1),
627 texttop,
628 ETO_CLIPPED,
629 &textrect,
630 realtitle.c_str(),
631 (UINT)realtitle.size(),
632 nullptr);
633 if (pSecondPic)
635 realtitle = (pictitle2.empty() ? picpath2 : pictitle2);
636 ExtTextOut(hDC,
637 max(textrect.left + ((textrect.right-textrect.left)-nStringLength)/2, 1),
638 texttop + stringsize.cy,
639 ETO_CLIPPED,
640 &textrect,
641 realtitle.c_str(),
642 (UINT)realtitle.size(),
643 nullptr);
646 if (HasMultipleImages())
648 if (GetTextExtentPoint32(hDC, imgnumstring.c_str(), (int)imgnumstring.size(), &stringsize))
650 int nStringLength = stringsize.cx;
652 ExtTextOut(hDC,
653 max(textrect.left + ((textrect.right-textrect.left)-nStringLength)/2, 1),
654 textrect.top + header_height + (header_height /2) - stringsize.cy/2,
655 ETO_CLIPPED,
656 &textrect,
657 imgnumstring.c_str(),
658 (UINT)imgnumstring.size(),
659 nullptr);
662 SelectObject(hDC, (HGDIOBJ)hFontOld);
663 DeleteObject(hFont);
666 void CPicWindow::SetupScrollBars()
668 RECT rect;
669 GetClientRect(&rect);
671 SCROLLINFO si = {sizeof(si)};
673 si.fMask = SIF_POS | SIF_PAGE | SIF_RANGE | SIF_DISABLENOSCROLL;
675 long width = picture.m_Width*picscale/100;
676 long height = picture.m_Height*picscale/100;
677 if (pSecondPic && pTheOtherPic)
679 width = max(width, pSecondPic->m_Width*pTheOtherPic->GetZoom()/100);
680 height = max(height, pSecondPic->m_Height*pTheOtherPic->GetZoom()/100);
683 bool bShowHScrollBar = (nHScrollPos > 0); // left of pic is left of window
684 bShowHScrollBar = bShowHScrollBar || (width-nHScrollPos > rect.right); // right of pic is outside right of window
685 bShowHScrollBar = bShowHScrollBar || (width+nHScrollPos > rect.right); // right of pic is outside right of window
686 bool bShowVScrollBar = (nVScrollPos > 0); // top of pic is above window
687 bShowVScrollBar = bShowVScrollBar || (height-nVScrollPos+rect.top > rect.bottom); // bottom of pic is below window
688 bShowVScrollBar = bShowVScrollBar || (height+nVScrollPos+rect.top > rect.bottom); // bottom of pic is below window
690 // if the image is smaller than the window, we don't need the scrollbars
691 ShowScrollBar(*this, SB_HORZ, bShowHScrollBar);
692 ShowScrollBar(*this, SB_VERT, bShowVScrollBar);
694 if (bShowVScrollBar)
696 si.nPos = nVScrollPos;
697 si.nPage = rect.bottom-rect.top;
698 if (height < rect.bottom-rect.top)
700 if (nVScrollPos > 0)
702 si.nMin = 0;
703 si.nMax = rect.bottom+nVScrollPos-rect.top;
705 else
707 si.nMin = nVScrollPos;
708 si.nMax = int(height);
711 else
713 if (nVScrollPos > 0)
715 si.nMin = 0;
716 si.nMax = int(max(height, rect.bottom+nVScrollPos-rect.top));
718 else
720 si.nMin = 0;
721 si.nMax = int(height-nVScrollPos);
724 SetScrollInfo(*this, SB_VERT, &si, TRUE);
727 if (bShowHScrollBar)
729 si.nPos = nHScrollPos;
730 si.nPage = rect.right-rect.left;
731 if (width < rect.right-rect.left)
733 if (nHScrollPos > 0)
735 si.nMin = 0;
736 si.nMax = rect.right+nHScrollPos-rect.left;
738 else
740 si.nMin = nHScrollPos;
741 si.nMax = int(width);
744 else
746 if (nHScrollPos > 0)
748 si.nMin = 0;
749 si.nMax = int(max(width, rect.right+nHScrollPos-rect.left));
751 else
753 si.nMin = 0;
754 si.nMax = int(width-nHScrollPos);
757 SetScrollInfo(*this, SB_HORZ, &si, TRUE);
760 PositionChildren();
763 void CPicWindow::OnVScroll(UINT nSBCode, UINT nPos)
765 RECT rect;
766 GetClientRect(&rect);
768 switch (nSBCode)
770 case SB_BOTTOM:
771 nVScrollPos = LONG(picture.GetHeight()*picscale/100);
772 break;
773 case SB_TOP:
774 nVScrollPos = 0;
775 break;
776 case SB_LINEDOWN:
777 nVScrollPos++;
778 break;
779 case SB_LINEUP:
780 nVScrollPos--;
781 break;
782 case SB_PAGEDOWN:
783 nVScrollPos += (rect.bottom-rect.top);
784 break;
785 case SB_PAGEUP:
786 nVScrollPos -= (rect.bottom-rect.top);
787 break;
788 case SB_THUMBPOSITION:
789 nVScrollPos = nPos;
790 break;
791 case SB_THUMBTRACK:
792 nVScrollPos = nPos;
793 break;
794 default:
795 return;
797 LONG height = LONG(picture.GetHeight()*picscale/100);
798 if (pSecondPic)
800 height = max(height, LONG(pSecondPic->GetHeight()*picscale/100));
801 nVSecondScrollPos = nVScrollPos;
803 SetupScrollBars();
804 PositionChildren();
805 InvalidateRect(*this, nullptr, TRUE);
808 void CPicWindow::OnHScroll(UINT nSBCode, UINT nPos)
810 RECT rect;
811 GetClientRect(&rect);
813 switch (nSBCode)
815 case SB_RIGHT:
816 nHScrollPos = LONG(picture.GetWidth()*picscale/100);
817 break;
818 case SB_LEFT:
819 nHScrollPos = 0;
820 break;
821 case SB_LINERIGHT:
822 nHScrollPos++;
823 break;
824 case SB_LINELEFT:
825 nHScrollPos--;
826 break;
827 case SB_PAGERIGHT:
828 nHScrollPos += (rect.right-rect.left);
829 break;
830 case SB_PAGELEFT:
831 nHScrollPos -= (rect.right-rect.left);
832 break;
833 case SB_THUMBPOSITION:
834 nHScrollPos = nPos;
835 break;
836 case SB_THUMBTRACK:
837 nHScrollPos = nPos;
838 break;
839 default:
840 return;
842 LONG width = LONG(picture.GetWidth()*picscale/100);
843 if (pSecondPic)
845 width = max(width, LONG(pSecondPic->GetWidth()*picscale/100));
846 nHSecondScrollPos = nHScrollPos;
848 SetupScrollBars();
849 PositionChildren();
850 InvalidateRect(*this, nullptr, TRUE);
853 void CPicWindow::OnMouseWheel(short fwKeys, short zDelta)
855 RECT rect;
856 GetClientRect(&rect);
857 LONG width = long(picture.m_Width*picscale/100);
858 LONG height = long(picture.m_Height*picscale/100);
859 if (pSecondPic)
861 width = max(width, long(pSecondPic->m_Width*picscale/100));
862 height = max(height, long(pSecondPic->m_Height*picscale/100));
864 if ((fwKeys & MK_SHIFT)&&(fwKeys & MK_CONTROL)&&(pSecondPic))
866 // ctrl+shift+wheel: change the alpha channel
867 float a = blendAlpha;
868 a -= float(zDelta)/120.0f/4.0f;
869 if (a < 0.0f)
870 a = 0.0f;
871 else if (a > 1.0f)
872 a = 1.0f;
873 SetBlendAlpha(m_blend, a);
875 else if (fwKeys & MK_SHIFT)
877 // shift means scrolling sideways
878 OnHScroll(SB_THUMBPOSITION, GetHPos()-zDelta);
879 if ((bLinkedPositions)&&(pTheOtherPic))
881 pTheOtherPic->OnHScroll(SB_THUMBPOSITION, pTheOtherPic->GetHPos()-zDelta);
884 else if (fwKeys & MK_CONTROL)
886 // control means adjusting the scale factor
887 Zoom(zDelta>0, true);
888 PositionChildren();
889 InvalidateRect(*this, nullptr, FALSE);
890 SetWindowPos(*this, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOREPOSITION | SWP_NOMOVE);
891 UpdateWindow(*this);
892 if ((bLinkedPositions || bOverlap) && pTheOtherPic)
894 pTheOtherPic->nHScrollPos = nHScrollPos;
895 pTheOtherPic->nVScrollPos = nVScrollPos;
896 pTheOtherPic->SetupScrollBars();
897 InvalidateRect(*pTheOtherPic, nullptr, TRUE);
898 UpdateWindow(*pTheOtherPic);
901 else
903 OnVScroll(SB_THUMBPOSITION, GetVPos()-zDelta);
904 if ((bLinkedPositions)&&(pTheOtherPic))
906 pTheOtherPic->OnVScroll(SB_THUMBPOSITION, pTheOtherPic->GetVPos()-zDelta);
911 void CPicWindow::GetClientRect(RECT * pRect)
913 ::GetClientRect(*this, pRect);
914 pRect->top += CDPIAware::Instance().ScaleY(HEADER_HEIGHT);
915 if (HasMultipleImages())
917 pRect->top += CDPIAware::Instance().ScaleY(HEADER_HEIGHT);
919 if (pSecondPic)
920 pRect->left += CDPIAware::Instance().ScaleX(SLIDER_WIDTH);
923 void CPicWindow::GetClientRectWithScrollbars(RECT * pRect)
925 GetClientRect(pRect);
926 ::GetWindowRect(*this, pRect);
927 pRect->right = pRect->right-pRect->left;
928 pRect->bottom = pRect->bottom-pRect->top;
929 pRect->top = 0;
930 pRect->left = 0;
931 pRect->top += CDPIAware::Instance().ScaleY(HEADER_HEIGHT);
932 if (HasMultipleImages())
934 pRect->top += CDPIAware::Instance().ScaleY(HEADER_HEIGHT);
936 if (pSecondPic)
937 pRect->left += CDPIAware::Instance().ScaleX(SLIDER_WIDTH);
941 void CPicWindow::SetZoom(int Zoom, bool centermouse, bool inzoom)
943 // Set the interpolation mode depending on zoom
944 int oldPicscale = picscale;
945 int oldOtherPicscale = picscale;
947 picture.SetInterpolationMode(InterpolationModeNearestNeighbor);
948 if (pSecondPic)
949 pSecondPic->SetInterpolationMode(InterpolationModeNearestNeighbor);
951 if ((oldPicscale == 0) || (Zoom == 0))
952 return;
954 picscale = Zoom;
956 if (pTheOtherPic && !inzoom)
958 if (bOverlap)
960 pTheOtherPic->SetZoom(Zoom, centermouse, true);
962 if (bFitHeights)
964 m_linkedHeight = 0;
965 pTheOtherPic->SetZoomToHeight(picture.m_Height*Zoom/100);
967 if (bFitWidths)
969 m_linkedWidth = 0;
970 pTheOtherPic->SetZoomToWidth(picture.m_Width*Zoom/100);
974 // adjust the scrollbar positions according to the new zoom and the
975 // mouse position: if possible, keep the pixel where the mouse pointer
976 // is at the same position after the zoom
977 if (!inzoom)
979 POINT cpos;
980 DWORD ptW = GetMessagePos();
981 cpos.x = GET_X_LPARAM(ptW);
982 cpos.y = GET_Y_LPARAM(ptW);
983 ScreenToClient(*this, &cpos);
984 RECT clientrect;
985 GetClientRect(&clientrect);
986 if ((PtInRect(&clientrect, cpos))&&(centermouse))
988 // the mouse pointer is over our window
989 nHScrollPos = (nHScrollPos + cpos.x)*Zoom/oldPicscale-cpos.x;
990 nVScrollPos = (nVScrollPos + cpos.y)*Zoom/oldPicscale-cpos.y;
991 if (pTheOtherPic && bMainPic)
993 int otherzoom = pTheOtherPic->GetZoom();
994 nHSecondScrollPos = (nHSecondScrollPos + cpos.x)*otherzoom/oldOtherPicscale-cpos.x;
995 nVSecondScrollPos = (nVSecondScrollPos + cpos.y)*otherzoom/oldOtherPicscale-cpos.y;
998 else
1000 nHScrollPos = (nHScrollPos + ((clientrect.right-clientrect.left)/2))*Zoom/oldPicscale-((clientrect.right-clientrect.left)/2);
1001 nVScrollPos = (nVScrollPos + ((clientrect.bottom-clientrect.top)/2))*Zoom/oldPicscale-((clientrect.bottom-clientrect.top)/2);
1002 if (pTheOtherPic && bMainPic)
1004 int otherzoom = pTheOtherPic->GetZoom();
1005 nHSecondScrollPos = (nHSecondScrollPos + ((clientrect.right-clientrect.left)/2))*otherzoom/oldOtherPicscale-((clientrect.right-clientrect.left)/2);
1006 nVSecondScrollPos = (nVSecondScrollPos + ((clientrect.bottom-clientrect.top)/2))*otherzoom/oldOtherPicscale-((clientrect.bottom-clientrect.top)/2);
1011 SetupScrollBars();
1012 PositionChildren();
1013 InvalidateRect(*this, nullptr, TRUE);
1016 void CPicWindow::Zoom(bool in, bool centermouse)
1018 int zoomFactor;
1020 // Find correct zoom factor and quantize picscale
1021 if (picscale % 10)
1023 picscale /= 10;
1024 picscale *= 10;
1025 if (!in)
1026 picscale += 10;
1029 if (!in && picscale <= 20)
1031 picscale = 10;
1032 zoomFactor = 0;
1034 else if ((in && picscale < 100) || (!in && picscale <= 100))
1036 zoomFactor = 10;
1038 else if ((in && picscale < 200) || (!in && picscale <= 200))
1040 zoomFactor = 20;
1042 else
1044 zoomFactor = 10;
1047 // Set zoom
1048 if (in)
1050 SetZoom(picscale+zoomFactor, centermouse);
1052 else
1054 SetZoom(picscale-zoomFactor, centermouse);
1058 void CPicWindow::FitImageInWindow()
1060 RECT rect;
1062 GetClientRectWithScrollbars(&rect);
1064 const auto border = CDPIAware::Instance().ScaleX(2);
1065 if (rect.right-rect.left)
1067 int Zoom = 100;
1068 if (((rect.right - rect.left) > picture.m_Width + border) && ((rect.bottom - rect.top) > picture.m_Height + border))
1070 // image is smaller than the window
1071 Zoom = 100;
1073 else
1075 // image is bigger than the window
1076 int xscale = (rect.right - rect.left - border) * 100 / picture.m_Width;
1077 int yscale = (rect.bottom - rect.top - border) * 100 / picture.m_Height;
1078 Zoom = min(yscale, xscale);
1080 if (pSecondPic)
1082 if (((rect.right - rect.left) > pSecondPic->m_Width + border) && ((rect.bottom - rect.top) > pSecondPic->m_Height + border))
1084 // image is smaller than the window
1085 if (pTheOtherPic)
1086 pTheOtherPic->SetZoom(min(100, Zoom), false);
1088 else
1090 // image is bigger than the window
1091 int xscale = (rect.right - rect.left - border) * 100 / pSecondPic->m_Width;
1092 int yscale = (rect.bottom - rect.top - border) * 100 / pSecondPic->m_Height;
1093 if (pTheOtherPic)
1094 pTheOtherPic->SetZoom(min(yscale, xscale), false);
1096 nHSecondScrollPos = 0;
1097 nVSecondScrollPos = 0;
1099 SetZoom(Zoom, false);
1101 CenterImage();
1102 PositionChildren();
1103 InvalidateRect(*this, nullptr, TRUE);
1106 void CPicWindow::CenterImage()
1108 RECT rect;
1109 GetClientRectWithScrollbars(&rect);
1110 const auto border = CDPIAware::Instance().ScaleX(2);
1111 long width = picture.m_Width*picscale / 100 + border;
1112 long height = picture.m_Height*picscale / 100 + border;
1113 if (pSecondPic && pTheOtherPic)
1115 width = max(width, pSecondPic->m_Width*pTheOtherPic->GetZoom() / 100 + border);
1116 height = max(height, pSecondPic->m_Height*pTheOtherPic->GetZoom() / 100 + border);
1119 bool bPicWidthBigger = (int(width) > (rect.right-rect.left));
1120 bool bPicHeightBigger = (int(height) > (rect.bottom-rect.top));
1121 // set the scroll position so that the image is drawn centered in the window
1122 // if the window is bigger than the image
1123 if (!bPicWidthBigger)
1125 nHScrollPos = -((rect.right-rect.left+4)-int(width))/2;
1126 nHSecondScrollPos = nHScrollPos;
1128 if (!bPicHeightBigger)
1130 nVScrollPos = -((rect.bottom-rect.top+4)-int(height))/2;
1131 nVSecondScrollPos = nVScrollPos;
1133 SetupScrollBars();
1136 void CPicWindow::FitWidths(bool bFit)
1138 bFitWidths = bFit;
1140 SetZoom(GetZoom(), false);
1143 void CPicWindow::FitHeights(bool bFit)
1145 bFitHeights = bFit;
1147 SetZoom(GetZoom(), false);
1150 void CPicWindow::ShowPicWithBorder(HDC hdc, const RECT &bounds, CPicture &pic, int scale)
1152 ::SetBkColor(hdc, transparentColor);
1153 ::ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &bounds, nullptr, 0, nullptr);
1155 RECT picrect;
1156 picrect.left = bounds.left - nHScrollPos;
1157 picrect.top = bounds.top - nVScrollPos;
1158 if ((!bLinkedPositions || bOverlap) && (pTheOtherPic) && (&pic != &picture))
1160 picrect.left = bounds.left - nHSecondScrollPos;
1161 picrect.top = bounds.top - nVSecondScrollPos;
1163 picrect.right = (picrect.left + pic.m_Width * scale / 100);
1164 picrect.bottom = (picrect.top + pic.m_Height * scale / 100);
1166 if (bFitWidths && m_linkedWidth)
1167 picrect.right = picrect.left + m_linkedWidth;
1168 if (bFitHeights && m_linkedHeight)
1169 picrect.bottom = picrect.top + m_linkedHeight;
1171 pic.Show(hdc, picrect);
1173 const auto bordersize = CDPIAware::Instance().ScaleX(1);
1175 RECT border;
1176 border.left = picrect.left - bordersize;
1177 border.top = picrect.top - bordersize;
1178 border.right = picrect.right + bordersize;
1179 border.bottom = picrect.bottom + bordersize;
1181 HPEN hPen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DDKSHADOW));
1182 HPEN hOldPen = (HPEN)SelectObject(hdc, hPen);
1183 MoveToEx(hdc, border.left, border.top, nullptr);
1184 LineTo(hdc, border.left, border.bottom);
1185 LineTo(hdc, border.right, border.bottom);
1186 LineTo(hdc, border.right, border.top);
1187 LineTo(hdc, border.left, border.top);
1188 SelectObject(hdc, hOldPen);
1189 DeleteObject(hPen);
1192 void CPicWindow::Paint(HWND hwnd)
1194 PAINTSTRUCT ps;
1195 HDC hdc;
1196 RECT rect, fullrect;
1198 GetUpdateRect(hwnd, &rect, FALSE);
1199 if (IsRectEmpty(&rect))
1200 return;
1202 const auto slider_width = CDPIAware::Instance().ScaleX(SLIDER_WIDTH);
1203 const auto border = CDPIAware::Instance().ScaleX(4);
1204 ::GetClientRect(*this, &fullrect);
1205 hdc = BeginPaint(hwnd, &ps);
1207 // Exclude the alpha control and button
1208 if ((pSecondPic)&&(m_blend == BLEND_ALPHA))
1209 ExcludeClipRect(hdc, 0, m_inforect.top - border, slider_width, m_inforect.bottom + border);
1211 CMyMemDC memDC(hdc);
1212 if ((pSecondPic)&&(m_blend != BLEND_ALPHA))
1214 // erase the place where the alpha slider would be
1215 ::SetBkColor(memDC, transparentColor);
1216 RECT bounds = { 0, m_inforect.top - border, slider_width, m_inforect.bottom + border };
1217 ::ExtTextOut(memDC, 0, 0, ETO_OPAQUE, &bounds, nullptr, 0, nullptr);
1220 GetClientRect(&rect);
1221 if (bValid)
1223 ShowPicWithBorder(memDC, rect, picture, picscale);
1224 if (pSecondPic)
1226 HDC secondhdc = CreateCompatibleDC(hdc);
1227 HBITMAP hBitmap = CreateCompatibleBitmap(hdc, rect.right - rect.left, rect.bottom - rect.top);
1228 HBITMAP hOldBitmap = (HBITMAP)SelectObject(secondhdc, hBitmap);
1229 SetWindowOrgEx(secondhdc, rect.left, rect.top, nullptr);
1231 if ((pSecondPic)&&(m_blend != BLEND_ALPHA))
1233 // erase the place where the alpha slider would be
1234 ::SetBkColor(secondhdc, transparentColor);
1235 RECT bounds = { 0, m_inforect.top - border, slider_width, m_inforect.bottom + border };
1236 ::ExtTextOut(secondhdc, 0, 0, ETO_OPAQUE, &bounds, nullptr, 0, nullptr);
1238 if (pTheOtherPic)
1239 ShowPicWithBorder(secondhdc, rect, *pSecondPic, pTheOtherPic->GetZoom());
1241 if (m_blend == BLEND_ALPHA)
1243 BLENDFUNCTION blender;
1244 blender.AlphaFormat = 0;
1245 blender.BlendFlags = 0;
1246 blender.BlendOp = AC_SRC_OVER;
1247 blender.SourceConstantAlpha = (BYTE)(blendAlpha*255);
1248 AlphaBlend(memDC,
1249 rect.left,
1250 rect.top,
1251 rect.right-rect.left,
1252 rect.bottom-rect.top,
1253 secondhdc,
1254 rect.left,
1255 rect.top,
1256 rect.right-rect.left,
1257 rect.bottom-rect.top,
1258 blender);
1260 else if (m_blend == BLEND_XOR)
1262 BitBlt(memDC,
1263 rect.left,
1264 rect.top,
1265 rect.right-rect.left,
1266 rect.bottom-rect.top,
1267 secondhdc,
1268 rect.left,
1269 rect.top,
1270 //rect.right-rect.left,
1271 //rect.bottom-rect.top,
1272 SRCINVERT);
1273 InvertRect(memDC, &rect);
1275 SelectObject(secondhdc, hOldBitmap);
1276 DeleteObject(hBitmap);
1277 DeleteDC(secondhdc);
1279 else if (bDragging && pTheOtherPic && !bLinkedPositions)
1281 // when dragging, show lines indicating the position of the other image
1282 HPEN hPen = CreatePen(PS_SOLID, 1, GetSysColor(/*COLOR_ACTIVEBORDER*/COLOR_HIGHLIGHT));
1283 HPEN hOldPen = (HPEN)SelectObject(memDC, hPen);
1284 int xpos = rect.left - pTheOtherPic->nHScrollPos - 1;
1285 MoveToEx(memDC, xpos, rect.top, nullptr);
1286 LineTo(memDC, xpos, rect.bottom);
1287 xpos = rect.left - pTheOtherPic->nHScrollPos + pTheOtherPic->picture.m_Width*pTheOtherPic->GetZoom()/100 + 1;
1288 if (bFitWidths && m_linkedWidth)
1289 xpos = rect.left + pTheOtherPic->m_linkedWidth + 1;
1290 MoveToEx(memDC, xpos, rect.top, nullptr);
1291 LineTo(memDC, xpos, rect.bottom);
1293 int ypos = rect.top - pTheOtherPic->nVScrollPos - 1;
1294 MoveToEx(memDC, rect.left, ypos, nullptr);
1295 LineTo(memDC, rect.right, ypos);
1296 ypos = rect.top - pTheOtherPic->nVScrollPos + pTheOtherPic->picture.m_Height*pTheOtherPic->GetZoom()/100 + 1;
1297 if (bFitHeights && m_linkedHeight)
1298 ypos = rect.top - pTheOtherPic->m_linkedHeight + 1;
1299 MoveToEx(memDC, rect.left, ypos, nullptr);
1300 LineTo(memDC, rect.right, ypos);
1302 SelectObject(memDC, hOldPen);
1303 DeleteObject(hPen);
1306 int sliderwidth = 0;
1307 if ((pSecondPic)&&(m_blend == BLEND_ALPHA))
1308 sliderwidth = slider_width;
1309 m_inforect.left = rect.left + border + sliderwidth;
1310 m_inforect.top = rect.top;
1311 m_inforect.right = rect.right+sliderwidth;
1312 m_inforect.bottom = rect.bottom;
1314 SetBkColor(memDC, transparentColor);
1315 if (bShowInfo)
1317 auto infostring = std::make_unique<TCHAR[]>(8192);
1318 BuildInfoString(infostring.get(), 8192, false);
1319 // set the font
1320 NONCLIENTMETRICS metrics = {0};
1321 metrics.cbSize = sizeof(NONCLIENTMETRICS);
1322 SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &metrics, FALSE);
1323 HFONT hFont = CreateFontIndirect(&metrics.lfStatusFont);
1324 HFONT hFontOld = (HFONT)SelectObject(memDC, (HGDIOBJ)hFont);
1325 // find out how big the rectangle for the text has to be
1326 DrawText(memDC, infostring.get(), -1, &m_inforect, DT_EDITCONTROL | DT_EXPANDTABS | DT_LEFT | DT_VCENTER | DT_CALCRECT);
1328 // the text should be drawn with a four pixel offset to the window borders
1329 m_inforect.top = rect.bottom - (m_inforect.bottom-m_inforect.top) - border;
1330 m_inforect.bottom = rect.bottom-4;
1332 // first draw an edge rectangle
1333 RECT edgerect;
1334 edgerect.left = m_inforect.left - border;
1335 edgerect.top = m_inforect.top - border;
1336 edgerect.right = m_inforect.right + border;
1337 edgerect.bottom = m_inforect.bottom + border;
1338 ::ExtTextOut(memDC, 0, 0, ETO_OPAQUE, &edgerect, nullptr, 0, nullptr);
1339 DrawEdge(memDC, &edgerect, EDGE_BUMP, BF_RECT | BF_SOFT);
1341 SetTextColor(memDC, GetSysColor(COLOR_WINDOWTEXT));
1342 DrawText(memDC, infostring.get(), -1, &m_inforect, DT_EDITCONTROL | DT_EXPANDTABS | DT_LEFT | DT_VCENTER);
1343 SelectObject(memDC, (HGDIOBJ)hFontOld);
1344 DeleteObject(hFont);
1347 else
1349 SetBkColor(memDC, ::GetSysColor(COLOR_WINDOW));
1350 SetTextColor(memDC, ::GetSysColor(COLOR_WINDOWTEXT));
1351 ::ExtTextOut(memDC, 0, 0, ETO_OPAQUE, &rect, nullptr, 0, nullptr);
1352 SIZE stringsize;
1353 ResString str = ResString(hResource, IDS_INVALIDIMAGEINFO);
1355 // set the font
1356 NONCLIENTMETRICS metrics = {0};
1357 metrics.cbSize = sizeof(NONCLIENTMETRICS);
1358 SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &metrics, FALSE);
1359 HFONT hFont = CreateFontIndirect(&metrics.lfStatusFont);
1360 HFONT hFontOld = (HFONT)SelectObject(memDC, (HGDIOBJ)hFont);
1362 if (GetTextExtentPoint32(memDC, str, (int)wcslen(str), &stringsize))
1364 int nStringLength = stringsize.cx;
1366 ExtTextOut(memDC,
1367 max(rect.left + ((rect.right-rect.left)-nStringLength)/2, 1),
1368 rect.top + ((rect.bottom-rect.top) - stringsize.cy)/2,
1369 ETO_CLIPPED,
1370 &rect,
1371 str,
1372 (UINT)wcslen(str),
1373 nullptr);
1375 SelectObject(memDC, (HGDIOBJ)hFontOld);
1376 DeleteObject(hFont);
1378 DrawViewTitle(memDC, &fullrect);
1380 EndPaint(hwnd, &ps);
1383 bool CPicWindow::CreateButtons()
1385 // Ensure that the common control DLL is loaded.
1386 INITCOMMONCONTROLSEX icex;
1387 icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
1388 icex.dwICC = ICC_BAR_CLASSES | ICC_WIN95_CLASSES;
1389 InitCommonControlsEx(&icex);
1391 hwndLeftBtn = CreateWindowEx(0,
1392 L"BUTTON",
1393 (LPCTSTR)nullptr,
1394 WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_ICON | BS_FLAT,
1395 0, 0, 0, 0,
1396 (HWND)*this,
1397 (HMENU)LEFTBUTTON_ID,
1398 hResource,
1399 nullptr);
1400 if (hwndLeftBtn == INVALID_HANDLE_VALUE)
1401 return false;
1402 int iconWidth = GetSystemMetrics(SM_CXSMICON);
1403 int iconHeight = GetSystemMetrics(SM_CYSMICON);
1404 hLeft = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_BACKWARD), iconWidth, iconHeight);
1405 SendMessage(hwndLeftBtn, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hLeft);
1406 hwndRightBtn = CreateWindowEx(0,
1407 L"BUTTON",
1408 (LPCTSTR)nullptr,
1409 WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_ICON | BS_FLAT,
1410 0, 0, 0, 0,
1411 *this,
1412 (HMENU)RIGHTBUTTON_ID,
1413 hResource,
1414 nullptr);
1415 if (hwndRightBtn == INVALID_HANDLE_VALUE)
1416 return false;
1417 hRight = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_FORWARD), iconWidth, iconHeight);
1418 SendMessage(hwndRightBtn, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hRight);
1419 hwndPlayBtn = CreateWindowEx(0,
1420 L"BUTTON",
1421 (LPCTSTR)nullptr,
1422 WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_ICON | BS_FLAT,
1423 0, 0, 0, 0,
1424 *this,
1425 (HMENU)PLAYBUTTON_ID,
1426 hResource,
1427 nullptr);
1428 if (hwndPlayBtn == INVALID_HANDLE_VALUE)
1429 return false;
1430 hPlay = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_START), iconWidth, iconHeight);
1431 hStop = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_STOP), iconWidth, iconHeight);
1432 SendMessage(hwndPlayBtn, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hPlay);
1433 hwndAlphaToggleBtn = CreateWindowEx(0,
1434 L"BUTTON",
1435 (LPCTSTR)nullptr,
1436 WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_ICON | BS_FLAT | BS_NOTIFY | BS_PUSHLIKE,
1437 0, 0, 0, 0,
1438 (HWND)*this,
1439 (HMENU)ALPHATOGGLEBUTTON_ID,
1440 hResource,
1441 nullptr);
1442 if (hwndAlphaToggleBtn == INVALID_HANDLE_VALUE)
1443 return false;
1444 hAlphaToggle = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_ALPHATOGGLE), iconWidth, iconHeight);
1445 SendMessage(hwndAlphaToggleBtn, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hAlphaToggle);
1447 TOOLINFO ti = {0};
1448 ti.cbSize = sizeof(TOOLINFO);
1449 ti.uFlags = TTF_IDISHWND|TTF_SUBCLASS;
1450 ti.hwnd = *this;
1451 ti.hinst = hResource;
1452 ti.uId = (UINT_PTR)hwndAlphaToggleBtn;
1453 ti.lpszText = LPSTR_TEXTCALLBACK;
1454 // ToolTip control will cover the whole window
1455 ti.rect.left = 0;
1456 ti.rect.top = 0;
1457 ti.rect.right = 0;
1458 ti.rect.bottom = 0;
1459 SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &ti);
1460 ResString sSelect(hResource, IDS_SELECT);
1461 hwndSelectBtn = CreateWindowEx(0,
1462 L"BUTTON",
1463 sSelect,
1464 WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
1465 0, 0, 0, 0,
1466 *this,
1467 (HMENU)SELECTBUTTON_ID,
1468 hResource,
1469 nullptr);
1470 if (hwndPlayBtn == INVALID_HANDLE_VALUE)
1471 return false;
1473 return true;
1476 void CPicWindow::PositionChildren()
1478 const auto header_height = CDPIAware::Instance().ScaleY(HEADER_HEIGHT);
1479 const auto selBorder = CDPIAware::Instance().ScaleX(100);
1480 RECT rect;
1481 ::GetClientRect(*this, &rect);
1482 if (HasMultipleImages())
1484 int iconWidth = GetSystemMetrics(SM_CXSMICON);
1485 int iconHeight = GetSystemMetrics(SM_CYSMICON);
1486 SetWindowPos(hwndLeftBtn, HWND_TOP, rect.left + iconWidth / 4, rect.top + header_height + (header_height -iconHeight)/2, iconWidth, iconHeight, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
1487 SetWindowPos(hwndRightBtn, HWND_TOP, rect.left + iconWidth + iconWidth / 2, rect.top + header_height + (header_height - iconHeight) / 2, iconWidth, iconHeight, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
1488 if (nFrames > 1)
1489 SetWindowPos(hwndPlayBtn, HWND_TOP, rect.left + iconWidth * 2 + iconWidth / 2, rect.top + header_height + (header_height - iconHeight) / 2, iconWidth, iconHeight, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
1490 else
1491 ShowWindow(hwndPlayBtn, SW_HIDE);
1493 else
1495 ShowWindow(hwndLeftBtn, SW_HIDE);
1496 ShowWindow(hwndRightBtn, SW_HIDE);
1497 ShowWindow(hwndPlayBtn, SW_HIDE);
1499 if (bSelectionMode)
1500 SetWindowPos(hwndSelectBtn, HWND_TOP, rect.right - selBorder, rect.bottom - header_height, selBorder, header_height, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
1501 else
1502 ShowWindow(hwndSelectBtn, SW_HIDE);
1503 PositionTrackBar();
1506 bool CPicWindow::HasMultipleImages()
1508 return (((nDimensions > 1) || (nFrames > 1)) && (!pSecondPic));
1511 void CPicWindow::CreateTrackbar(HWND hwndParent)
1513 HWND hwndTrack = CreateWindowEx(
1514 0, // no extended styles
1515 TRACKBAR_CLASS, // class name
1516 L"Trackbar Control", // title (caption)
1517 WS_CHILD | WS_VISIBLE | TBS_VERT | TBS_TOOLTIPS | TBS_AUTOTICKS, // style
1518 10, 10, // position
1519 200, 30, // size
1520 hwndParent, // parent window
1521 (HMENU)TRACKBAR_ID, // control identifier
1522 hInst, // instance
1523 nullptr // no WM_CREATE parameter
1526 SendMessage(hwndTrack, TBM_SETRANGE,
1527 (WPARAM) TRUE, // redraw flag
1528 (LPARAM) MAKELONG(0, 16)); // min. & max. positions
1529 SendMessage(hwndTrack, TBM_SETTIPSIDE,
1530 (WPARAM) TBTS_TOP,
1531 (LPARAM) 0);
1533 m_AlphaSlider.ConvertTrackbarToNice(hwndTrack);
1536 void CPicWindow::BuildInfoString(TCHAR * buf, int size, bool bTooltip)
1538 // Unfortunately, we need two different strings for the tooltip
1539 // and the info box. Because the tooltips use a different tab size
1540 // than ExtTextOut(), and to keep the output aligned we therefore
1541 // need two different strings.
1542 // Note: some translations could end up with two identical strings, but
1543 // in English we need two - even if we wouldn't need two in English, some
1544 // translation might then need two again.
1545 if (pSecondPic && pTheOtherPic)
1547 swprintf_s(buf, size,
1548 (TCHAR const *)ResString(hResource, bTooltip ? IDS_DUALIMAGEINFOTT : IDS_DUALIMAGEINFO),
1549 picture.GetFileSizeAsText().c_str(), picture.GetFileSizeAsText(false).c_str(),
1550 picture.m_Width, picture.m_Height,
1551 picture.GetHorizontalResolution(), picture.GetVerticalResolution(),
1552 picture.m_ColorDepth,
1553 (UINT)GetZoom(),
1554 pSecondPic->GetFileSizeAsText().c_str(), pSecondPic->GetFileSizeAsText(false).c_str(),
1555 pSecondPic->m_Width, pSecondPic->m_Height,
1556 pSecondPic->GetHorizontalResolution(), pSecondPic->GetVerticalResolution(),
1557 pSecondPic->m_ColorDepth,
1558 (UINT)pTheOtherPic->GetZoom());
1560 else
1562 swprintf_s(buf, size,
1563 (TCHAR const *)ResString(hResource, bTooltip ? IDS_IMAGEINFOTT : IDS_IMAGEINFO),
1564 picture.GetFileSizeAsText().c_str(), picture.GetFileSizeAsText(false).c_str(),
1565 picture.m_Width, picture.m_Height,
1566 picture.GetHorizontalResolution(), picture.GetVerticalResolution(),
1567 picture.m_ColorDepth,
1568 (UINT)GetZoom());
1572 void CPicWindow::SetZoomToWidth( long width )
1574 m_linkedWidth = width;
1575 if (picture.m_Width)
1577 int zoom = width*100/picture.m_Width;
1578 SetZoom(zoom, false, true);
1582 void CPicWindow::SetZoomToHeight( long height )
1584 m_linkedHeight = height;
1585 if (picture.m_Height)
1587 int zoom = height*100/picture.m_Height;
1588 SetZoom(zoom, false, true);