Use static method
[TortoiseGit.git] / src / TortoiseIDiff / PicWindow.cpp
blobff6da65be18d640c0eca5fac656261345310e196
1 // TortoiseIDiff - an image diff viewer in TortoiseSVN
3 // Copyright (C) 2006-2012 - TortoiseSVN
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "stdafx.h"
20 #include <shellapi.h>
21 #include <CommCtrl.h>
22 #include "PicWindow.h"
23 #include <math.h>
24 #include "SysInfo.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(NULL, IDC_ARROW);
42 wcx.lpszClassName = _T("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(WS_EX_TOPMOST,
92 TOOLTIPS_CLASS,
93 NULL,
94 WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
95 CW_USEDEFAULT,
96 CW_USEDEFAULT,
97 CW_USEDEFAULT,
98 CW_USEDEFAULT,
99 hwnd,
100 NULL,
101 hResource,
102 NULL
105 SetWindowPos(hwndTT,
106 HWND_TOPMOST,
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, NULL, 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, NULL);
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)
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)
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));
209 if (bFitSizes)
210 pTheOtherPic->OnMouseWheel(GET_KEYSTATE_WPARAM(wParam), GET_WHEEL_DELTA_WPARAM(wParam));
212 break;
213 case WM_MOUSEHWHEEL:
215 OnMouseWheel(GET_KEYSTATE_WPARAM(wParam)|MK_SHIFT, GET_WHEEL_DELTA_WPARAM(wParam));
216 if (bFitSizes)
217 pTheOtherPic->OnMouseWheel(GET_KEYSTATE_WPARAM(wParam)|MK_SHIFT, GET_WHEEL_DELTA_WPARAM(wParam));
219 break;
220 case WM_LBUTTONDOWN:
221 SetFocus(*this);
222 ptPanStart.x = GET_X_LPARAM(lParam);
223 ptPanStart.y = GET_Y_LPARAM(lParam);
224 startVScrollPos = nVScrollPos;
225 startHScrollPos = nHScrollPos;
226 startVSecondScrollPos = nVSecondScrollPos;
227 startHSecondScrollPos = nHSecondScrollPos;
228 SetCapture(*this);
229 break;
230 case WM_LBUTTONUP:
231 ReleaseCapture();
232 break;
233 case WM_MOUSELEAVE:
234 ptPanStart.x = -1;
235 ptPanStart.y = -1;
236 m_lastTTPos.x = 0;
237 m_lastTTPos.y = 0;
238 SendMessage(hwndTT, TTM_TRACKACTIVATE, FALSE, 0);
239 break;
240 case WM_MOUSEMOVE:
242 mevt.cbSize = sizeof(TRACKMOUSEEVENT);
243 mevt.dwFlags = TME_LEAVE;
244 mevt.dwHoverTime = HOVER_DEFAULT;
245 mevt.hwndTrack = *this;
246 ::TrackMouseEvent(&mevt);
247 POINT pt = {((int)(short)LOWORD(lParam)), ((int)(short)HIWORD(lParam))};
248 if (pt.y < HEADER_HEIGHT)
250 ClientToScreen(*this, &pt);
251 if ((abs(m_lastTTPos.x - pt.x) > 20)||(abs(m_lastTTPos.y - pt.y) > 10))
253 m_lastTTPos = pt;
254 pt.x += 15;
255 pt.y += 15;
256 SendMessage(hwndTT, TTM_TRACKPOSITION, 0, MAKELONG(pt.x, pt.y));
257 TOOLINFO ti = {0};
258 ti.cbSize = sizeof(TOOLINFO);
259 ti.hwnd = *this;
260 ti.uId = 0;
261 SendMessage(hwndTT, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti);
264 else
266 SendMessage(hwndTT, TTM_TRACKACTIVATE, FALSE, 0);
267 m_lastTTPos.x = 0;
268 m_lastTTPos.y = 0;
270 if ((wParam & MK_LBUTTON) &&
271 (ptPanStart.x >= 0) &&
272 (ptPanStart.y >= 0))
274 // pan the image
275 int xPos = GET_X_LPARAM(lParam);
276 int yPos = GET_Y_LPARAM(lParam);
278 if (wParam & MK_CONTROL)
280 nHSecondScrollPos = startHSecondScrollPos + (ptPanStart.x - xPos);
281 nVSecondScrollPos = startVSecondScrollPos + (ptPanStart.y - yPos);
283 else if (wParam & MK_SHIFT)
285 nHScrollPos = startHScrollPos + (ptPanStart.x - xPos);
286 nVScrollPos = startVScrollPos + (ptPanStart.y - yPos);
288 else
290 nHSecondScrollPos = startHSecondScrollPos + (ptPanStart.x - xPos);
291 nVSecondScrollPos = startVSecondScrollPos + (ptPanStart.y - yPos);
292 nHScrollPos = startHScrollPos + (ptPanStart.x - xPos);
293 nVScrollPos = startVScrollPos + (ptPanStart.y - yPos);
295 SetupScrollBars();
296 InvalidateRect(*this, NULL, TRUE);
297 UpdateWindow(*this);
298 if ((bLinkedPositions)&&((wParam & MK_SHIFT)==0))
300 pTheOtherPic->nHScrollPos = nHScrollPos;
301 pTheOtherPic->nVScrollPos = nVScrollPos;
302 pTheOtherPic->SetupScrollBars();
303 InvalidateRect(*pTheOtherPic, NULL, TRUE);
304 UpdateWindow(*pTheOtherPic);
308 break;
309 case WM_SETCURSOR:
311 // we show a hand cursor if the image can be dragged,
312 // and a hand-down cursor if the image is currently dragged
313 if ((*this == (HWND)wParam)&&(LOWORD(lParam)==HTCLIENT))
315 RECT rect;
316 GetClientRect(&rect);
317 LONG width = picture.m_Width;
318 LONG height = picture.m_Height;
319 if (pSecondPic)
321 width = max(width, pSecondPic->m_Width);
322 height = max(height, pSecondPic->m_Height);
325 if ((GetKeyState(VK_LBUTTON)&0x8000)||(HIWORD(lParam) == WM_LBUTTONDOWN))
327 SetCursor(curHandDown);
329 else
331 SetCursor(curHand);
333 return TRUE;
335 return DefWindowProc(hwnd, uMsg, wParam, lParam);
337 break;
338 case WM_DROPFILES:
340 HDROP hDrop = (HDROP)wParam;
341 TCHAR szFileName[MAX_PATH];
342 // we only use the first file dropped (if multiple files are dropped)
343 if (DragQueryFile(hDrop, 0, szFileName, _countof(szFileName)))
345 SetPic(szFileName, _T(""), bMainPic);
346 FitImageInWindow();
347 InvalidateRect(*this, NULL, TRUE);
350 break;
351 case WM_COMMAND:
353 switch (LOWORD(wParam))
355 case LEFTBUTTON_ID:
357 PrevImage();
358 if (bLinkedPositions)
359 pTheOtherPic->PrevImage();
360 return 0;
362 break;
363 case RIGHTBUTTON_ID:
365 NextImage();
366 if (bLinkedPositions)
367 pTheOtherPic->NextImage();
368 return 0;
370 break;
371 case PLAYBUTTON_ID:
373 bPlaying = !bPlaying;
374 Animate(bPlaying);
375 if (bLinkedPositions)
376 pTheOtherPic->Animate(bPlaying);
377 return 0;
379 break;
380 case ALPHATOGGLEBUTTON_ID:
382 WORD msg = HIWORD(wParam);
383 switch (msg)
385 case BN_DOUBLECLICKED:
387 SendMessage(hwndAlphaToggleBtn, BM_SETSTATE, 1, 0);
388 SetTimer(*this, ID_ALPHATOGGLETIMER, 1000, NULL);
390 break;
391 case BN_CLICKED:
392 KillTimer(*this, ID_ALPHATOGGLETIMER);
393 ToggleAlpha();
394 break;
396 return 0;
398 break;
399 case BLENDALPHA_ID:
401 m_blend = BLEND_ALPHA;
402 PositionTrackBar();
403 InvalidateRect(*this, NULL, TRUE);
405 break;
406 case BLENDXOR_ID:
408 m_blend = BLEND_XOR;
409 PositionTrackBar();
410 InvalidateRect(*this, NULL, TRUE);
412 break;
415 break;
416 case WM_TIMER:
418 switch (wParam)
420 case ID_ANIMATIONTIMER:
422 nCurrentFrame++;
423 if (nCurrentFrame > picture.GetNumberOfFrames(0))
424 nCurrentFrame = 1;
425 long delay = picture.SetActiveFrame(nCurrentFrame);
426 delay = max(100, delay);
427 SetTimer(*this, ID_ANIMATIONTIMER, delay, NULL);
428 InvalidateRect(*this, NULL, FALSE);
430 break;
431 case TIMER_ALPHASLIDER:
433 SetBlendAlpha(m_blend, SendMessage(m_AlphaSlider.GetWindow(), TBM_GETPOS, 0, 0)/16.0f);
434 KillTimer(*this, TIMER_ALPHASLIDER);
436 break;
437 case ID_ALPHATOGGLETIMER:
439 ToggleAlpha();
441 break;
444 break;
445 case WM_NOTIFY:
447 LPNMHDR pNMHDR = (LPNMHDR)lParam;
448 if (pNMHDR->code == TTN_GETDISPINFO)
450 if (pNMHDR->hwndFrom == m_AlphaSlider.GetWindow())
452 LPTOOLTIPTEXT lpttt = (LPTOOLTIPTEXT) lParam;
453 lpttt->hinst = hResource;
454 TCHAR stringbuf[MAX_PATH] = {0};
455 _stprintf_s(stringbuf, _T("%i%% alpha"), (int)(SendMessage(m_AlphaSlider.GetWindow(),TBM_GETPOS,0,0)/16.0f*100.0f));
456 _tcscpy_s(lpttt->lpszText, 80, stringbuf);
458 else if (pNMHDR->idFrom == (UINT_PTR)hwndAlphaToggleBtn)
460 _stprintf_s(m_wszTip, (TCHAR const *)ResString(hResource, IDS_ALPHABUTTONTT), (int)(SendMessage(m_AlphaSlider.GetWindow(),TBM_GETPOS,0,0)/16.0f*100.0f));
461 if (pNMHDR->code == TTN_NEEDTEXTW)
463 NMTTDISPINFOW* pTTTW = (NMTTDISPINFOW*)pNMHDR;
464 pTTTW->lpszText = m_wszTip;
466 else
468 NMTTDISPINFOA* pTTTA = (NMTTDISPINFOA*)pNMHDR;
469 pTTTA->lpszText = m_szTip;
470 ::WideCharToMultiByte(CP_ACP, 0, m_wszTip, -1, m_szTip, 8192, NULL, NULL);
473 else
475 BuildInfoString(m_wszTip, _countof(m_wszTip), true);
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, NULL, NULL);
490 break;
491 case WM_DESTROY:
492 DestroyIcon(hLeft);
493 DestroyIcon(hRight);
494 DestroyIcon(hPlay);
495 DestroyIcon(hStop);
496 bWindowClosed = TRUE;
497 break;
498 default:
499 return DefWindowProc(hwnd, uMsg, wParam, lParam);
502 return 0;
505 void CPicWindow::NextImage()
507 nCurrentDimension++;
508 if (nCurrentDimension > picture.GetNumberOfDimensions())
509 nCurrentDimension = picture.GetNumberOfDimensions();
510 nCurrentFrame++;
511 if (nCurrentFrame > picture.GetNumberOfFrames(0))
512 nCurrentFrame = picture.GetNumberOfFrames(0);
513 picture.SetActiveFrame(nCurrentFrame >= nCurrentDimension ? nCurrentFrame : nCurrentDimension);
514 InvalidateRect(*this, NULL, FALSE);
515 PositionChildren();
518 void CPicWindow::PrevImage()
520 nCurrentDimension--;
521 if (nCurrentDimension < 1)
522 nCurrentDimension = 1;
523 nCurrentFrame--;
524 if (nCurrentFrame < 1)
525 nCurrentFrame = 1;
526 picture.SetActiveFrame(nCurrentFrame >= nCurrentDimension ? nCurrentFrame : nCurrentDimension);
527 InvalidateRect(*this, NULL, FALSE);
528 PositionChildren();
531 void CPicWindow::Animate(bool bStart)
533 if (bStart)
535 SendMessage(hwndPlayBtn, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hStop);
536 SetTimer(*this, ID_ANIMATIONTIMER, 0, NULL);
538 else
540 SendMessage(hwndPlayBtn, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hPlay);
541 KillTimer(*this, ID_ANIMATIONTIMER);
545 void CPicWindow::SetPic(tstring path, tstring title, bool bFirst)
547 bMainPic = bFirst;
548 picpath=path;pictitle=title;
549 picture.SetInterpolationMode(InterpolationModeHighQualityBicubic);
550 bValid = picture.Load(picpath);
551 nDimensions = picture.GetNumberOfDimensions();
552 if (nDimensions)
553 nFrames = picture.GetNumberOfFrames(0);
554 if (bValid)
556 picscale = 1.0;
557 PositionChildren();
558 InvalidateRect(*this, NULL, FALSE);
562 void CPicWindow::DrawViewTitle(HDC hDC, RECT * rect)
564 HFONT hFont = NULL;
565 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, _T("MS Shell Dlg"));
566 HFONT hFontOld = (HFONT)SelectObject(hDC, (HGDIOBJ)hFont);
568 RECT textrect;
569 textrect.left = rect->left;
570 textrect.top = rect->top;
571 textrect.right = rect->right;
572 textrect.bottom = rect->top + HEADER_HEIGHT;
573 if (HasMultipleImages())
574 textrect.bottom += HEADER_HEIGHT;
576 COLORREF crBk, crFg;
577 crBk = ::GetSysColor(COLOR_SCROLLBAR);
578 crFg = ::GetSysColor(COLOR_WINDOWTEXT);
579 SetBkColor(hDC, crBk);
580 ::ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &textrect, NULL, 0, NULL);
582 if (GetFocus() == *this)
583 DrawEdge(hDC, &textrect, EDGE_BUMP, BF_RECT);
584 else
585 DrawEdge(hDC, &textrect, EDGE_ETCHED, BF_RECT);
587 SetTextColor(hDC, crFg);
589 // use the path if no title is set.
590 tstring * title = pictitle.empty() ? &picpath : &pictitle;
592 tstring realtitle = *title;
593 tstring imgnumstring;
595 if (HasMultipleImages())
597 TCHAR buf[MAX_PATH];
598 if (nFrames > 1)
599 _stprintf_s(buf, (const TCHAR *)ResString(hResource, IDS_DIMENSIONSANDFRAMES), nCurrentFrame, nFrames);
600 else
601 _stprintf_s(buf, (const TCHAR *)ResString(hResource, IDS_DIMENSIONSANDFRAMES), nCurrentDimension, nDimensions);
602 imgnumstring = buf;
605 SIZE stringsize;
606 if (GetTextExtentPoint32(hDC, realtitle.c_str(), (int)realtitle.size(), &stringsize))
608 int nStringLength = stringsize.cx;
609 int texttop = pSecondPic ? textrect.top + (HEADER_HEIGHT/2) - stringsize.cy : textrect.top + (HEADER_HEIGHT/2) - stringsize.cy/2;
610 ExtTextOut(hDC,
611 max(textrect.left + ((textrect.right-textrect.left)-nStringLength)/2, 1),
612 texttop,
613 ETO_CLIPPED,
614 &textrect,
615 realtitle.c_str(),
616 (UINT)realtitle.size(),
617 NULL);
618 if (pSecondPic)
620 realtitle = (pictitle2.empty() ? picpath2 : pictitle2);
621 ExtTextOut(hDC,
622 max(textrect.left + ((textrect.right-textrect.left)-nStringLength)/2, 1),
623 texttop + stringsize.cy,
624 ETO_CLIPPED,
625 &textrect,
626 realtitle.c_str(),
627 (UINT)realtitle.size(),
628 NULL);
631 if (HasMultipleImages())
633 if (GetTextExtentPoint32(hDC, imgnumstring.c_str(), (int)imgnumstring.size(), &stringsize))
635 int nStringLength = stringsize.cx;
637 ExtTextOut(hDC,
638 max(textrect.left + ((textrect.right-textrect.left)-nStringLength)/2, 1),
639 textrect.top + HEADER_HEIGHT + (HEADER_HEIGHT/2) - stringsize.cy/2,
640 ETO_CLIPPED,
641 &textrect,
642 imgnumstring.c_str(),
643 (UINT)imgnumstring.size(),
644 NULL);
647 SelectObject(hDC, (HGDIOBJ)hFontOld);
648 DeleteObject(hFont);
651 void CPicWindow::SetupScrollBars()
653 RECT rect;
654 GetClientRect(&rect);
656 SCROLLINFO si = {sizeof(si)};
658 si.fMask = SIF_POS | SIF_PAGE | SIF_RANGE | SIF_DISABLENOSCROLL;
660 double width = double(picture.m_Width)*picscale;
661 double height = double(picture.m_Height)*picscale;
662 if (pSecondPic && pTheOtherPic)
664 width = max(width, double(pSecondPic->m_Width)*pTheOtherPic->GetZoom());
665 height = max(height, double(pSecondPic->m_Height)*pTheOtherPic->GetZoom());
668 bool bShowHScrollBar = (nHScrollPos > 0); // left of pic is left of window
669 bShowHScrollBar = bShowHScrollBar || (width-nHScrollPos > rect.right); // right of pic is outside right of window
670 bShowHScrollBar = bShowHScrollBar || (width+nHScrollPos > rect.right); // right of pic is outside right of window
671 bool bShowVScrollBar = (nVScrollPos > 0); // top of pic is above window
672 bShowVScrollBar = bShowVScrollBar || (height-nVScrollPos+rect.top > rect.bottom); // bottom of pic is below window
673 bShowVScrollBar = bShowVScrollBar || (height+nVScrollPos+rect.top > rect.bottom); // bottom of pic is below window
675 // if the image is smaller than the window, we don't need the scrollbars
676 ShowScrollBar(*this, SB_HORZ, bShowHScrollBar);
677 ShowScrollBar(*this, SB_VERT, bShowVScrollBar);
679 if (bShowVScrollBar)
681 si.nPos = nVScrollPos;
682 si.nPage = rect.bottom-rect.top;
683 if (height < rect.bottom-rect.top)
685 if (nVScrollPos > 0)
687 si.nMin = 0;
688 si.nMax = rect.bottom+nVScrollPos-rect.top;
690 else
692 si.nMin = nVScrollPos;
693 si.nMax = int(height);
696 else
698 if (nVScrollPos > 0)
700 si.nMin = 0;
701 si.nMax = int(max(height, rect.bottom+nVScrollPos-rect.top));
703 else
705 si.nMin = 0;
706 si.nMax = int(height-nVScrollPos);
709 SetScrollInfo(*this, SB_VERT, &si, TRUE);
712 if (bShowHScrollBar)
714 si.nPos = nHScrollPos;
715 si.nPage = rect.right-rect.left;
716 if (width < rect.right-rect.left)
718 if (nHScrollPos > 0)
720 si.nMin = 0;
721 si.nMax = rect.right+nHScrollPos-rect.left;
723 else
725 si.nMin = nHScrollPos;
726 si.nMax = int(width);
729 else
731 if (nHScrollPos > 0)
733 si.nMin = 0;
734 si.nMax = int(max(width, rect.right+nHScrollPos-rect.left));
736 else
738 si.nMin = 0;
739 si.nMax = int(width-nHScrollPos);
742 SetScrollInfo(*this, SB_HORZ, &si, TRUE);
745 PositionChildren();
748 void CPicWindow::OnVScroll(UINT nSBCode, UINT nPos)
750 RECT rect;
751 GetClientRect(&rect);
753 switch (nSBCode)
755 case SB_BOTTOM:
756 nVScrollPos = LONG(double(picture.GetHeight())*picscale);
757 break;
758 case SB_TOP:
759 nVScrollPos = 0;
760 break;
761 case SB_LINEDOWN:
762 nVScrollPos++;
763 break;
764 case SB_LINEUP:
765 nVScrollPos--;
766 break;
767 case SB_PAGEDOWN:
768 nVScrollPos += (rect.bottom-rect.top);
769 break;
770 case SB_PAGEUP:
771 nVScrollPos -= (rect.bottom-rect.top);
772 break;
773 case SB_THUMBPOSITION:
774 nVScrollPos = nPos;
775 break;
776 case SB_THUMBTRACK:
777 nVScrollPos = nPos;
778 break;
779 default:
780 return;
782 LONG height = LONG(double(picture.GetHeight())*picscale);
783 if (pSecondPic)
785 height = max(height, LONG(double(pSecondPic->GetHeight())*picscale));
786 nVSecondScrollPos = nVScrollPos;
788 SetupScrollBars();
789 PositionChildren();
790 InvalidateRect(*this, NULL, TRUE);
793 void CPicWindow::OnHScroll(UINT nSBCode, UINT nPos)
795 RECT rect;
796 GetClientRect(&rect);
798 switch (nSBCode)
800 case SB_RIGHT:
801 nHScrollPos = LONG(double(picture.GetWidth())*picscale);
802 break;
803 case SB_LEFT:
804 nHScrollPos = 0;
805 break;
806 case SB_LINERIGHT:
807 nHScrollPos++;
808 break;
809 case SB_LINELEFT:
810 nHScrollPos--;
811 break;
812 case SB_PAGERIGHT:
813 nHScrollPos += (rect.right-rect.left);
814 break;
815 case SB_PAGELEFT:
816 nHScrollPos -= (rect.right-rect.left);
817 break;
818 case SB_THUMBPOSITION:
819 nHScrollPos = nPos;
820 break;
821 case SB_THUMBTRACK:
822 nHScrollPos = nPos;
823 break;
824 default:
825 return;
827 LONG width = LONG(double(picture.GetWidth())*picscale);
828 if (pSecondPic)
830 width = max(width, LONG(double(pSecondPic->GetWidth())*picscale));
831 nHSecondScrollPos = nHScrollPos;
833 SetupScrollBars();
834 PositionChildren();
835 InvalidateRect(*this, NULL, TRUE);
838 void CPicWindow::OnMouseWheel(short fwKeys, short zDelta)
840 RECT rect;
841 GetClientRect(&rect);
842 LONG width = long(double(picture.m_Width)*picscale);
843 LONG height = long(double(picture.m_Height)*picscale);
844 if (pSecondPic)
846 width = max(width, long(double(pSecondPic->m_Width)*picscale));
847 height = max(height, long(double(pSecondPic->m_Height)*picscale));
849 if ((fwKeys & MK_SHIFT)&&(fwKeys & MK_CONTROL)&&(pSecondPic))
851 // ctrl+shift+wheel: change the alpha channel
852 float a = blendAlpha;
853 a -= float(zDelta)/120.0f/4.0f;
854 if (a < 0.0f)
855 a = 0.0f;
856 else if (a > 1.0f)
857 a = 1.0f;
858 SetBlendAlpha(m_blend, a);
860 else if (fwKeys & MK_SHIFT)
862 // shift means scrolling sideways
863 OnHScroll(SB_THUMBPOSITION, GetHPos()-zDelta);
864 if ((bLinkedPositions)&&(pTheOtherPic))
866 pTheOtherPic->OnHScroll(SB_THUMBPOSITION, pTheOtherPic->GetHPos()-zDelta);
869 else if (fwKeys & MK_CONTROL)
871 // control means adjusting the scale factor
872 Zoom(zDelta>0, true);
873 if ((bFitSizes)&&(pTheOtherPic)&&(!bOverlap))
874 pTheOtherPic->Zoom(zDelta>0, true);
875 PositionChildren();
876 InvalidateRect(*this, NULL, FALSE);
877 SetWindowPos(*this, NULL, 0, 0, 0, 0, SWP_FRAMECHANGED|SWP_NOSIZE|SWP_NOREPOSITION|SWP_NOMOVE);
879 else
881 OnVScroll(SB_THUMBPOSITION, GetVPos()-zDelta);
882 if ((bLinkedPositions)&&(pTheOtherPic))
884 pTheOtherPic->OnVScroll(SB_THUMBPOSITION, pTheOtherPic->GetVPos()-zDelta);
889 void CPicWindow::GetClientRect(RECT * pRect)
891 ::GetClientRect(*this, pRect);
892 pRect->top += HEADER_HEIGHT;
893 if (HasMultipleImages())
895 pRect->top += HEADER_HEIGHT;
897 if (pSecondPic)
898 pRect->left += SLIDER_WIDTH;
901 void CPicWindow::GetClientRectWithScrollbars(RECT * pRect)
903 GetClientRect(pRect);
904 ::GetWindowRect(*this, pRect);
905 pRect->right = pRect->right-pRect->left;
906 pRect->bottom = pRect->bottom-pRect->top;
907 pRect->top = 0;
908 pRect->left = 0;
909 pRect->top += HEADER_HEIGHT;
910 if (HasMultipleImages())
912 pRect->top += HEADER_HEIGHT;
914 if (pSecondPic)
915 pRect->left += SLIDER_WIDTH;
919 void CPicWindow::SetZoom(double dZoom, bool centermouse)
921 // Set the interpolation mode depending on zoom
922 double oldPicscale = picscale;
923 double oldOtherPicscale = picscale;
924 if (dZoom < 1.0)
925 { // Zoomed out, use high quality bicubic
926 picture.SetInterpolationMode(InterpolationModeHighQualityBicubic);
927 if (pSecondPic)
928 pSecondPic->SetInterpolationMode(InterpolationModeHighQualityBicubic);
930 else if (!((int)(dZoom*100.0)%100))
931 { // "Even" zoom sizes should be shown w-o any interpolation
932 picture.SetInterpolationMode(InterpolationModeNearestNeighbor);
933 if (pSecondPic)
934 pSecondPic->SetInterpolationMode(InterpolationModeNearestNeighbor);
936 else
937 { // Arbitrary zoomed in, use bilinear that is semi-smoothed
938 picture.SetInterpolationMode(InterpolationModeBilinear);
939 if (pSecondPic)
940 pSecondPic->SetInterpolationMode(InterpolationModeBilinear);
942 picscale = dZoom;
944 if ((bFitSizes)&&(bMainPic))
946 double width, height;
947 double zoomWidth, zoomHeight;
948 width = double(picture.m_Width)*dZoom;
949 height = double(picture.m_Height)*dZoom;
950 zoomWidth = width/double(pTheOtherPic->GetPic()->m_Width);
951 zoomHeight = height/double(pTheOtherPic->GetPic()->m_Height);
952 oldOtherPicscale = pTheOtherPic->GetZoom();
953 pTheOtherPic->SetZoom(min(zoomWidth, zoomHeight), false);
956 // adjust the scrollbar positions according to the new zoom and the
957 // mouse position: if possible, keep the pixel where the mouse pointer
958 // is at the same position after the zoom
959 POINT cpos;
960 DWORD ptW = GetMessagePos();
961 cpos.x = GET_X_LPARAM(ptW);
962 cpos.y = GET_Y_LPARAM(ptW);
963 ScreenToClient(*this, &cpos);
964 RECT clientrect;
965 GetClientRect(&clientrect);
966 if ((PtInRect(&clientrect, cpos))&&(centermouse))
968 // the mouse pointer is over our window
969 nHScrollPos = int(double(nHScrollPos + cpos.x)*(dZoom/oldPicscale))-cpos.x;
970 nVScrollPos = int(double(nVScrollPos + cpos.y)*(dZoom/oldPicscale))-cpos.y;
971 if (pTheOtherPic && bMainPic)
973 double otherzoom = pTheOtherPic->GetZoom();
974 nHSecondScrollPos = int(double(nHSecondScrollPos + cpos.x)*(otherzoom/oldOtherPicscale))-cpos.x;
975 nVSecondScrollPos = int(double(nVSecondScrollPos + cpos.y)*(otherzoom/oldOtherPicscale))-cpos.y;
978 else
980 nHScrollPos = int(double(nHScrollPos + ((clientrect.right-clientrect.left)/2))*(dZoom/oldPicscale))-((clientrect.right-clientrect.left)/2);
981 nVScrollPos = int(double(nVScrollPos + ((clientrect.bottom-clientrect.top)/2))*(dZoom/oldPicscale))-((clientrect.bottom-clientrect.top)/2);
982 if (pTheOtherPic && bMainPic)
984 double otherzoom = pTheOtherPic->GetZoom();
985 nHSecondScrollPos = int(double(nHSecondScrollPos + ((clientrect.right-clientrect.left)/2))*(otherzoom/oldOtherPicscale))-((clientrect.right-clientrect.left)/2);
986 nVSecondScrollPos = int(double(nVSecondScrollPos + ((clientrect.bottom-clientrect.top)/2))*(otherzoom/oldOtherPicscale))-((clientrect.bottom-clientrect.top)/2);
990 SetupScrollBars();
991 PositionChildren();
992 InvalidateRect(*this, NULL, TRUE);
995 void CPicWindow::Zoom(bool in, bool centermouse)
997 double zoomFactor;
999 // Find correct zoom factor and quantize picscale
1000 if (!in && picscale <= 0.2)
1002 picscale = 0.1;
1003 zoomFactor = 0;
1005 else if ((in && picscale < 1.0) || (!in && picscale <= 1.0))
1007 picscale = 0.1 * RoundDouble(picscale/0.1, 0); // Quantize to 0.1
1008 zoomFactor = 0.1;
1010 else if ((in && picscale < 2.0) || (!in && picscale <= 2.0))
1012 picscale = 0.25 * RoundDouble(picscale/0.25, 0); // Quantize to 0.25
1013 zoomFactor = 0.25;
1015 else
1017 picscale = RoundDouble(picscale,0);
1018 zoomFactor = 1;
1021 // Set zoom
1022 if (in)
1024 if ((pSecondPic)&&(!bFitSizes))
1025 pTheOtherPic->SetZoom(pTheOtherPic->GetZoom()+zoomFactor, false);
1026 SetZoom(picscale+zoomFactor, centermouse);
1028 else
1030 if ((pSecondPic)&&(!bFitSizes))
1031 pTheOtherPic->SetZoom(pTheOtherPic->GetZoom()-zoomFactor, false);
1032 SetZoom(picscale-zoomFactor, centermouse);
1036 double CPicWindow::RoundDouble(double doValue, int nPrecision)
1038 static const double doBase = 10.0;
1039 double doComplete5, doComplete5i;
1041 doComplete5 = doValue * pow(doBase, (double) (nPrecision + 1));
1043 if (doValue < 0.0)
1045 doComplete5 -= 5.0;
1047 else
1049 doComplete5 += 5.0;
1052 doComplete5 /= doBase;
1053 modf(doComplete5, &doComplete5i);
1055 return doComplete5i / pow(doBase, (double) nPrecision);
1057 void CPicWindow::FitImageInWindow()
1059 RECT rect;
1061 GetClientRectWithScrollbars(&rect);
1063 if (rect.right-rect.left)
1065 double dZoom = 1.0;
1066 if (((rect.right - rect.left) > picture.m_Width+2)&&((rect.bottom - rect.top)> picture.m_Height+2))
1068 // image is smaller than the window
1069 dZoom = 1.0;
1071 else
1073 // image is bigger than the window
1074 double xscale = double(rect.right-rect.left-2)/double(picture.m_Width);
1075 double yscale = double(rect.bottom-rect.top-2)/double(picture.m_Height);
1076 dZoom = min(yscale, xscale);
1078 if (pSecondPic)
1080 if (((rect.right - rect.left) > pSecondPic->m_Width+2)&&((rect.bottom - rect.top)> pSecondPic->m_Height+2))
1082 // image is smaller than the window
1083 pTheOtherPic->SetZoom(min(1.0, dZoom), false);
1085 else
1087 // image is bigger than the window
1088 double xscale = double(rect.right-rect.left-2)/double(pSecondPic->m_Width);
1089 double yscale = double(rect.bottom-rect.top-2)/double(pSecondPic->m_Height);
1090 pTheOtherPic->SetZoom(min(yscale, xscale), false);
1092 nHSecondScrollPos = 0;
1093 nVSecondScrollPos = 0;
1095 SetZoom(dZoom, false);
1097 CenterImage();
1098 PositionChildren();
1099 InvalidateRect(*this, NULL, TRUE);
1102 void CPicWindow::CenterImage()
1104 RECT rect;
1105 GetClientRectWithScrollbars(&rect);
1106 double width = (double(picture.m_Width)*picscale) + 2.0;
1107 double height = (double(picture.m_Height)*picscale) + 2.0;
1108 if (pSecondPic)
1110 width = max(width, (double(pSecondPic->m_Width)*pTheOtherPic->GetZoom()) + 2.0);
1111 height = max(height, (double(pSecondPic->m_Height)*pTheOtherPic->GetZoom()) + 2.0);
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::FitSizes(bool bFit)
1133 bFitSizes = bFit;
1135 SetZoom(GetZoom(), false);
1138 void CPicWindow::ShowPicWithBorder(HDC hdc, const RECT &bounds, CPicture &pic, double scale)
1140 ::SetBkColor(hdc, transparentColor);
1141 ::ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &bounds, NULL, 0, NULL);
1143 RECT picrect;
1144 picrect.left = bounds.left - nHScrollPos;
1145 picrect.top = bounds.top - nVScrollPos;
1146 if ((!bLinkedPositions || bOverlap) && (pTheOtherPic) && (&pic != &picture))
1148 picrect.left = bounds.left - nHSecondScrollPos;
1149 picrect.top = bounds.top - nVSecondScrollPos;
1151 picrect.right = (picrect.left + LONG(double(pic.m_Width) * scale));
1152 picrect.bottom = (picrect.top + LONG(double(pic.m_Height) * scale));
1154 pic.Show(hdc, picrect);
1156 RECT border;
1157 border.left = picrect.left-1;
1158 border.top = picrect.top-1;
1159 border.right = picrect.right+1;
1160 border.bottom = picrect.bottom+1;
1162 HPEN hPen = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DDKSHADOW));
1163 HPEN hOldPen = (HPEN)SelectObject(hdc, hPen);
1164 MoveToEx(hdc, border.left, border.top, NULL);
1165 LineTo(hdc, border.left, border.bottom);
1166 LineTo(hdc, border.right, border.bottom);
1167 LineTo(hdc, border.right, border.top);
1168 LineTo(hdc, border.left, border.top);
1169 SelectObject(hdc, hOldPen);
1170 DeleteObject(hPen);
1173 void CPicWindow::Paint(HWND hwnd)
1175 PAINTSTRUCT ps;
1176 HDC hdc;
1177 RECT rect, fullrect;
1179 GetUpdateRect(hwnd, &rect, FALSE);
1180 if (IsRectEmpty(&rect))
1181 return;
1183 ::GetClientRect(*this, &fullrect);
1184 hdc = BeginPaint(hwnd, &ps);
1186 // Exclude the alpha control and button
1187 if ((pSecondPic)&&(m_blend == BLEND_ALPHA))
1188 ExcludeClipRect(hdc, 0, m_inforect.top-4, SLIDER_WIDTH, m_inforect.bottom+4);
1190 CMyMemDC memDC(hdc);
1191 if ((pSecondPic)&&(m_blend != BLEND_ALPHA))
1193 // erase the place where the alpha slider would be
1194 ::SetBkColor(memDC, transparentColor);
1195 RECT bounds = {0, m_inforect.top-4, SLIDER_WIDTH, m_inforect.bottom+4};
1196 ::ExtTextOut(memDC, 0, 0, ETO_OPAQUE, &bounds, NULL, 0, NULL);
1199 GetClientRect(&rect);
1200 if (bValid)
1202 ShowPicWithBorder(memDC, rect, picture, picscale);
1203 if (pSecondPic)
1205 HDC secondhdc = CreateCompatibleDC(hdc);
1206 HBITMAP hBitmap = CreateCompatibleBitmap(hdc, rect.right - rect.left, rect.bottom - rect.top);
1207 HBITMAP hOldBitmap = (HBITMAP)SelectObject(secondhdc, hBitmap);
1208 SetWindowOrgEx(secondhdc, rect.left, rect.top, NULL);
1210 if ((pSecondPic)&&(m_blend != BLEND_ALPHA))
1212 // erase the place where the alpha slider would be
1213 ::SetBkColor(secondhdc, transparentColor);
1214 RECT bounds = {0, m_inforect.top-4, SLIDER_WIDTH, m_inforect.bottom+4};
1215 ::ExtTextOut(secondhdc, 0, 0, ETO_OPAQUE, &bounds, NULL, 0, NULL);
1217 ShowPicWithBorder(secondhdc, rect, *pSecondPic, pTheOtherPic->GetZoom());
1219 if (m_blend == BLEND_ALPHA)
1221 BLENDFUNCTION blender;
1222 blender.AlphaFormat = 0;
1223 blender.BlendFlags = 0;
1224 blender.BlendOp = AC_SRC_OVER;
1225 blender.SourceConstantAlpha = (BYTE)(blendAlpha*255);
1226 AlphaBlend(memDC,
1227 rect.left,
1228 rect.top,
1229 rect.right-rect.left,
1230 rect.bottom-rect.top,
1231 secondhdc,
1232 rect.left,
1233 rect.top,
1234 rect.right-rect.left,
1235 rect.bottom-rect.top,
1236 blender);
1238 else if (m_blend == BLEND_XOR)
1240 BitBlt(memDC,
1241 rect.left,
1242 rect.top,
1243 rect.right-rect.left,
1244 rect.bottom-rect.top,
1245 secondhdc,
1246 rect.left,
1247 rect.top,
1248 //rect.right-rect.left,
1249 //rect.bottom-rect.top,
1250 SRCINVERT);
1251 InvertRect(memDC, &rect);
1253 SelectObject(secondhdc, hOldBitmap);
1254 DeleteObject(hBitmap);
1255 DeleteDC(secondhdc);
1257 int sliderwidth = 0;
1258 if ((pSecondPic)&&(m_blend == BLEND_ALPHA))
1259 sliderwidth = SLIDER_WIDTH;
1260 m_inforect.left = rect.left+4+sliderwidth;
1261 m_inforect.top = rect.top;
1262 m_inforect.right = rect.right+sliderwidth;
1263 m_inforect.bottom = rect.bottom;
1265 SetBkColor(memDC, transparentColor);
1266 if (bShowInfo)
1268 std::unique_ptr<TCHAR[]> infostring(new TCHAR[8192]);
1269 BuildInfoString(infostring.get(), 8192, false);
1270 // set the font
1271 NONCLIENTMETRICS metrics = {0};
1272 metrics.cbSize = sizeof(NONCLIENTMETRICS);
1274 #if (WINVER >= 0x600)
1275 if (!SysInfo::Instance().IsVistaOrLater())
1277 metrics.cbSize -= sizeof(int); // subtract the size of the iPaddedBorderWidth member which is not available on XP
1279 #endif
1281 SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &metrics, FALSE);
1282 HFONT hFont = CreateFontIndirect(&metrics.lfStatusFont);
1283 HFONT hFontOld = (HFONT)SelectObject(memDC, (HGDIOBJ)hFont);
1284 // find out how big the rectangle for the text has to be
1285 DrawText(memDC, infostring.get(), -1, &m_inforect, DT_EDITCONTROL | DT_EXPANDTABS | DT_LEFT | DT_VCENTER | DT_CALCRECT);
1287 // the text should be drawn with a four pixel offset to the window borders
1288 m_inforect.top = rect.bottom - (m_inforect.bottom-m_inforect.top) - 4;
1289 m_inforect.bottom = rect.bottom-4;
1291 // first draw an edge rectangle
1292 RECT edgerect;
1293 edgerect.left = m_inforect.left-4;
1294 edgerect.top = m_inforect.top-4;
1295 edgerect.right = m_inforect.right+4;
1296 edgerect.bottom = m_inforect.bottom+4;
1297 ::ExtTextOut(memDC, 0, 0, ETO_OPAQUE, &edgerect, NULL, 0, NULL);
1298 DrawEdge(memDC, &edgerect, EDGE_BUMP, BF_RECT | BF_SOFT);
1300 SetTextColor(memDC, GetSysColor(COLOR_WINDOWTEXT));
1301 DrawText(memDC, infostring.get(), -1, &m_inforect, DT_EDITCONTROL | DT_EXPANDTABS | DT_LEFT | DT_VCENTER);
1302 SelectObject(memDC, (HGDIOBJ)hFontOld);
1303 DeleteObject(hFont);
1306 else
1308 SetBkColor(memDC, ::GetSysColor(COLOR_WINDOW));
1309 ::ExtTextOut(memDC, 0, 0, ETO_OPAQUE, &rect, NULL, 0, NULL);
1310 SIZE stringsize;
1311 ResString str = ResString(hResource, IDS_INVALIDIMAGEINFO);
1313 // set the font
1314 NONCLIENTMETRICS metrics = {0};
1315 metrics.cbSize = sizeof(NONCLIENTMETRICS);
1316 #if (WINVER >= 0x600)
1317 if (!SysInfo::Instance().IsVistaOrLater())
1319 metrics.cbSize -= sizeof(int); // subtract the size of the iPaddedBorderWidth member which is not available on XP
1321 #endif
1322 SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &metrics, FALSE);
1323 HFONT hFont = CreateFontIndirect(&metrics.lfStatusFont);
1324 HFONT hFontOld = (HFONT)SelectObject(memDC, (HGDIOBJ)hFont);
1326 if (GetTextExtentPoint32(memDC, str, (int)_tcslen(str), &stringsize))
1328 int nStringLength = stringsize.cx;
1330 ExtTextOut(memDC,
1331 max(rect.left + ((rect.right-rect.left)-nStringLength)/2, 1),
1332 rect.top + ((rect.bottom-rect.top) - stringsize.cy)/2,
1333 ETO_CLIPPED,
1334 &rect,
1335 str,
1336 (UINT)_tcslen(str),
1337 NULL);
1339 SelectObject(memDC, (HGDIOBJ)hFontOld);
1340 DeleteObject(hFont);
1342 DrawViewTitle(memDC, &fullrect);
1344 EndPaint(hwnd, &ps);
1347 bool CPicWindow::CreateButtons()
1349 // Ensure that the common control DLL is loaded.
1350 INITCOMMONCONTROLSEX icex;
1351 icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
1352 icex.dwICC = ICC_BAR_CLASSES | ICC_WIN95_CLASSES;
1353 InitCommonControlsEx(&icex);
1355 hwndLeftBtn = CreateWindowEx(0,
1356 _T("BUTTON"),
1357 (LPCTSTR)NULL,
1358 WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_ICON | BS_FLAT,
1359 0, 0, 0, 0,
1360 (HWND)*this,
1361 (HMENU)LEFTBUTTON_ID,
1362 hResource,
1363 NULL);
1364 if (hwndLeftBtn == INVALID_HANDLE_VALUE)
1365 return false;
1366 hLeft = (HICON)LoadImage(hResource, MAKEINTRESOURCE(IDI_BACKWARD), IMAGE_ICON, 16, 16, LR_LOADTRANSPARENT);
1367 SendMessage(hwndLeftBtn, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hLeft);
1368 hwndRightBtn = CreateWindowEx(0,
1369 _T("BUTTON"),
1370 (LPCTSTR)NULL,
1371 WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_ICON | BS_FLAT,
1372 0, 0, 0, 0,
1373 *this,
1374 (HMENU)RIGHTBUTTON_ID,
1375 hResource,
1376 NULL);
1377 if (hwndRightBtn == INVALID_HANDLE_VALUE)
1378 return false;
1379 hRight = (HICON)LoadImage(hResource, MAKEINTRESOURCE(IDI_FORWARD), IMAGE_ICON, 16, 16, LR_LOADTRANSPARENT);
1380 SendMessage(hwndRightBtn, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hRight);
1381 hwndPlayBtn = CreateWindowEx(0,
1382 _T("BUTTON"),
1383 (LPCTSTR)NULL,
1384 WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_ICON | BS_FLAT,
1385 0, 0, 0, 0,
1386 *this,
1387 (HMENU)PLAYBUTTON_ID,
1388 hResource,
1389 NULL);
1390 if (hwndPlayBtn == INVALID_HANDLE_VALUE)
1391 return false;
1392 hPlay = (HICON)LoadImage(hResource, MAKEINTRESOURCE(IDI_START), IMAGE_ICON, 16, 16, LR_LOADTRANSPARENT);
1393 hStop = (HICON)LoadImage(hResource, MAKEINTRESOURCE(IDI_STOP), IMAGE_ICON, 16, 16, LR_LOADTRANSPARENT);
1394 SendMessage(hwndPlayBtn, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hPlay);
1395 hwndAlphaToggleBtn = CreateWindowEx(0,
1396 _T("BUTTON"),
1397 (LPCTSTR)NULL,
1398 WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_ICON | BS_FLAT | BS_NOTIFY | BS_PUSHLIKE,
1399 0, 0, 0, 0,
1400 (HWND)*this,
1401 (HMENU)ALPHATOGGLEBUTTON_ID,
1402 hResource,
1403 NULL);
1404 if (hwndAlphaToggleBtn == INVALID_HANDLE_VALUE)
1405 return false;
1406 hAlphaToggle = (HICON)LoadImage(hResource, MAKEINTRESOURCE(IDI_ALPHATOGGLE), IMAGE_ICON, 16, 16, LR_LOADTRANSPARENT);
1407 SendMessage(hwndAlphaToggleBtn, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hAlphaToggle);
1409 TOOLINFO ti = {0};
1410 ti.cbSize = sizeof(TOOLINFO);
1411 ti.uFlags = TTF_IDISHWND|TTF_SUBCLASS;
1412 ti.hwnd = *this;
1413 ti.hinst = hResource;
1414 ti.uId = (UINT_PTR)hwndAlphaToggleBtn;
1415 ti.lpszText = LPSTR_TEXTCALLBACK;
1416 // ToolTip control will cover the whole window
1417 ti.rect.left = 0;
1418 ti.rect.top = 0;
1419 ti.rect.right = 0;
1420 ti.rect.bottom = 0;
1421 SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &ti);
1423 return true;
1426 void CPicWindow::PositionChildren()
1428 RECT rect;
1429 ::GetClientRect(*this, &rect);
1430 if (HasMultipleImages())
1432 SetWindowPos(hwndLeftBtn, HWND_TOP, rect.left+3, rect.top + HEADER_HEIGHT + (HEADER_HEIGHT-16)/2, 16, 16, SWP_FRAMECHANGED|SWP_SHOWWINDOW);
1433 SetWindowPos(hwndRightBtn, HWND_TOP, rect.left+23, rect.top + HEADER_HEIGHT + (HEADER_HEIGHT-16)/2, 16, 16, SWP_FRAMECHANGED|SWP_SHOWWINDOW);
1434 if (nFrames > 1)
1435 SetWindowPos(hwndPlayBtn, HWND_TOP, rect.left+43, rect.top + HEADER_HEIGHT + (HEADER_HEIGHT-16)/2, 16, 16, SWP_FRAMECHANGED|SWP_SHOWWINDOW);
1436 else
1437 ShowWindow(hwndPlayBtn, SW_HIDE);
1439 else
1441 ShowWindow(hwndLeftBtn, SW_HIDE);
1442 ShowWindow(hwndRightBtn, SW_HIDE);
1443 ShowWindow(hwndPlayBtn, SW_HIDE);
1445 PositionTrackBar();
1448 bool CPicWindow::HasMultipleImages()
1450 return (((nDimensions > 1)||(nFrames > 1))&&(pSecondPic == NULL));
1453 void CPicWindow::CreateTrackbar(HWND hwndParent)
1455 HWND hwndTrack = CreateWindowEx(
1456 0, // no extended styles
1457 TRACKBAR_CLASS, // class name
1458 _T("Trackbar Control"), // title (caption)
1459 WS_CHILD | WS_VISIBLE | TBS_VERT | TBS_TOOLTIPS | TBS_AUTOTICKS, // style
1460 10, 10, // position
1461 200, 30, // size
1462 hwndParent, // parent window
1463 (HMENU)TRACKBAR_ID, // control identifier
1464 hInst, // instance
1465 NULL // no WM_CREATE parameter
1468 SendMessage(hwndTrack, TBM_SETRANGE,
1469 (WPARAM) TRUE, // redraw flag
1470 (LPARAM) MAKELONG(0, 16)); // min. & max. positions
1471 SendMessage(hwndTrack, TBM_SETTIPSIDE,
1472 (WPARAM) TBTS_TOP,
1473 (LPARAM) 0);
1475 m_AlphaSlider.ConvertTrackbarToNice(hwndTrack);
1478 void CPicWindow::BuildInfoString(TCHAR * buf, int size, bool bTooltip)
1480 // Unfortunately, we need two different strings for the tooltip
1481 // and the info box. Because the tooltips use a different tab size
1482 // than ExtTextOut(), and to keep the output aligned we therefore
1483 // need two different strings.
1484 // Note: some translations could end up with two identical strings, but
1485 // in English we need two - even if we wouldn't need two in English, some
1486 // translation might then need two again.
1487 if (pSecondPic)
1489 _stprintf_s(buf, size,
1490 (TCHAR const *)ResString(hResource, bTooltip ? IDS_DUALIMAGEINFOTT : IDS_DUALIMAGEINFO),
1491 picture.GetFileSizeAsText().c_str(), picture.GetFileSizeAsText(false).c_str(),
1492 picture.m_Width, picture.m_Height,
1493 picture.GetHorizontalResolution(), picture.GetVerticalResolution(),
1494 picture.m_ColorDepth,
1495 (UINT)(GetZoom()*100.0),
1496 pSecondPic->GetFileSizeAsText().c_str(), pSecondPic->GetFileSizeAsText(false).c_str(),
1497 pSecondPic->m_Width, pSecondPic->m_Height,
1498 pSecondPic->GetHorizontalResolution(), pSecondPic->GetVerticalResolution(),
1499 pSecondPic->m_ColorDepth,
1500 (UINT)(pTheOtherPic->GetZoom()*100.0));
1502 else
1504 _stprintf_s(buf, size,
1505 (TCHAR const *)ResString(hResource, bTooltip ? IDS_IMAGEINFOTT : IDS_IMAGEINFO),
1506 picture.GetFileSizeAsText().c_str(), picture.GetFileSizeAsText(false).c_str(),
1507 picture.m_Width, picture.m_Height,
1508 picture.GetHorizontalResolution(), picture.GetVerticalResolution(),
1509 picture.m_ColorDepth,
1510 (UINT)(GetZoom()*100.0));