Fixed issue #3107: Clean: Allow to remove orphaned submodules (i.e., clean -f -f)
[TortoiseGit.git] / src / TortoiseIDiff / PicWindow.cpp
blob94e21e989c9744dd02fb57513957dc77c113b867
1 // TortoiseIDiff - an image diff viewer in TortoiseSVN
3 // Copyright (C) 2006-2013 - TortoiseSVN
4 // Copyright (C) 2016 - 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>
27 #pragma comment(lib, "Msimg32.lib")
28 #pragma comment(lib, "shell32.lib")
30 bool CPicWindow::RegisterAndCreateWindow(HWND hParent)
32 WNDCLASSEX wcx;
34 // Fill in the window class structure with default parameters
35 wcx.cbSize = sizeof(WNDCLASSEX);
36 wcx.style = CS_HREDRAW | CS_VREDRAW;
37 wcx.lpfnWndProc = CWindow::stWinMsgHandler;
38 wcx.cbClsExtra = 0;
39 wcx.cbWndExtra = 0;
40 wcx.hInstance = hResource;
41 wcx.hCursor = LoadCursor(nullptr, IDC_ARROW);
42 wcx.lpszClassName = L"TortoiseGitIDiffPicWindow";
43 wcx.hIcon = LoadIcon(hResource, MAKEINTRESOURCE(IDI_TORTOISEIDIFF));
44 wcx.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
45 wcx.lpszMenuName = MAKEINTRESOURCE(IDC_TORTOISEIDIFF);
46 wcx.hIconSm = LoadIcon(wcx.hInstance, MAKEINTRESOURCE(IDI_TORTOISEIDIFF));
47 RegisterWindow(&wcx);
48 if (CreateEx(WS_EX_ACCEPTFILES | WS_EX_CLIENTEDGE, WS_CHILD | WS_HSCROLL | WS_VSCROLL | WS_VISIBLE, hParent))
50 ShowWindow(m_hwnd, SW_SHOW);
51 UpdateWindow(m_hwnd);
52 CreateButtons();
53 return true;
55 return false;
58 void CPicWindow::PositionTrackBar()
60 RECT rc;
61 GetClientRect(&rc);
62 HWND slider = m_AlphaSlider.GetWindow();
63 if ((pSecondPic)&&(m_blend == BLEND_ALPHA))
65 MoveWindow(slider, 0, rc.top-4+SLIDER_WIDTH, SLIDER_WIDTH, rc.bottom-rc.top-SLIDER_WIDTH+8, true);
66 ShowWindow(slider, SW_SHOW);
67 MoveWindow(hwndAlphaToggleBtn, 0, rc.top-4, SLIDER_WIDTH, SLIDER_WIDTH, true);
68 ShowWindow(hwndAlphaToggleBtn, SW_SHOW);
70 else
72 ShowWindow(slider, SW_HIDE);
73 ShowWindow(hwndAlphaToggleBtn, SW_HIDE);
77 LRESULT CALLBACK CPicWindow::WinMsgHandler(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
79 TRACKMOUSEEVENT mevt;
80 switch (uMsg)
82 case WM_CREATE:
84 // create a slider control
85 CreateTrackbar(hwnd);
86 ShowWindow(m_AlphaSlider.GetWindow(), SW_HIDE);
87 //Create the tooltips
88 TOOLINFO ti;
89 RECT rect; // for client area coordinates
91 hwndTT = CreateWindowEx(0,
92 TOOLTIPS_CLASS,
93 nullptr,
94 WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
95 CW_USEDEFAULT,
96 CW_USEDEFAULT,
97 CW_USEDEFAULT,
98 CW_USEDEFAULT,
99 hwnd,
100 nullptr,
101 hResource,
102 nullptr
105 SetWindowPos(hwndTT,
106 HWND_TOP,
111 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
113 ::GetClientRect(hwnd, &rect);
115 ti.cbSize = sizeof(TOOLINFO);
116 ti.uFlags = TTF_TRACK | TTF_ABSOLUTE;
117 ti.hwnd = hwnd;
118 ti.hinst = hResource;
119 ti.uId = 0;
120 ti.lpszText = LPSTR_TEXTCALLBACK;
121 // ToolTip control will cover the whole window
122 ti.rect.left = rect.left;
123 ti.rect.top = rect.top;
124 ti.rect.right = rect.right;
125 ti.rect.bottom = rect.bottom;
127 SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &ti);
128 SendMessage(hwndTT, TTM_SETMAXTIPWIDTH, 0, 600);
129 nHSecondScrollPos = 0;
130 nVSecondScrollPos = 0;
132 break;
133 case WM_SETFOCUS:
134 case WM_KILLFOCUS:
135 InvalidateRect(*this, nullptr, FALSE);
136 break;
137 case WM_ERASEBKGND:
138 return 1;
139 break;
140 case WM_PAINT:
141 Paint(hwnd);
142 break;
143 case WM_SIZE:
144 PositionTrackBar();
145 SetupScrollBars();
146 break;
147 case WM_VSCROLL:
148 if ((pSecondPic)&&((HWND)lParam == m_AlphaSlider.GetWindow()))
150 if (LOWORD(wParam) == TB_THUMBTRACK)
152 // while tracking, only redraw after 50 milliseconds
153 ::SetTimer(*this, TIMER_ALPHASLIDER, 50, nullptr);
155 else
156 SetBlendAlpha(m_blend, SendMessage(m_AlphaSlider.GetWindow(), TBM_GETPOS, 0, 0) / 16.0f);
158 else
160 UINT nPos = HIWORD(wParam);
161 bool bForceUpdate = false;
162 if (LOWORD(wParam) == SB_THUMBTRACK || LOWORD(wParam) == SB_THUMBPOSITION)
164 // Get true 32-bit scroll position
165 SCROLLINFO si;
166 si.cbSize = sizeof(SCROLLINFO);
167 si.fMask = SIF_TRACKPOS;
168 GetScrollInfo(*this, SB_VERT, &si);
169 nPos = si.nTrackPos;
170 bForceUpdate = true;
173 OnVScroll(LOWORD(wParam), nPos);
174 if (bLinkedPositions && pTheOtherPic)
176 pTheOtherPic->OnVScroll(LOWORD(wParam), nPos);
177 if (bForceUpdate)
178 ::UpdateWindow(*pTheOtherPic);
181 break;
182 case WM_HSCROLL:
184 UINT nPos = HIWORD(wParam);
185 bool bForceUpdate = false;
186 if (LOWORD(wParam) == SB_THUMBTRACK || LOWORD(wParam) == SB_THUMBPOSITION)
188 // Get true 32-bit scroll position
189 SCROLLINFO si;
190 si.cbSize = sizeof(SCROLLINFO);
191 si.fMask = SIF_TRACKPOS;
192 GetScrollInfo(*this, SB_VERT, &si);
193 nPos = si.nTrackPos;
194 bForceUpdate = true;
197 OnHScroll(LOWORD(wParam), nPos);
198 if (bLinkedPositions && pTheOtherPic)
200 pTheOtherPic->OnHScroll(LOWORD(wParam), nPos);
201 if (bForceUpdate)
202 ::UpdateWindow(*pTheOtherPic);
205 break;
206 case WM_MOUSEWHEEL:
208 OnMouseWheel(GET_KEYSTATE_WPARAM(wParam), GET_WHEEL_DELTA_WPARAM(wParam));
210 break;
211 case WM_MOUSEHWHEEL:
213 OnMouseWheel(GET_KEYSTATE_WPARAM(wParam)|MK_SHIFT, GET_WHEEL_DELTA_WPARAM(wParam));
215 break;
216 case WM_LBUTTONDOWN:
217 SetFocus(*this);
218 ptPanStart.x = GET_X_LPARAM(lParam);
219 ptPanStart.y = GET_Y_LPARAM(lParam);
220 startVScrollPos = nVScrollPos;
221 startHScrollPos = nHScrollPos;
222 startVSecondScrollPos = nVSecondScrollPos;
223 startHSecondScrollPos = nHSecondScrollPos;
224 bDragging = true;
225 SetCapture(*this);
226 break;
227 case WM_LBUTTONUP:
228 bDragging = false;
229 ReleaseCapture();
230 InvalidateRect(*this, nullptr, FALSE);
231 break;
232 case WM_MOUSELEAVE:
233 ptPanStart.x = -1;
234 ptPanStart.y = -1;
235 m_lastTTPos.x = 0;
236 m_lastTTPos.y = 0;
237 SendMessage(hwndTT, TTM_TRACKACTIVATE, FALSE, 0);
238 break;
239 case WM_MOUSEMOVE:
241 mevt.cbSize = sizeof(TRACKMOUSEEVENT);
242 mevt.dwFlags = TME_LEAVE;
243 mevt.dwHoverTime = HOVER_DEFAULT;
244 mevt.hwndTrack = *this;
245 ::TrackMouseEvent(&mevt);
246 POINT pt = {((int)(short)LOWORD(lParam)), ((int)(short)HIWORD(lParam))};
247 if (pt.y < HEADER_HEIGHT)
249 ClientToScreen(*this, &pt);
250 if ((abs(m_lastTTPos.x - pt.x) > 20)||(abs(m_lastTTPos.y - pt.y) > 10))
252 m_lastTTPos = pt;
253 pt.x += 15;
254 pt.y += 15;
255 SendMessage(hwndTT, TTM_TRACKPOSITION, 0, MAKELONG(pt.x, pt.y));
256 TOOLINFO ti = {0};
257 ti.cbSize = sizeof(TOOLINFO);
258 ti.hwnd = *this;
259 ti.uId = 0;
260 SendMessage(hwndTT, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti);
263 else
265 SendMessage(hwndTT, TTM_TRACKACTIVATE, FALSE, 0);
266 m_lastTTPos.x = 0;
267 m_lastTTPos.y = 0;
269 if ((wParam & MK_LBUTTON) &&
270 (ptPanStart.x >= 0) &&
271 (ptPanStart.y >= 0))
273 // pan the image
274 int xPos = GET_X_LPARAM(lParam);
275 int yPos = GET_Y_LPARAM(lParam);
277 if (wParam & MK_CONTROL)
279 nHSecondScrollPos = startHSecondScrollPos + (ptPanStart.x - xPos);
280 nVSecondScrollPos = startVSecondScrollPos + (ptPanStart.y - yPos);
282 else if (wParam & MK_SHIFT)
284 nHScrollPos = startHScrollPos + (ptPanStart.x - xPos);
285 nVScrollPos = startVScrollPos + (ptPanStart.y - yPos);
287 else
289 nHSecondScrollPos = startHSecondScrollPos + (ptPanStart.x - xPos);
290 nVSecondScrollPos = startVSecondScrollPos + (ptPanStart.y - yPos);
291 nHScrollPos = startHScrollPos + (ptPanStart.x - xPos);
292 nVScrollPos = startVScrollPos + (ptPanStart.y - yPos);
293 if (!bLinkedPositions && pTheOtherPic)
295 // snap to the other picture borders
296 if (abs(nVScrollPos-pTheOtherPic->nVScrollPos) < 10)
297 nVScrollPos = pTheOtherPic->nVScrollPos;
298 if (abs(nHScrollPos-pTheOtherPic->nHScrollPos) < 10)
299 nHScrollPos = pTheOtherPic->nHScrollPos;
302 SetupScrollBars();
303 InvalidateRect(*this, nullptr, TRUE);
304 UpdateWindow(*this);
305 if (pTheOtherPic && (bLinkedPositions) && ((wParam & MK_SHIFT)==0))
307 pTheOtherPic->nHScrollPos = nHScrollPos;
308 pTheOtherPic->nVScrollPos = nVScrollPos;
309 pTheOtherPic->SetupScrollBars();
310 InvalidateRect(*pTheOtherPic, nullptr, TRUE);
311 UpdateWindow(*pTheOtherPic);
315 break;
316 case WM_SETCURSOR:
318 // we show a hand cursor if the image can be dragged,
319 // and a hand-down cursor if the image is currently dragged
320 if ((*this == (HWND)wParam)&&(LOWORD(lParam)==HTCLIENT))
322 RECT rect;
323 GetClientRect(&rect);
324 LONG width = picture.m_Width;
325 LONG height = picture.m_Height;
326 if (pSecondPic)
328 width = max(width, pSecondPic->m_Width);
329 height = max(height, pSecondPic->m_Height);
332 if ((GetKeyState(VK_LBUTTON)&0x8000)||(HIWORD(lParam) == WM_LBUTTONDOWN))
334 SetCursor(curHandDown);
336 else
338 SetCursor(curHand);
340 return TRUE;
342 return DefWindowProc(hwnd, uMsg, wParam, lParam);
344 break;
345 case WM_DROPFILES:
347 HDROP hDrop = (HDROP)wParam;
348 TCHAR szFileName[MAX_PATH] = {0};
349 // we only use the first file dropped (if multiple files are dropped)
350 if (DragQueryFile(hDrop, 0, szFileName, _countof(szFileName)))
352 SetPic(szFileName, L"", bMainPic);
353 FitImageInWindow();
354 InvalidateRect(*this, nullptr, TRUE);
357 break;
358 case WM_COMMAND:
360 switch (LOWORD(wParam))
362 case LEFTBUTTON_ID:
364 PrevImage();
365 if (bLinkedPositions && pTheOtherPic)
366 pTheOtherPic->PrevImage();
367 return 0;
369 break;
370 case RIGHTBUTTON_ID:
372 NextImage();
373 if (bLinkedPositions && pTheOtherPic)
374 pTheOtherPic->NextImage();
375 return 0;
377 break;
378 case PLAYBUTTON_ID:
380 bPlaying = !bPlaying;
381 Animate(bPlaying);
382 if (bLinkedPositions && pTheOtherPic)
383 pTheOtherPic->Animate(bPlaying);
384 return 0;
386 break;
387 case ALPHATOGGLEBUTTON_ID:
389 WORD msg = HIWORD(wParam);
390 switch (msg)
392 case BN_DOUBLECLICKED:
394 SendMessage(hwndAlphaToggleBtn, BM_SETSTATE, 1, 0);
395 SetTimer(*this, ID_ALPHATOGGLETIMER, 1000, nullptr);
397 break;
398 case BN_CLICKED:
399 KillTimer(*this, ID_ALPHATOGGLETIMER);
400 ToggleAlpha();
401 break;
403 return 0;
405 break;
406 case BLENDALPHA_ID:
408 m_blend = BLEND_ALPHA;
409 PositionTrackBar();
410 InvalidateRect(*this, nullptr, TRUE);
412 break;
413 case BLENDXOR_ID:
415 m_blend = BLEND_XOR;
416 PositionTrackBar();
417 InvalidateRect(*this, nullptr, TRUE);
419 break;
420 case SELECTBUTTON_ID:
422 SendMessage(GetParent(m_hwnd), WM_COMMAND, MAKEWPARAM(SELECTBUTTON_ID, SELECTBUTTON_ID), (LPARAM)m_hwnd);
424 break;
427 break;
428 case WM_TIMER:
430 switch (wParam)
432 case ID_ANIMATIONTIMER:
434 nCurrentFrame++;
435 if (nCurrentFrame > picture.GetNumberOfFrames(0))
436 nCurrentFrame = 1;
437 long delay = picture.SetActiveFrame(nCurrentFrame);
438 delay = max(100, delay);
439 SetTimer(*this, ID_ANIMATIONTIMER, delay, nullptr);
440 InvalidateRect(*this, nullptr, FALSE);
442 break;
443 case TIMER_ALPHASLIDER:
445 SetBlendAlpha(m_blend, SendMessage(m_AlphaSlider.GetWindow(), TBM_GETPOS, 0, 0)/16.0f);
446 KillTimer(*this, TIMER_ALPHASLIDER);
448 break;
449 case ID_ALPHATOGGLETIMER:
451 ToggleAlpha();
453 break;
456 break;
457 case WM_NOTIFY:
459 LPNMHDR pNMHDR = (LPNMHDR)lParam;
460 if (pNMHDR->code == TTN_GETDISPINFO)
462 if (pNMHDR->hwndFrom == m_AlphaSlider.GetWindow())
464 LPTOOLTIPTEXT lpttt = (LPTOOLTIPTEXT) lParam;
465 lpttt->hinst = hResource;
466 TCHAR stringbuf[MAX_PATH] = {0};
467 swprintf_s(stringbuf, L"%i%% alpha", (int)(SendMessage(m_AlphaSlider.GetWindow(),TBM_GETPOS, 0, 0) / 16.0f * 100.0f));
468 wcscpy_s(lpttt->lpszText, 80, stringbuf);
470 else if (pNMHDR->idFrom == (UINT_PTR)hwndAlphaToggleBtn)
472 swprintf_s(m_wszTip, (TCHAR const*)ResString(hResource, IDS_ALPHABUTTONTT), (int)(SendMessage(m_AlphaSlider.GetWindow(),TBM_GETPOS, 0, 0) / 16.0f * 100.0f));
473 if (pNMHDR->code == TTN_NEEDTEXTW)
475 NMTTDISPINFOW* pTTTW = (NMTTDISPINFOW*)pNMHDR;
476 pTTTW->lpszText = m_wszTip;
478 else
480 NMTTDISPINFOA* pTTTA = (NMTTDISPINFOA*)pNMHDR;
481 pTTTA->lpszText = m_szTip;
482 ::WideCharToMultiByte(CP_ACP, 0, m_wszTip, -1, m_szTip, 8192, nullptr, nullptr);
485 else
487 BuildInfoString(m_wszTip, _countof(m_wszTip), true);
488 if (pNMHDR->code == TTN_NEEDTEXTW)
490 NMTTDISPINFOW* pTTTW = (NMTTDISPINFOW*)pNMHDR;
491 pTTTW->lpszText = m_wszTip;
493 else
495 NMTTDISPINFOA* pTTTA = (NMTTDISPINFOA*)pNMHDR;
496 pTTTA->lpszText = m_szTip;
497 ::WideCharToMultiByte(CP_ACP, 0, m_wszTip, -1, m_szTip, 8192, nullptr, nullptr);
502 break;
503 case WM_DESTROY:
504 DestroyIcon(hLeft);
505 DestroyIcon(hRight);
506 DestroyIcon(hPlay);
507 DestroyIcon(hStop);
508 bWindowClosed = TRUE;
509 break;
510 default:
511 return DefWindowProc(hwnd, uMsg, wParam, lParam);
514 return 0;
517 void CPicWindow::NextImage()
519 nCurrentDimension++;
520 if (nCurrentDimension > picture.GetNumberOfDimensions())
521 nCurrentDimension = picture.GetNumberOfDimensions();
522 nCurrentFrame++;
523 if (nCurrentFrame > picture.GetNumberOfFrames(0))
524 nCurrentFrame = picture.GetNumberOfFrames(0);
525 picture.SetActiveFrame(nCurrentFrame >= nCurrentDimension ? nCurrentFrame : nCurrentDimension);
526 InvalidateRect(*this, nullptr, FALSE);
527 PositionChildren();
530 void CPicWindow::PrevImage()
532 nCurrentDimension--;
533 if (nCurrentDimension < 1)
534 nCurrentDimension = 1;
535 nCurrentFrame--;
536 if (nCurrentFrame < 1)
537 nCurrentFrame = 1;
538 picture.SetActiveFrame(nCurrentFrame >= nCurrentDimension ? nCurrentFrame : nCurrentDimension);
539 InvalidateRect(*this, nullptr, FALSE);
540 PositionChildren();
543 void CPicWindow::Animate(bool bStart)
545 if (bStart)
547 SendMessage(hwndPlayBtn, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hStop);
548 SetTimer(*this, ID_ANIMATIONTIMER, 0, nullptr);
550 else
552 SendMessage(hwndPlayBtn, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hPlay);
553 KillTimer(*this, ID_ANIMATIONTIMER);
557 void CPicWindow::SetPic(const tstring& path, const tstring& title, bool bFirst)
559 bMainPic = bFirst;
560 picpath=path;pictitle=title;
561 picture.SetInterpolationMode(InterpolationModeHighQualityBicubic);
562 bValid = picture.Load(picpath);
563 nDimensions = picture.GetNumberOfDimensions();
564 if (nDimensions)
565 nFrames = picture.GetNumberOfFrames(0);
566 if (bValid)
568 picscale = 100;
569 PositionChildren();
570 InvalidateRect(*this, nullptr, FALSE);
574 void CPicWindow::DrawViewTitle(HDC hDC, RECT * rect)
576 HFONT hFont = nullptr;
577 hFont = CreateFont(-MulDiv(pSecondPic ? 8 : 10, GetDeviceCaps(hDC, LOGPIXELSY), 72), 0, 0, 0, FW_DONTCARE, false, false, false, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH, L"MS Shell Dlg");
578 HFONT hFontOld = (HFONT)SelectObject(hDC, (HGDIOBJ)hFont);
580 RECT textrect;
581 textrect.left = rect->left;
582 textrect.top = rect->top;
583 textrect.right = rect->right;
584 textrect.bottom = rect->top + HEADER_HEIGHT;
585 if (HasMultipleImages())
586 textrect.bottom += HEADER_HEIGHT;
588 COLORREF crBk, crFg;
589 crBk = ::GetSysColor(COLOR_SCROLLBAR);
590 crFg = ::GetSysColor(COLOR_WINDOWTEXT);
591 SetBkColor(hDC, crBk);
592 ::ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &textrect, nullptr, 0, nullptr);
594 if (GetFocus() == *this)
595 DrawEdge(hDC, &textrect, EDGE_BUMP, BF_RECT);
596 else
597 DrawEdge(hDC, &textrect, EDGE_ETCHED, BF_RECT);
599 SetTextColor(hDC, crFg);
601 // use the path if no title is set.
602 tstring * title = pictitle.empty() ? &picpath : &pictitle;
604 tstring realtitle = *title;
605 tstring imgnumstring;
607 if (HasMultipleImages())
609 TCHAR buf[MAX_PATH] = {0};
610 if (nFrames > 1)
611 swprintf_s(buf, (const TCHAR *)ResString(hResource, IDS_DIMENSIONSANDFRAMES), nCurrentFrame, nFrames);
612 else
613 swprintf_s(buf, (const TCHAR *)ResString(hResource, IDS_DIMENSIONSANDFRAMES), nCurrentDimension, nDimensions);
614 imgnumstring = buf;
617 SIZE stringsize;
618 if (GetTextExtentPoint32(hDC, realtitle.c_str(), (int)realtitle.size(), &stringsize))
620 int nStringLength = stringsize.cx;
621 int texttop = pSecondPic ? textrect.top + (HEADER_HEIGHT/2) - stringsize.cy : textrect.top + (HEADER_HEIGHT/2) - stringsize.cy/2;
622 ExtTextOut(hDC,
623 max(textrect.left + ((textrect.right-textrect.left)-nStringLength)/2, 1),
624 texttop,
625 ETO_CLIPPED,
626 &textrect,
627 realtitle.c_str(),
628 (UINT)realtitle.size(),
629 nullptr);
630 if (pSecondPic)
632 realtitle = (pictitle2.empty() ? picpath2 : pictitle2);
633 ExtTextOut(hDC,
634 max(textrect.left + ((textrect.right-textrect.left)-nStringLength)/2, 1),
635 texttop + stringsize.cy,
636 ETO_CLIPPED,
637 &textrect,
638 realtitle.c_str(),
639 (UINT)realtitle.size(),
640 nullptr);
643 if (HasMultipleImages())
645 if (GetTextExtentPoint32(hDC, imgnumstring.c_str(), (int)imgnumstring.size(), &stringsize))
647 int nStringLength = stringsize.cx;
649 ExtTextOut(hDC,
650 max(textrect.left + ((textrect.right-textrect.left)-nStringLength)/2, 1),
651 textrect.top + HEADER_HEIGHT + (HEADER_HEIGHT/2) - stringsize.cy/2,
652 ETO_CLIPPED,
653 &textrect,
654 imgnumstring.c_str(),
655 (UINT)imgnumstring.size(),
656 nullptr);
659 SelectObject(hDC, (HGDIOBJ)hFontOld);
660 DeleteObject(hFont);
663 void CPicWindow::SetupScrollBars()
665 RECT rect;
666 GetClientRect(&rect);
668 SCROLLINFO si = {sizeof(si)};
670 si.fMask = SIF_POS | SIF_PAGE | SIF_RANGE | SIF_DISABLENOSCROLL;
672 long width = picture.m_Width*picscale/100;
673 long height = picture.m_Height*picscale/100;
674 if (pSecondPic && pTheOtherPic)
676 width = max(width, pSecondPic->m_Width*pTheOtherPic->GetZoom()/100);
677 height = max(height, pSecondPic->m_Height*pTheOtherPic->GetZoom()/100);
680 bool bShowHScrollBar = (nHScrollPos > 0); // left of pic is left of window
681 bShowHScrollBar = bShowHScrollBar || (width-nHScrollPos > rect.right); // right of pic is outside right of window
682 bShowHScrollBar = bShowHScrollBar || (width+nHScrollPos > rect.right); // right of pic is outside right of window
683 bool bShowVScrollBar = (nVScrollPos > 0); // top of pic is above window
684 bShowVScrollBar = bShowVScrollBar || (height-nVScrollPos+rect.top > rect.bottom); // bottom of pic is below window
685 bShowVScrollBar = bShowVScrollBar || (height+nVScrollPos+rect.top > rect.bottom); // bottom of pic is below window
687 // if the image is smaller than the window, we don't need the scrollbars
688 ShowScrollBar(*this, SB_HORZ, bShowHScrollBar);
689 ShowScrollBar(*this, SB_VERT, bShowVScrollBar);
691 if (bShowVScrollBar)
693 si.nPos = nVScrollPos;
694 si.nPage = rect.bottom-rect.top;
695 if (height < rect.bottom-rect.top)
697 if (nVScrollPos > 0)
699 si.nMin = 0;
700 si.nMax = rect.bottom+nVScrollPos-rect.top;
702 else
704 si.nMin = nVScrollPos;
705 si.nMax = int(height);
708 else
710 if (nVScrollPos > 0)
712 si.nMin = 0;
713 si.nMax = int(max(height, rect.bottom+nVScrollPos-rect.top));
715 else
717 si.nMin = 0;
718 si.nMax = int(height-nVScrollPos);
721 SetScrollInfo(*this, SB_VERT, &si, TRUE);
724 if (bShowHScrollBar)
726 si.nPos = nHScrollPos;
727 si.nPage = rect.right-rect.left;
728 if (width < rect.right-rect.left)
730 if (nHScrollPos > 0)
732 si.nMin = 0;
733 si.nMax = rect.right+nHScrollPos-rect.left;
735 else
737 si.nMin = nHScrollPos;
738 si.nMax = int(width);
741 else
743 if (nHScrollPos > 0)
745 si.nMin = 0;
746 si.nMax = int(max(width, rect.right+nHScrollPos-rect.left));
748 else
750 si.nMin = 0;
751 si.nMax = int(width-nHScrollPos);
754 SetScrollInfo(*this, SB_HORZ, &si, TRUE);
757 PositionChildren();
760 void CPicWindow::OnVScroll(UINT nSBCode, UINT nPos)
762 RECT rect;
763 GetClientRect(&rect);
765 switch (nSBCode)
767 case SB_BOTTOM:
768 nVScrollPos = LONG(picture.GetHeight()*picscale/100);
769 break;
770 case SB_TOP:
771 nVScrollPos = 0;
772 break;
773 case SB_LINEDOWN:
774 nVScrollPos++;
775 break;
776 case SB_LINEUP:
777 nVScrollPos--;
778 break;
779 case SB_PAGEDOWN:
780 nVScrollPos += (rect.bottom-rect.top);
781 break;
782 case SB_PAGEUP:
783 nVScrollPos -= (rect.bottom-rect.top);
784 break;
785 case SB_THUMBPOSITION:
786 nVScrollPos = nPos;
787 break;
788 case SB_THUMBTRACK:
789 nVScrollPos = nPos;
790 break;
791 default:
792 return;
794 LONG height = LONG(picture.GetHeight()*picscale/100);
795 if (pSecondPic)
797 height = max(height, LONG(pSecondPic->GetHeight()*picscale/100));
798 nVSecondScrollPos = nVScrollPos;
800 SetupScrollBars();
801 PositionChildren();
802 InvalidateRect(*this, nullptr, TRUE);
805 void CPicWindow::OnHScroll(UINT nSBCode, UINT nPos)
807 RECT rect;
808 GetClientRect(&rect);
810 switch (nSBCode)
812 case SB_RIGHT:
813 nHScrollPos = LONG(picture.GetWidth()*picscale/100);
814 break;
815 case SB_LEFT:
816 nHScrollPos = 0;
817 break;
818 case SB_LINERIGHT:
819 nHScrollPos++;
820 break;
821 case SB_LINELEFT:
822 nHScrollPos--;
823 break;
824 case SB_PAGERIGHT:
825 nHScrollPos += (rect.right-rect.left);
826 break;
827 case SB_PAGELEFT:
828 nHScrollPos -= (rect.right-rect.left);
829 break;
830 case SB_THUMBPOSITION:
831 nHScrollPos = nPos;
832 break;
833 case SB_THUMBTRACK:
834 nHScrollPos = nPos;
835 break;
836 default:
837 return;
839 LONG width = LONG(picture.GetWidth()*picscale/100);
840 if (pSecondPic)
842 width = max(width, LONG(pSecondPic->GetWidth()*picscale/100));
843 nHSecondScrollPos = nHScrollPos;
845 SetupScrollBars();
846 PositionChildren();
847 InvalidateRect(*this, nullptr, TRUE);
850 void CPicWindow::OnMouseWheel(short fwKeys, short zDelta)
852 RECT rect;
853 GetClientRect(&rect);
854 LONG width = long(picture.m_Width*picscale/100);
855 LONG height = long(picture.m_Height*picscale/100);
856 if (pSecondPic)
858 width = max(width, long(pSecondPic->m_Width*picscale/100));
859 height = max(height, long(pSecondPic->m_Height*picscale/100));
861 if ((fwKeys & MK_SHIFT)&&(fwKeys & MK_CONTROL)&&(pSecondPic))
863 // ctrl+shift+wheel: change the alpha channel
864 float a = blendAlpha;
865 a -= float(zDelta)/120.0f/4.0f;
866 if (a < 0.0f)
867 a = 0.0f;
868 else if (a > 1.0f)
869 a = 1.0f;
870 SetBlendAlpha(m_blend, a);
872 else if (fwKeys & MK_SHIFT)
874 // shift means scrolling sideways
875 OnHScroll(SB_THUMBPOSITION, GetHPos()-zDelta);
876 if ((bLinkedPositions)&&(pTheOtherPic))
878 pTheOtherPic->OnHScroll(SB_THUMBPOSITION, pTheOtherPic->GetHPos()-zDelta);
881 else if (fwKeys & MK_CONTROL)
883 // control means adjusting the scale factor
884 Zoom(zDelta>0, true);
885 PositionChildren();
886 InvalidateRect(*this, nullptr, FALSE);
887 SetWindowPos(*this, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOREPOSITION | SWP_NOMOVE);
888 UpdateWindow(*this);
889 if ((bLinkedPositions || bOverlap) && pTheOtherPic)
891 pTheOtherPic->nHScrollPos = nHScrollPos;
892 pTheOtherPic->nVScrollPos = nVScrollPos;
893 pTheOtherPic->SetupScrollBars();
894 InvalidateRect(*pTheOtherPic, nullptr, TRUE);
895 UpdateWindow(*pTheOtherPic);
898 else
900 OnVScroll(SB_THUMBPOSITION, GetVPos()-zDelta);
901 if ((bLinkedPositions)&&(pTheOtherPic))
903 pTheOtherPic->OnVScroll(SB_THUMBPOSITION, pTheOtherPic->GetVPos()-zDelta);
908 void CPicWindow::GetClientRect(RECT * pRect)
910 ::GetClientRect(*this, pRect);
911 pRect->top += HEADER_HEIGHT;
912 if (HasMultipleImages())
914 pRect->top += HEADER_HEIGHT;
916 if (pSecondPic)
917 pRect->left += SLIDER_WIDTH;
920 void CPicWindow::GetClientRectWithScrollbars(RECT * pRect)
922 GetClientRect(pRect);
923 ::GetWindowRect(*this, pRect);
924 pRect->right = pRect->right-pRect->left;
925 pRect->bottom = pRect->bottom-pRect->top;
926 pRect->top = 0;
927 pRect->left = 0;
928 pRect->top += HEADER_HEIGHT;
929 if (HasMultipleImages())
931 pRect->top += HEADER_HEIGHT;
933 if (pSecondPic)
934 pRect->left += SLIDER_WIDTH;
938 void CPicWindow::SetZoom(int Zoom, bool centermouse, bool inzoom)
940 // Set the interpolation mode depending on zoom
941 int oldPicscale = picscale;
942 int oldOtherPicscale = picscale;
944 picture.SetInterpolationMode(InterpolationModeNearestNeighbor);
945 if (pSecondPic)
946 pSecondPic->SetInterpolationMode(InterpolationModeNearestNeighbor);
948 if ((oldPicscale == 0) || (Zoom == 0))
949 return;
951 picscale = Zoom;
953 if (pTheOtherPic && !inzoom)
955 if (bOverlap)
957 pTheOtherPic->SetZoom(Zoom, centermouse, true);
959 if (bFitHeights)
961 m_linkedHeight = 0;
962 pTheOtherPic->SetZoomToHeight(picture.m_Height*Zoom/100);
964 if (bFitWidths)
966 m_linkedWidth = 0;
967 pTheOtherPic->SetZoomToWidth(picture.m_Width*Zoom/100);
971 // adjust the scrollbar positions according to the new zoom and the
972 // mouse position: if possible, keep the pixel where the mouse pointer
973 // is at the same position after the zoom
974 if (!inzoom)
976 POINT cpos;
977 DWORD ptW = GetMessagePos();
978 cpos.x = GET_X_LPARAM(ptW);
979 cpos.y = GET_Y_LPARAM(ptW);
980 ScreenToClient(*this, &cpos);
981 RECT clientrect;
982 GetClientRect(&clientrect);
983 if ((PtInRect(&clientrect, cpos))&&(centermouse))
985 // the mouse pointer is over our window
986 nHScrollPos = (nHScrollPos + cpos.x)*Zoom/oldPicscale-cpos.x;
987 nVScrollPos = (nVScrollPos + cpos.y)*Zoom/oldPicscale-cpos.y;
988 if (pTheOtherPic && bMainPic)
990 int otherzoom = pTheOtherPic->GetZoom();
991 nHSecondScrollPos = (nHSecondScrollPos + cpos.x)*otherzoom/oldOtherPicscale-cpos.x;
992 nVSecondScrollPos = (nVSecondScrollPos + cpos.y)*otherzoom/oldOtherPicscale-cpos.y;
995 else
997 nHScrollPos = (nHScrollPos + ((clientrect.right-clientrect.left)/2))*Zoom/oldPicscale-((clientrect.right-clientrect.left)/2);
998 nVScrollPos = (nVScrollPos + ((clientrect.bottom-clientrect.top)/2))*Zoom/oldPicscale-((clientrect.bottom-clientrect.top)/2);
999 if (pTheOtherPic && bMainPic)
1001 int otherzoom = pTheOtherPic->GetZoom();
1002 nHSecondScrollPos = (nHSecondScrollPos + ((clientrect.right-clientrect.left)/2))*otherzoom/oldOtherPicscale-((clientrect.right-clientrect.left)/2);
1003 nVSecondScrollPos = (nVSecondScrollPos + ((clientrect.bottom-clientrect.top)/2))*otherzoom/oldOtherPicscale-((clientrect.bottom-clientrect.top)/2);
1008 SetupScrollBars();
1009 PositionChildren();
1010 InvalidateRect(*this, nullptr, TRUE);
1013 void CPicWindow::Zoom(bool in, bool centermouse)
1015 int zoomFactor;
1017 // Find correct zoom factor and quantize picscale
1018 if (picscale % 10)
1020 picscale /= 10;
1021 picscale *= 10;
1022 if (!in)
1023 picscale += 10;
1026 if (!in && picscale <= 20)
1028 picscale = 10;
1029 zoomFactor = 0;
1031 else if ((in && picscale < 100) || (!in && picscale <= 100))
1033 zoomFactor = 10;
1035 else if ((in && picscale < 200) || (!in && picscale <= 200))
1037 zoomFactor = 20;
1039 else
1041 zoomFactor = 10;
1044 // Set zoom
1045 if (in)
1047 SetZoom(picscale+zoomFactor, centermouse);
1049 else
1051 SetZoom(picscale-zoomFactor, centermouse);
1055 void CPicWindow::FitImageInWindow()
1057 RECT rect;
1059 GetClientRectWithScrollbars(&rect);
1061 if (rect.right-rect.left)
1063 int Zoom = 100;
1064 if (((rect.right - rect.left) > picture.m_Width+2)&&((rect.bottom - rect.top)> picture.m_Height+2))
1066 // image is smaller than the window
1067 Zoom = 100;
1069 else
1071 // image is bigger than the window
1072 int xscale = (rect.right-rect.left-2)*100/picture.m_Width;
1073 int yscale = (rect.bottom-rect.top-2)*100/picture.m_Height;
1074 Zoom = min(yscale, xscale);
1076 if (pSecondPic)
1078 if (((rect.right - rect.left) > pSecondPic->m_Width+2)&&((rect.bottom - rect.top)> pSecondPic->m_Height+2))
1080 // image is smaller than the window
1081 if (pTheOtherPic)
1082 pTheOtherPic->SetZoom(min(100, Zoom), false);
1084 else
1086 // image is bigger than the window
1087 int xscale = (rect.right-rect.left-2)*100/pSecondPic->m_Width;
1088 int yscale = (rect.bottom-rect.top-2)*100/pSecondPic->m_Height;
1089 if (pTheOtherPic)
1090 pTheOtherPic->SetZoom(min(yscale, xscale), false);
1092 nHSecondScrollPos = 0;
1093 nVSecondScrollPos = 0;
1095 SetZoom(Zoom, false);
1097 CenterImage();
1098 PositionChildren();
1099 InvalidateRect(*this, nullptr, TRUE);
1102 void CPicWindow::CenterImage()
1104 RECT rect;
1105 GetClientRectWithScrollbars(&rect);
1106 long width = picture.m_Width*picscale/100 + 2;
1107 long height = picture.m_Height*picscale/100 + 2;
1108 if (pSecondPic && pTheOtherPic)
1110 width = max(width, pSecondPic->m_Width*pTheOtherPic->GetZoom()/100 + 2);
1111 height = max(height, pSecondPic->m_Height*pTheOtherPic->GetZoom()/100 + 2);
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, transparentColor);
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 RECT border;
1169 border.left = picrect.left-1;
1170 border.top = picrect.top-1;
1171 border.right = picrect.right+1;
1172 border.bottom = picrect.bottom+1;
1174 HPEN hPen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DDKSHADOW));
1175 HPEN hOldPen = (HPEN)SelectObject(hdc, hPen);
1176 MoveToEx(hdc, border.left, border.top, nullptr);
1177 LineTo(hdc, border.left, border.bottom);
1178 LineTo(hdc, border.right, border.bottom);
1179 LineTo(hdc, border.right, border.top);
1180 LineTo(hdc, border.left, border.top);
1181 SelectObject(hdc, hOldPen);
1182 DeleteObject(hPen);
1185 void CPicWindow::Paint(HWND hwnd)
1187 PAINTSTRUCT ps;
1188 HDC hdc;
1189 RECT rect, fullrect;
1191 GetUpdateRect(hwnd, &rect, FALSE);
1192 if (IsRectEmpty(&rect))
1193 return;
1195 ::GetClientRect(*this, &fullrect);
1196 hdc = BeginPaint(hwnd, &ps);
1198 // Exclude the alpha control and button
1199 if ((pSecondPic)&&(m_blend == BLEND_ALPHA))
1200 ExcludeClipRect(hdc, 0, m_inforect.top-4, SLIDER_WIDTH, m_inforect.bottom+4);
1202 CMyMemDC memDC(hdc);
1203 if ((pSecondPic)&&(m_blend != BLEND_ALPHA))
1205 // erase the place where the alpha slider would be
1206 ::SetBkColor(memDC, transparentColor);
1207 RECT bounds = {0, m_inforect.top-4, SLIDER_WIDTH, m_inforect.bottom+4};
1208 ::ExtTextOut(memDC, 0, 0, ETO_OPAQUE, &bounds, nullptr, 0, nullptr);
1211 GetClientRect(&rect);
1212 if (bValid)
1214 ShowPicWithBorder(memDC, rect, picture, picscale);
1215 if (pSecondPic)
1217 HDC secondhdc = CreateCompatibleDC(hdc);
1218 HBITMAP hBitmap = CreateCompatibleBitmap(hdc, rect.right - rect.left, rect.bottom - rect.top);
1219 HBITMAP hOldBitmap = (HBITMAP)SelectObject(secondhdc, hBitmap);
1220 SetWindowOrgEx(secondhdc, rect.left, rect.top, nullptr);
1222 if ((pSecondPic)&&(m_blend != BLEND_ALPHA))
1224 // erase the place where the alpha slider would be
1225 ::SetBkColor(secondhdc, transparentColor);
1226 RECT bounds = {0, m_inforect.top-4, SLIDER_WIDTH, m_inforect.bottom+4};
1227 ::ExtTextOut(secondhdc, 0, 0, ETO_OPAQUE, &bounds, nullptr, 0, nullptr);
1229 if (pTheOtherPic)
1230 ShowPicWithBorder(secondhdc, rect, *pSecondPic, pTheOtherPic->GetZoom());
1232 if (m_blend == BLEND_ALPHA)
1234 BLENDFUNCTION blender;
1235 blender.AlphaFormat = 0;
1236 blender.BlendFlags = 0;
1237 blender.BlendOp = AC_SRC_OVER;
1238 blender.SourceConstantAlpha = (BYTE)(blendAlpha*255);
1239 AlphaBlend(memDC,
1240 rect.left,
1241 rect.top,
1242 rect.right-rect.left,
1243 rect.bottom-rect.top,
1244 secondhdc,
1245 rect.left,
1246 rect.top,
1247 rect.right-rect.left,
1248 rect.bottom-rect.top,
1249 blender);
1251 else if (m_blend == BLEND_XOR)
1253 BitBlt(memDC,
1254 rect.left,
1255 rect.top,
1256 rect.right-rect.left,
1257 rect.bottom-rect.top,
1258 secondhdc,
1259 rect.left,
1260 rect.top,
1261 //rect.right-rect.left,
1262 //rect.bottom-rect.top,
1263 SRCINVERT);
1264 InvertRect(memDC, &rect);
1266 SelectObject(secondhdc, hOldBitmap);
1267 DeleteObject(hBitmap);
1268 DeleteDC(secondhdc);
1270 else if (bDragging && pTheOtherPic && !bLinkedPositions)
1272 // when dragging, show lines indicating the position of the other image
1273 HPEN hPen = CreatePen(PS_SOLID, 1, GetSysColor(/*COLOR_ACTIVEBORDER*/COLOR_HIGHLIGHT));
1274 HPEN hOldPen = (HPEN)SelectObject(memDC, hPen);
1275 int xpos = rect.left - pTheOtherPic->nHScrollPos - 1;
1276 MoveToEx(memDC, xpos, rect.top, nullptr);
1277 LineTo(memDC, xpos, rect.bottom);
1278 xpos = rect.left - pTheOtherPic->nHScrollPos + pTheOtherPic->picture.m_Width*pTheOtherPic->GetZoom()/100 + 1;
1279 if (bFitWidths && m_linkedWidth)
1280 xpos = rect.left + pTheOtherPic->m_linkedWidth + 1;
1281 MoveToEx(memDC, xpos, rect.top, nullptr);
1282 LineTo(memDC, xpos, rect.bottom);
1284 int ypos = rect.top - pTheOtherPic->nVScrollPos - 1;
1285 MoveToEx(memDC, rect.left, ypos, nullptr);
1286 LineTo(memDC, rect.right, ypos);
1287 ypos = rect.top - pTheOtherPic->nVScrollPos + pTheOtherPic->picture.m_Height*pTheOtherPic->GetZoom()/100 + 1;
1288 if (bFitHeights && m_linkedHeight)
1289 ypos = rect.top - pTheOtherPic->m_linkedHeight + 1;
1290 MoveToEx(memDC, rect.left, ypos, nullptr);
1291 LineTo(memDC, rect.right, ypos);
1293 SelectObject(memDC, hOldPen);
1294 DeleteObject(hPen);
1297 int sliderwidth = 0;
1298 if ((pSecondPic)&&(m_blend == BLEND_ALPHA))
1299 sliderwidth = SLIDER_WIDTH;
1300 m_inforect.left = rect.left+4+sliderwidth;
1301 m_inforect.top = rect.top;
1302 m_inforect.right = rect.right+sliderwidth;
1303 m_inforect.bottom = rect.bottom;
1305 SetBkColor(memDC, transparentColor);
1306 if (bShowInfo)
1308 auto infostring = std::make_unique<TCHAR[]>(8192);
1309 BuildInfoString(infostring.get(), 8192, false);
1310 // set the font
1311 NONCLIENTMETRICS metrics = {0};
1312 metrics.cbSize = sizeof(NONCLIENTMETRICS);
1314 SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &metrics, FALSE);
1315 HFONT hFont = CreateFontIndirect(&metrics.lfStatusFont);
1316 HFONT hFontOld = (HFONT)SelectObject(memDC, (HGDIOBJ)hFont);
1317 // find out how big the rectangle for the text has to be
1318 DrawText(memDC, infostring.get(), -1, &m_inforect, DT_EDITCONTROL | DT_EXPANDTABS | DT_LEFT | DT_VCENTER | DT_CALCRECT);
1320 // the text should be drawn with a four pixel offset to the window borders
1321 m_inforect.top = rect.bottom - (m_inforect.bottom-m_inforect.top) - 4;
1322 m_inforect.bottom = rect.bottom-4;
1324 // first draw an edge rectangle
1325 RECT edgerect;
1326 edgerect.left = m_inforect.left-4;
1327 edgerect.top = m_inforect.top-4;
1328 edgerect.right = m_inforect.right+4;
1329 edgerect.bottom = m_inforect.bottom+4;
1330 ::ExtTextOut(memDC, 0, 0, ETO_OPAQUE, &edgerect, nullptr, 0, nullptr);
1331 DrawEdge(memDC, &edgerect, EDGE_BUMP, BF_RECT | BF_SOFT);
1333 SetTextColor(memDC, GetSysColor(COLOR_WINDOWTEXT));
1334 DrawText(memDC, infostring.get(), -1, &m_inforect, DT_EDITCONTROL | DT_EXPANDTABS | DT_LEFT | DT_VCENTER);
1335 SelectObject(memDC, (HGDIOBJ)hFontOld);
1336 DeleteObject(hFont);
1339 else
1341 SetBkColor(memDC, ::GetSysColor(COLOR_WINDOW));
1342 SetTextColor(memDC, ::GetSysColor(COLOR_WINDOWTEXT));
1343 ::ExtTextOut(memDC, 0, 0, ETO_OPAQUE, &rect, nullptr, 0, nullptr);
1344 SIZE stringsize;
1345 ResString str = ResString(hResource, IDS_INVALIDIMAGEINFO);
1347 // set the font
1348 NONCLIENTMETRICS metrics = {0};
1349 metrics.cbSize = sizeof(NONCLIENTMETRICS);
1350 SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &metrics, FALSE);
1351 HFONT hFont = CreateFontIndirect(&metrics.lfStatusFont);
1352 HFONT hFontOld = (HFONT)SelectObject(memDC, (HGDIOBJ)hFont);
1354 if (GetTextExtentPoint32(memDC, str, (int)wcslen(str), &stringsize))
1356 int nStringLength = stringsize.cx;
1358 ExtTextOut(memDC,
1359 max(rect.left + ((rect.right-rect.left)-nStringLength)/2, 1),
1360 rect.top + ((rect.bottom-rect.top) - stringsize.cy)/2,
1361 ETO_CLIPPED,
1362 &rect,
1363 str,
1364 (UINT)wcslen(str),
1365 nullptr);
1367 SelectObject(memDC, (HGDIOBJ)hFontOld);
1368 DeleteObject(hFont);
1370 DrawViewTitle(memDC, &fullrect);
1372 EndPaint(hwnd, &ps);
1375 bool CPicWindow::CreateButtons()
1377 // Ensure that the common control DLL is loaded.
1378 INITCOMMONCONTROLSEX icex;
1379 icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
1380 icex.dwICC = ICC_BAR_CLASSES | ICC_WIN95_CLASSES;
1381 InitCommonControlsEx(&icex);
1383 hwndLeftBtn = CreateWindowEx(0,
1384 L"BUTTON",
1385 (LPCTSTR)nullptr,
1386 WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_ICON | BS_FLAT,
1387 0, 0, 0, 0,
1388 (HWND)*this,
1389 (HMENU)LEFTBUTTON_ID,
1390 hResource,
1391 nullptr);
1392 if (hwndLeftBtn == INVALID_HANDLE_VALUE)
1393 return false;
1394 int iconWidth = GetSystemMetrics(SM_CXSMICON);
1395 int iconHeight = GetSystemMetrics(SM_CYSMICON);
1396 hLeft = (HICON)LoadImage(hResource, MAKEINTRESOURCE(IDI_BACKWARD), IMAGE_ICON, iconWidth, iconHeight, LR_LOADTRANSPARENT);
1397 SendMessage(hwndLeftBtn, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hLeft);
1398 hwndRightBtn = CreateWindowEx(0,
1399 L"BUTTON",
1400 (LPCTSTR)nullptr,
1401 WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_ICON | BS_FLAT,
1402 0, 0, 0, 0,
1403 *this,
1404 (HMENU)RIGHTBUTTON_ID,
1405 hResource,
1406 nullptr);
1407 if (hwndRightBtn == INVALID_HANDLE_VALUE)
1408 return false;
1409 hRight = (HICON)LoadImage(hResource, MAKEINTRESOURCE(IDI_FORWARD), IMAGE_ICON, iconWidth, iconHeight, LR_LOADTRANSPARENT);
1410 SendMessage(hwndRightBtn, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hRight);
1411 hwndPlayBtn = CreateWindowEx(0,
1412 L"BUTTON",
1413 (LPCTSTR)nullptr,
1414 WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_ICON | BS_FLAT,
1415 0, 0, 0, 0,
1416 *this,
1417 (HMENU)PLAYBUTTON_ID,
1418 hResource,
1419 nullptr);
1420 if (hwndPlayBtn == INVALID_HANDLE_VALUE)
1421 return false;
1422 hPlay = (HICON)LoadImage(hResource, MAKEINTRESOURCE(IDI_START), IMAGE_ICON, iconWidth, iconHeight, LR_LOADTRANSPARENT);
1423 hStop = (HICON)LoadImage(hResource, MAKEINTRESOURCE(IDI_STOP), IMAGE_ICON, iconWidth, iconHeight, LR_LOADTRANSPARENT);
1424 SendMessage(hwndPlayBtn, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hPlay);
1425 hwndAlphaToggleBtn = CreateWindowEx(0,
1426 L"BUTTON",
1427 (LPCTSTR)nullptr,
1428 WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_ICON | BS_FLAT | BS_NOTIFY | BS_PUSHLIKE,
1429 0, 0, 0, 0,
1430 (HWND)*this,
1431 (HMENU)ALPHATOGGLEBUTTON_ID,
1432 hResource,
1433 nullptr);
1434 if (hwndAlphaToggleBtn == INVALID_HANDLE_VALUE)
1435 return false;
1436 hAlphaToggle = (HICON)LoadImage(hResource, MAKEINTRESOURCE(IDI_ALPHATOGGLE), IMAGE_ICON, iconWidth, iconHeight, LR_LOADTRANSPARENT);
1437 SendMessage(hwndAlphaToggleBtn, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hAlphaToggle);
1439 TOOLINFO ti = {0};
1440 ti.cbSize = sizeof(TOOLINFO);
1441 ti.uFlags = TTF_IDISHWND|TTF_SUBCLASS;
1442 ti.hwnd = *this;
1443 ti.hinst = hResource;
1444 ti.uId = (UINT_PTR)hwndAlphaToggleBtn;
1445 ti.lpszText = LPSTR_TEXTCALLBACK;
1446 // ToolTip control will cover the whole window
1447 ti.rect.left = 0;
1448 ti.rect.top = 0;
1449 ti.rect.right = 0;
1450 ti.rect.bottom = 0;
1451 SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &ti);
1452 ResString sSelect(hResource, IDS_SELECT);
1453 hwndSelectBtn = CreateWindowEx(0,
1454 L"BUTTON",
1455 sSelect,
1456 WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
1457 0, 0, 0, 0,
1458 *this,
1459 (HMENU)SELECTBUTTON_ID,
1460 hResource,
1461 nullptr);
1462 if (hwndPlayBtn == INVALID_HANDLE_VALUE)
1463 return false;
1465 return true;
1468 void CPicWindow::PositionChildren()
1470 RECT rect;
1471 ::GetClientRect(*this, &rect);
1472 if (HasMultipleImages())
1474 int iconWidth = GetSystemMetrics(SM_CXSMICON);
1475 int iconHeight = GetSystemMetrics(SM_CYSMICON);
1476 SetWindowPos(hwndLeftBtn, HWND_TOP, rect.left + iconWidth / 4, rect.top + HEADER_HEIGHT + (HEADER_HEIGHT-iconHeight)/2, iconWidth, iconHeight, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
1477 SetWindowPos(hwndRightBtn, HWND_TOP, rect.left + iconWidth + iconWidth / 2, rect.top + HEADER_HEIGHT + (HEADER_HEIGHT - iconHeight) / 2, iconWidth, iconHeight, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
1478 if (nFrames > 1)
1479 SetWindowPos(hwndPlayBtn, HWND_TOP, rect.left + iconWidth * 2 + iconWidth / 2, rect.top + HEADER_HEIGHT + (HEADER_HEIGHT - iconHeight) / 2, iconWidth, iconHeight, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
1480 else
1481 ShowWindow(hwndPlayBtn, SW_HIDE);
1483 else
1485 ShowWindow(hwndLeftBtn, SW_HIDE);
1486 ShowWindow(hwndRightBtn, SW_HIDE);
1487 ShowWindow(hwndPlayBtn, SW_HIDE);
1489 if (bSelectionMode)
1490 SetWindowPos(hwndSelectBtn, HWND_TOP, rect.right-100, rect.bottom-HEADER_HEIGHT, 100, HEADER_HEIGHT, SWP_FRAMECHANGED|SWP_SHOWWINDOW);
1491 else
1492 ShowWindow(hwndSelectBtn, SW_HIDE);
1493 PositionTrackBar();
1496 bool CPicWindow::HasMultipleImages()
1498 return (((nDimensions > 1) || (nFrames > 1)) && (!pSecondPic));
1501 void CPicWindow::CreateTrackbar(HWND hwndParent)
1503 HWND hwndTrack = CreateWindowEx(
1504 0, // no extended styles
1505 TRACKBAR_CLASS, // class name
1506 L"Trackbar Control", // title (caption)
1507 WS_CHILD | WS_VISIBLE | TBS_VERT | TBS_TOOLTIPS | TBS_AUTOTICKS, // style
1508 10, 10, // position
1509 200, 30, // size
1510 hwndParent, // parent window
1511 (HMENU)TRACKBAR_ID, // control identifier
1512 hInst, // instance
1513 nullptr // no WM_CREATE parameter
1516 SendMessage(hwndTrack, TBM_SETRANGE,
1517 (WPARAM) TRUE, // redraw flag
1518 (LPARAM) MAKELONG(0, 16)); // min. & max. positions
1519 SendMessage(hwndTrack, TBM_SETTIPSIDE,
1520 (WPARAM) TBTS_TOP,
1521 (LPARAM) 0);
1523 m_AlphaSlider.ConvertTrackbarToNice(hwndTrack);
1526 void CPicWindow::BuildInfoString(TCHAR * buf, int size, bool bTooltip)
1528 // Unfortunately, we need two different strings for the tooltip
1529 // and the info box. Because the tooltips use a different tab size
1530 // than ExtTextOut(), and to keep the output aligned we therefore
1531 // need two different strings.
1532 // Note: some translations could end up with two identical strings, but
1533 // in English we need two - even if we wouldn't need two in English, some
1534 // translation might then need two again.
1535 if (pSecondPic && pTheOtherPic)
1537 swprintf_s(buf, size,
1538 (TCHAR const *)ResString(hResource, bTooltip ? IDS_DUALIMAGEINFOTT : IDS_DUALIMAGEINFO),
1539 picture.GetFileSizeAsText().c_str(), picture.GetFileSizeAsText(false).c_str(),
1540 picture.m_Width, picture.m_Height,
1541 picture.GetHorizontalResolution(), picture.GetVerticalResolution(),
1542 picture.m_ColorDepth,
1543 (UINT)GetZoom(),
1544 pSecondPic->GetFileSizeAsText().c_str(), pSecondPic->GetFileSizeAsText(false).c_str(),
1545 pSecondPic->m_Width, pSecondPic->m_Height,
1546 pSecondPic->GetHorizontalResolution(), pSecondPic->GetVerticalResolution(),
1547 pSecondPic->m_ColorDepth,
1548 (UINT)pTheOtherPic->GetZoom());
1550 else
1552 swprintf_s(buf, size,
1553 (TCHAR const *)ResString(hResource, bTooltip ? IDS_IMAGEINFOTT : IDS_IMAGEINFO),
1554 picture.GetFileSizeAsText().c_str(), picture.GetFileSizeAsText(false).c_str(),
1555 picture.m_Width, picture.m_Height,
1556 picture.GetHorizontalResolution(), picture.GetVerticalResolution(),
1557 picture.m_ColorDepth,
1558 (UINT)GetZoom());
1562 void CPicWindow::SetZoomToWidth( long width )
1564 m_linkedWidth = width;
1565 if (picture.m_Width)
1567 int zoom = width*100/picture.m_Width;
1568 SetZoom(zoom, false, true);
1572 void CPicWindow::SetZoomToHeight( long height )
1574 m_linkedHeight = height;
1575 if (picture.m_Height)
1577 int zoom = height*100/picture.m_Height;
1578 SetZoom(zoom, false, true);