1 /* 2 * PROJECT: ReactOS Picture and Fax Viewer 3 * LICENSE: GPL-2.0 (https://spdx.org/licenses/GPL-2.0) 4 * PURPOSE: Image file browsing and manipulation 5 * COPYRIGHT: Copyright Dmitry Chapyshev (dmitry@reactos.org) 6 * Copyright 2018-2023 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com) 7 */ 8 9 #include "shimgvw.h" 10 #include <windowsx.h> 11 #include <commctrl.h> 12 #include <commdlg.h> 13 #include <shlobj.h> 14 #include <shellapi.h> 15 16 /* Toolbar image size */ 17 #define TB_IMAGE_WIDTH 16 18 #define TB_IMAGE_HEIGHT 16 19 20 /* Slide show timer */ 21 #define SLIDESHOW_TIMER_ID 0xFACE 22 #define SLIDESHOW_TIMER_INTERVAL 5000 /* 5 seconds */ 23 24 HINSTANCE g_hInstance = NULL; 25 HWND g_hMainWnd = NULL; 26 HWND g_hwndFullscreen = NULL; 27 SHIMGVW_FILENODE * g_pCurrentFile = NULL; 28 GpImage * g_pImage = NULL; 29 SHIMGVW_SETTINGS g_Settings; 30 31 static const UINT s_ZoomSteps[] = 32 { 33 5, 10, 25, 50, 100, 200, 300, 500, 1000, 2000, 4000 34 }; 35 36 #define MIN_ZOOM s_ZoomSteps[0] 37 #define MAX_ZOOM s_ZoomSteps[_countof(s_ZoomSteps) - 1] 38 39 /* iBitmap, idCommand, fsState, fsStyle, bReserved[2], dwData, iString */ 40 #define DEFINE_BTN_INFO(_name) \ 41 { TBICON_##_name, IDC_##_name, TBSTATE_ENABLED, BTNS_BUTTON, {0}, 0, 0 } 42 43 #define DEFINE_BTN_SEPARATOR \ 44 { -1, 0, TBSTATE_ENABLED, BTNS_SEP, {0}, 0, 0 } 45 46 /* ToolBar Buttons */ 47 static const TBBUTTON s_Buttons[] = 48 { 49 DEFINE_BTN_INFO(PREV_PIC), 50 DEFINE_BTN_INFO(NEXT_PIC), 51 DEFINE_BTN_SEPARATOR, 52 DEFINE_BTN_INFO(BEST_FIT), 53 DEFINE_BTN_INFO(REAL_SIZE), 54 DEFINE_BTN_INFO(SLIDE_SHOW), 55 DEFINE_BTN_SEPARATOR, 56 DEFINE_BTN_INFO(ZOOM_IN), 57 DEFINE_BTN_INFO(ZOOM_OUT), 58 DEFINE_BTN_SEPARATOR, 59 DEFINE_BTN_INFO(ROT_CLOCKW), 60 DEFINE_BTN_INFO(ROT_COUNCW), 61 DEFINE_BTN_SEPARATOR, 62 DEFINE_BTN_INFO(ROT_CWSAVE), 63 DEFINE_BTN_INFO(ROT_CCWSAVE), 64 DEFINE_BTN_SEPARATOR, 65 DEFINE_BTN_INFO(DELETE), 66 DEFINE_BTN_INFO(PRINT), 67 DEFINE_BTN_INFO(SAVEAS), 68 DEFINE_BTN_INFO(MODIFY), 69 DEFINE_BTN_SEPARATOR, 70 DEFINE_BTN_INFO(HELP_TOC) 71 }; 72 73 /* ToolBar Button configuration */ 74 typedef struct 75 { 76 DWORD idb; /* Index to bitmap */ 77 DWORD ids; /* Index to tooltip */ 78 } TB_BUTTON_CONFIG; 79 80 #define DEFINE_BTN_CONFIG(_name) { IDB_##_name, IDS_TOOLTIP_##_name } 81 82 static const TB_BUTTON_CONFIG s_ButtonConfig[] = 83 { 84 DEFINE_BTN_CONFIG(PREV_PIC), 85 DEFINE_BTN_CONFIG(NEXT_PIC), 86 DEFINE_BTN_CONFIG(BEST_FIT), 87 DEFINE_BTN_CONFIG(REAL_SIZE), 88 DEFINE_BTN_CONFIG(SLIDE_SHOW), 89 DEFINE_BTN_CONFIG(ZOOM_IN), 90 DEFINE_BTN_CONFIG(ZOOM_OUT), 91 DEFINE_BTN_CONFIG(ROT_CLOCKW), 92 DEFINE_BTN_CONFIG(ROT_COUNCW), 93 DEFINE_BTN_CONFIG(ROT_CWSAVE), 94 DEFINE_BTN_CONFIG(ROT_CCWSAVE), 95 DEFINE_BTN_CONFIG(DELETE), 96 DEFINE_BTN_CONFIG(PRINT), 97 DEFINE_BTN_CONFIG(SAVEAS), 98 DEFINE_BTN_CONFIG(MODIFY), 99 DEFINE_BTN_CONFIG(HELP_TOC), 100 }; 101 102 typedef struct tagPREVIEW_DATA 103 { 104 HWND m_hwnd; 105 HWND m_hwndZoom; 106 HWND m_hwndToolBar; 107 INT m_nZoomPercents; 108 ANIME m_Anime; /* Animation */ 109 INT m_xScrollOffset; 110 INT m_yScrollOffset; 111 UINT m_nMouseDownMsg; 112 POINT m_ptOrigin; 113 IStream *m_pMemStream; 114 WCHAR m_szFile[MAX_PATH]; 115 } PREVIEW_DATA, *PPREVIEW_DATA; 116 117 static inline PPREVIEW_DATA 118 Preview_GetData(HWND hwnd) 119 { 120 return (PPREVIEW_DATA)GetWindowLongPtrW(hwnd, GWLP_USERDATA); 121 } 122 123 static inline BOOL 124 Preview_IsMainWnd(HWND hwnd) 125 { 126 return hwnd == g_hMainWnd; 127 } 128 129 static VOID 130 Preview_RestartTimer(HWND hwnd) 131 { 132 if (!Preview_IsMainWnd(hwnd)) 133 { 134 KillTimer(hwnd, SLIDESHOW_TIMER_ID); 135 SetTimer(hwnd, SLIDESHOW_TIMER_ID, SLIDESHOW_TIMER_INTERVAL, NULL); 136 } 137 } 138 139 static VOID 140 ZoomWnd_UpdateScroll(PPREVIEW_DATA pData, HWND hwnd, BOOL bResetPos) 141 { 142 RECT rcClient; 143 UINT ImageWidth, ImageHeight, ZoomedWidth, ZoomedHeight; 144 SCROLLINFO si; 145 BOOL bShowHorz, bShowVert; 146 147 if (bResetPos) 148 pData->m_xScrollOffset = pData->m_yScrollOffset = 0; 149 150 if (!g_pImage) 151 { 152 ShowScrollBar(hwnd, SB_BOTH, FALSE); 153 InvalidateRect(hwnd, NULL, TRUE); 154 return; 155 } 156 157 GdipGetImageWidth(g_pImage, &ImageWidth); 158 GdipGetImageHeight(g_pImage, &ImageHeight); 159 160 ZoomedWidth = (ImageWidth * pData->m_nZoomPercents) / 100; 161 ZoomedHeight = (ImageHeight * pData->m_nZoomPercents) / 100; 162 163 GetClientRect(hwnd, &rcClient); 164 165 bShowHorz = (rcClient.right < ZoomedWidth); 166 bShowVert = (rcClient.bottom < ZoomedHeight); 167 ShowScrollBar(hwnd, SB_HORZ, bShowHorz); 168 ShowScrollBar(hwnd, SB_VERT, bShowVert); 169 170 GetClientRect(hwnd, &rcClient); 171 172 ZeroMemory(&si, sizeof(si)); 173 si.cbSize = sizeof(si); 174 si.fMask = SIF_ALL; 175 176 if (bShowHorz) 177 { 178 GetScrollInfo(hwnd, SB_HORZ, &si); 179 si.nPage = rcClient.right; 180 si.nMin = 0; 181 si.nMax = ZoomedWidth; 182 si.nPos = (ZoomedWidth - rcClient.right) / 2 + pData->m_xScrollOffset; 183 si.nPos = max(min(si.nPos, si.nMax - (INT)si.nPage), si.nMin); 184 SetScrollInfo(hwnd, SB_HORZ, &si, TRUE); 185 pData->m_xScrollOffset = si.nPos - (ZoomedWidth - rcClient.right) / 2; 186 } 187 else 188 { 189 pData->m_xScrollOffset = 0; 190 } 191 192 if (bShowVert) 193 { 194 GetScrollInfo(hwnd, SB_VERT, &si); 195 si.nPage = rcClient.bottom; 196 si.nMin = 0; 197 si.nMax = ZoomedHeight; 198 si.nPos = (ZoomedHeight - rcClient.bottom) / 2 + pData->m_yScrollOffset; 199 si.nPos = max(min(si.nPos, si.nMax - (INT)si.nPage), si.nMin); 200 SetScrollInfo(hwnd, SB_VERT, &si, TRUE); 201 pData->m_yScrollOffset = si.nPos - (ZoomedHeight - rcClient.bottom) / 2; 202 } 203 else 204 { 205 pData->m_yScrollOffset = 0; 206 } 207 208 InvalidateRect(hwnd, NULL, TRUE); 209 } 210 211 static VOID 212 Preview_UpdateZoom(PPREVIEW_DATA pData, UINT NewZoom, BOOL bEnableBestFit, BOOL bEnableRealSize) 213 { 214 BOOL bEnableZoomIn, bEnableZoomOut; 215 HWND hToolBar = pData->m_hwndToolBar; 216 217 pData->m_nZoomPercents = NewZoom; 218 219 /* Check if a zoom button of the toolbar must be grayed */ 220 bEnableZoomIn = (NewZoom < MAX_ZOOM); 221 bEnableZoomOut = (NewZoom > MIN_ZOOM); 222 223 /* Update toolbar buttons */ 224 PostMessageW(hToolBar, TB_ENABLEBUTTON, IDC_ZOOM_OUT, bEnableZoomOut); 225 PostMessageW(hToolBar, TB_ENABLEBUTTON, IDC_ZOOM_IN, bEnableZoomIn); 226 PostMessageW(hToolBar, TB_ENABLEBUTTON, IDC_BEST_FIT, bEnableBestFit); 227 PostMessageW(hToolBar, TB_ENABLEBUTTON, IDC_REAL_SIZE, bEnableRealSize); 228 229 /* Redraw the display window */ 230 InvalidateRect(pData->m_hwndZoom, NULL, TRUE); 231 232 /* Restart timer if necessary */ 233 Preview_RestartTimer(pData->m_hwnd); 234 235 /* Update scroll info */ 236 ZoomWnd_UpdateScroll(pData, pData->m_hwndZoom, FALSE); 237 } 238 239 static VOID 240 Preview_ZoomInOrOut(PPREVIEW_DATA pData, BOOL bZoomIn) 241 { 242 UINT i, NewZoom; 243 244 if (g_pImage == NULL) 245 return; 246 247 if (bZoomIn) /* zoom in */ 248 { 249 /* find next step */ 250 for (i = 0; i < _countof(s_ZoomSteps); ++i) 251 { 252 if (pData->m_nZoomPercents < s_ZoomSteps[i]) 253 break; 254 } 255 NewZoom = ((i >= _countof(s_ZoomSteps)) ? MAX_ZOOM : s_ZoomSteps[i]); 256 } 257 else /* zoom out */ 258 { 259 /* find previous step */ 260 for (i = _countof(s_ZoomSteps); i > 0; ) 261 { 262 --i; 263 if (s_ZoomSteps[i] < pData->m_nZoomPercents) 264 break; 265 } 266 NewZoom = ((i < 0) ? MIN_ZOOM : s_ZoomSteps[i]); 267 } 268 269 /* Update toolbar and refresh screen */ 270 Preview_UpdateZoom(pData, NewZoom, TRUE, TRUE); 271 } 272 273 static VOID 274 Preview_ResetZoom(PPREVIEW_DATA pData) 275 { 276 RECT Rect; 277 UINT ImageWidth, ImageHeight, NewZoom; 278 279 if (g_pImage == NULL) 280 return; 281 282 /* get disp window size and image size */ 283 GetClientRect(pData->m_hwndZoom, &Rect); 284 GdipGetImageWidth(g_pImage, &ImageWidth); 285 GdipGetImageHeight(g_pImage, &ImageHeight); 286 287 /* compare two aspect rates. same as 288 (ImageHeight / ImageWidth < Rect.bottom / Rect.right) in real */ 289 if (ImageHeight * Rect.right < Rect.bottom * ImageWidth) 290 { 291 if (Rect.right < ImageWidth) 292 { 293 /* it's large, shrink it */ 294 NewZoom = (Rect.right * 100) / ImageWidth; 295 } 296 else 297 { 298 /* it's small. show as original size */ 299 NewZoom = 100; 300 } 301 } 302 else 303 { 304 if (Rect.bottom < ImageHeight) 305 { 306 /* it's large, shrink it */ 307 NewZoom = (Rect.bottom * 100) / ImageHeight; 308 } 309 else 310 { 311 /* it's small. show as original size */ 312 NewZoom = 100; 313 } 314 } 315 316 Preview_UpdateZoom(pData, NewZoom, FALSE, TRUE); 317 } 318 319 static VOID 320 Preview_UpdateTitle(PPREVIEW_DATA pData, LPCWSTR FileName) 321 { 322 WCHAR szText[MAX_PATH + 100]; 323 LPWSTR pchFileTitle; 324 325 LoadStringW(g_hInstance, IDS_APPTITLE, szText, _countof(szText)); 326 327 pchFileTitle = PathFindFileNameW(FileName); 328 if (pchFileTitle && *pchFileTitle) 329 { 330 StringCchCatW(szText, _countof(szText), L" - "); 331 StringCchCatW(szText, _countof(szText), pchFileTitle); 332 } 333 334 SetWindowTextW(pData->m_hwnd, szText); 335 } 336 337 static VOID 338 Preview_pFreeImage(PPREVIEW_DATA pData) 339 { 340 Anime_FreeInfo(&pData->m_Anime); 341 342 if (g_pImage) 343 { 344 GdipDisposeImage(g_pImage); 345 g_pImage = NULL; 346 } 347 348 if (pData->m_pMemStream) 349 { 350 pData->m_pMemStream->lpVtbl->Release(pData->m_pMemStream); 351 pData->m_pMemStream = NULL; 352 } 353 354 pData->m_szFile[0] = UNICODE_NULL; 355 } 356 357 IStream* MemStreamFromFile(LPCWSTR pszFileName) 358 { 359 HANDLE hFile; 360 DWORD dwFileSize, dwRead; 361 LPBYTE pbMemFile = NULL; 362 IStream *pStream; 363 364 hFile = CreateFileW(pszFileName, GENERIC_READ, FILE_SHARE_READ, NULL, 365 OPEN_EXISTING, 0, NULL); 366 if (hFile == INVALID_HANDLE_VALUE) 367 return NULL; 368 369 dwFileSize = GetFileSize(hFile, NULL); 370 pbMemFile = QuickAlloc(dwFileSize, FALSE); 371 if (!dwFileSize || (dwFileSize == INVALID_FILE_SIZE) || !pbMemFile) 372 { 373 CloseHandle(hFile); 374 return NULL; 375 } 376 377 if (!ReadFile(hFile, pbMemFile, dwFileSize, &dwRead, NULL) || (dwRead != dwFileSize)) 378 { 379 QuickFree(pbMemFile); 380 CloseHandle(hFile); 381 return NULL; 382 } 383 384 CloseHandle(hFile); 385 pStream = SHCreateMemStream(pbMemFile, dwFileSize); 386 QuickFree(pbMemFile); 387 return pStream; 388 } 389 390 static VOID 391 Preview_pLoadImage(PPREVIEW_DATA pData, LPCWSTR szOpenFileName) 392 { 393 Preview_pFreeImage(pData); 394 395 pData->m_pMemStream = MemStreamFromFile(szOpenFileName); 396 if (!pData->m_pMemStream) 397 { 398 DPRINT1("MemStreamFromFile() failed\n"); 399 Preview_UpdateTitle(pData, NULL); 400 return; 401 } 402 403 /* NOTE: GdipLoadImageFromFile locks the file. 404 Avoid file locking by using GdipLoadImageFromStream and memory stream. */ 405 GdipLoadImageFromStream(pData->m_pMemStream, &g_pImage); 406 if (!g_pImage) 407 { 408 DPRINT1("GdipLoadImageFromStream() failed\n"); 409 Preview_pFreeImage(pData); 410 Preview_UpdateTitle(pData, NULL); 411 return; 412 } 413 414 Anime_LoadInfo(&pData->m_Anime); 415 416 SHAddToRecentDocs(SHARD_PATHW, szOpenFileName); 417 GetFullPathNameW(szOpenFileName, _countof(pData->m_szFile), pData->m_szFile, NULL); 418 419 /* Reset zoom and redraw display */ 420 Preview_ResetZoom(pData); 421 422 Preview_UpdateTitle(pData, szOpenFileName); 423 } 424 425 static VOID 426 Preview_pLoadImageFromNode(PPREVIEW_DATA pData, SHIMGVW_FILENODE *pNode) 427 { 428 Preview_pLoadImage(pData, (pNode ? pNode->FileName : NULL)); 429 } 430 431 static BOOL 432 Preview_pSaveImage(PPREVIEW_DATA pData, LPCWSTR pszFile) 433 { 434 ImageCodecInfo *codecInfo; 435 GUID rawFormat; 436 UINT j, num, nFilterIndex, size; 437 BOOL ret = FALSE; 438 439 if (g_pImage == NULL) 440 return FALSE; 441 442 GdipGetImageEncodersSize(&num, &size); 443 codecInfo = QuickAlloc(size, FALSE); 444 if (!codecInfo) 445 { 446 DPRINT1("QuickAlloc() failed in pSaveImage()\n"); 447 return FALSE; 448 } 449 GdipGetImageEncoders(num, size, codecInfo); 450 451 GdipGetImageRawFormat(g_pImage, &rawFormat); 452 if (IsEqualGUID(&rawFormat, &ImageFormatMemoryBMP)) 453 rawFormat = ImageFormatBMP; 454 455 nFilterIndex = 0; 456 for (j = 0; j < num; ++j) 457 { 458 if (IsEqualGUID(&rawFormat, &codecInfo[j].FormatID)) 459 { 460 nFilterIndex = j + 1; 461 break; 462 } 463 } 464 465 Anime_Pause(&pData->m_Anime); 466 467 ret = (nFilterIndex > 0) && 468 (GdipSaveImageToFile(g_pImage, pszFile, &codecInfo[nFilterIndex - 1].Clsid, NULL) == Ok); 469 if (!ret) 470 DPRINT1("GdipSaveImageToFile() failed\n"); 471 472 Anime_Start(&pData->m_Anime, 0); 473 474 QuickFree(codecInfo); 475 return ret; 476 } 477 478 static VOID 479 Preview_pSaveImageAs(PPREVIEW_DATA pData) 480 { 481 OPENFILENAMEW sfn; 482 ImageCodecInfo *codecInfo; 483 WCHAR szSaveFileName[MAX_PATH]; 484 WCHAR *szFilterMask; 485 GUID rawFormat; 486 UINT num, size, j; 487 size_t sizeRemain; 488 WCHAR *c; 489 HWND hwnd = pData->m_hwnd; 490 491 if (g_pImage == NULL) 492 return; 493 494 GdipGetImageEncodersSize(&num, &size); 495 codecInfo = QuickAlloc(size, FALSE); 496 if (!codecInfo) 497 { 498 DPRINT1("QuickAlloc() failed in pSaveImageAs()\n"); 499 return; 500 } 501 502 GdipGetImageEncoders(num, size, codecInfo); 503 504 GdipGetImageRawFormat(g_pImage, &rawFormat); 505 if (IsEqualGUID(&rawFormat, &ImageFormatMemoryBMP)) 506 rawFormat = ImageFormatBMP; 507 508 sizeRemain = 0; 509 for (j = 0; j < num; ++j) 510 { 511 // Every pair needs space for the Description, twice the Extensions, 1 char for the space, 2 for the braces and 2 for the NULL terminators. 512 sizeRemain = sizeRemain + (((wcslen(codecInfo[j].FormatDescription) + (wcslen(codecInfo[j].FilenameExtension) * 2) + 5) * sizeof(WCHAR))); 513 } 514 515 /* Add two more chars for the last terminator */ 516 sizeRemain += (sizeof(WCHAR) * 2); 517 518 szFilterMask = QuickAlloc(sizeRemain, FALSE); 519 if (!szFilterMask) 520 { 521 DPRINT1("cannot allocate memory for filter mask in pSaveImageAs()"); 522 QuickFree(codecInfo); 523 return; 524 } 525 526 ZeroMemory(szSaveFileName, sizeof(szSaveFileName)); 527 ZeroMemory(szFilterMask, sizeRemain); 528 ZeroMemory(&sfn, sizeof(sfn)); 529 sfn.lStructSize = sizeof(sfn); 530 sfn.hwndOwner = hwnd; 531 sfn.lpstrFile = szSaveFileName; 532 sfn.lpstrFilter = szFilterMask; 533 sfn.nMaxFile = _countof(szSaveFileName); 534 sfn.Flags = OFN_EXPLORER | OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY; 535 sfn.lpstrDefExt = L"png"; 536 537 c = szFilterMask; 538 539 for (j = 0; j < num; ++j) 540 { 541 StringCbPrintfExW(c, sizeRemain, &c, &sizeRemain, 0, L"%ls (%ls)", codecInfo[j].FormatDescription, codecInfo[j].FilenameExtension); 542 543 /* Skip the NULL character */ 544 c++; 545 sizeRemain -= sizeof(*c); 546 547 StringCbPrintfExW(c, sizeRemain, &c, &sizeRemain, 0, L"%ls", codecInfo[j].FilenameExtension); 548 549 /* Skip the NULL character */ 550 c++; 551 sizeRemain -= sizeof(*c); 552 553 if (IsEqualGUID(&rawFormat, &codecInfo[j].FormatID)) 554 { 555 sfn.nFilterIndex = j + 1; 556 } 557 } 558 559 if (GetSaveFileNameW(&sfn) && sfn.nFilterIndex > 0) 560 { 561 Anime_Pause(&pData->m_Anime); 562 563 if (GdipSaveImageToFile(g_pImage, szSaveFileName, &codecInfo[sfn.nFilterIndex - 1].Clsid, NULL) != Ok) 564 { 565 DPRINT1("GdipSaveImageToFile() failed\n"); 566 } 567 568 Anime_Start(&pData->m_Anime, 0); 569 } 570 571 QuickFree(szFilterMask); 572 QuickFree(codecInfo); 573 } 574 575 static VOID 576 Preview_pPrintImage(PPREVIEW_DATA pData) 577 { 578 /* FIXME */ 579 } 580 581 static VOID 582 Preview_UpdateUI(PPREVIEW_DATA pData) 583 { 584 BOOL bEnable = (g_pImage != NULL); 585 PostMessageW(pData->m_hwndToolBar, TB_ENABLEBUTTON, IDC_SAVEAS, bEnable); 586 PostMessageW(pData->m_hwndToolBar, TB_ENABLEBUTTON, IDC_PRINT, bEnable); 587 } 588 589 static VOID 590 Preview_UpdateImage(PPREVIEW_DATA pData) 591 { 592 if (!Preview_IsMainWnd(pData->m_hwnd)) 593 Preview_ResetZoom(pData); 594 595 ZoomWnd_UpdateScroll(pData, pData->m_hwndZoom, TRUE); 596 } 597 598 static SHIMGVW_FILENODE* 599 pBuildFileList(LPCWSTR szFirstFile) 600 { 601 HANDLE hFindHandle; 602 WCHAR *extension; 603 WCHAR szSearchPath[MAX_PATH]; 604 WCHAR szSearchMask[MAX_PATH]; 605 WCHAR szFileTypes[MAX_PATH]; 606 WIN32_FIND_DATAW findData; 607 SHIMGVW_FILENODE *currentNode = NULL; 608 SHIMGVW_FILENODE *root = NULL; 609 SHIMGVW_FILENODE *conductor = NULL; 610 ImageCodecInfo *codecInfo; 611 UINT num; 612 UINT size; 613 UINT j; 614 615 StringCbCopyW(szSearchPath, sizeof(szSearchPath), szFirstFile); 616 PathRemoveFileSpecW(szSearchPath); 617 618 GdipGetImageDecodersSize(&num, &size); 619 codecInfo = QuickAlloc(size, FALSE); 620 if (!codecInfo) 621 { 622 DPRINT1("QuickAlloc() failed in pLoadFileList()\n"); 623 return NULL; 624 } 625 626 GdipGetImageDecoders(num, size, codecInfo); 627 628 root = QuickAlloc(sizeof(SHIMGVW_FILENODE), FALSE); 629 if (!root) 630 { 631 DPRINT1("QuickAlloc() failed in pLoadFileList()\n"); 632 QuickFree(codecInfo); 633 return NULL; 634 } 635 636 conductor = root; 637 638 for (j = 0; j < num; ++j) 639 { 640 StringCbCopyW(szFileTypes, sizeof(szFileTypes), codecInfo[j].FilenameExtension); 641 642 extension = wcstok(szFileTypes, L";"); 643 while (extension != NULL) 644 { 645 PathCombineW(szSearchMask, szSearchPath, extension); 646 647 hFindHandle = FindFirstFileW(szSearchMask, &findData); 648 if (hFindHandle != INVALID_HANDLE_VALUE) 649 { 650 do 651 { 652 PathCombineW(conductor->FileName, szSearchPath, findData.cFileName); 653 654 // compare the name of the requested file with the one currently found. 655 // if the name matches, the current node is returned by the function. 656 if (_wcsicmp(szFirstFile, conductor->FileName) == 0) 657 { 658 currentNode = conductor; 659 } 660 661 conductor->Next = QuickAlloc(sizeof(SHIMGVW_FILENODE), FALSE); 662 663 // if QuickAlloc fails, make circular what we have and return it 664 if (!conductor->Next) 665 { 666 DPRINT1("QuickAlloc() failed in pLoadFileList()\n"); 667 668 conductor->Next = root; 669 root->Prev = conductor; 670 671 FindClose(hFindHandle); 672 QuickFree(codecInfo); 673 return conductor; 674 } 675 676 conductor->Next->Prev = conductor; 677 conductor = conductor->Next; 678 } 679 while (FindNextFileW(hFindHandle, &findData) != 0); 680 681 FindClose(hFindHandle); 682 } 683 684 extension = wcstok(NULL, L";"); 685 } 686 } 687 688 // we now have a node too much in the list. In case the requested file was not found, 689 // we use this node to store the name of it, otherwise we free it. 690 if (currentNode == NULL) 691 { 692 StringCchCopyW(conductor->FileName, MAX_PATH, szFirstFile); 693 currentNode = conductor; 694 } 695 else 696 { 697 conductor = conductor->Prev; 698 QuickFree(conductor->Next); 699 } 700 701 // link the last node with the first one to make the list circular 702 conductor->Next = root; 703 root->Prev = conductor; 704 conductor = currentNode; 705 706 QuickFree(codecInfo); 707 708 return conductor; 709 } 710 711 static VOID 712 pFreeFileList(SHIMGVW_FILENODE *root) 713 { 714 SHIMGVW_FILENODE *conductor; 715 716 if (!root) 717 return; 718 719 root->Prev->Next = NULL; 720 root->Prev = NULL; 721 722 while (root) 723 { 724 conductor = root; 725 root = conductor->Next; 726 QuickFree(conductor); 727 } 728 } 729 730 static HBRUSH CreateCheckerBoardBrush(VOID) 731 { 732 static const CHAR pattern[] = 733 "\x28\x00\x00\x00\x10\x00\x00\x00\x10\x00\x00\x00\x01\x00\x04\x00\x00\x00" 734 "\x00\x00\x80\x00\x00\x00\x23\x2E\x00\x00\x23\x2E\x00\x00\x10\x00\x00\x00" 735 "\x00\x00\x00\x00\x99\x99\x99\x00\xCC\xCC\xCC\x00\x00\x00\x00\x00\x00\x00" 736 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 737 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 738 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11\x11\x11" 739 "\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00" 740 "\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00" 741 "\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11" 742 "\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00" 743 "\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11" 744 "\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11" 745 "\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11"; 746 747 return CreateDIBPatternBrushPt(pattern, DIB_RGB_COLORS); 748 } 749 750 static VOID 751 ZoomWnd_OnDraw( 752 PPREVIEW_DATA pData, 753 HDC hdc, 754 LPRECT prcPaint, 755 LPRECT prcClient) 756 { 757 GpGraphics *graphics; 758 INT ZoomedWidth, ZoomedHeight; 759 RECT rect, rcClient = *prcClient; 760 HDC hdcMem; 761 HBRUSH hBrush; 762 HPEN hPen; 763 HGDIOBJ hbrOld, hbmOld, hPenOld; 764 UINT uFlags; 765 HBITMAP hbmMem; 766 SIZE paintSize = { prcPaint->right - prcPaint->left, prcPaint->bottom - prcPaint->top }; 767 COLORREF color0, color1; 768 GpImageAttributes *imageAttributes; 769 770 /* We use a memory bitmap to reduce flickering */ 771 hdcMem = CreateCompatibleDC(hdc); 772 hbmMem = CreateCompatibleBitmap(hdc, paintSize.cx, paintSize.cy); 773 hbmOld = SelectObject(hdcMem, hbmMem); 774 775 /* Choose colors */ 776 if (Preview_IsMainWnd(pData->m_hwnd)) 777 { 778 color0 = GetSysColor(COLOR_WINDOW); 779 color1 = GetSysColor(COLOR_WINDOWTEXT); 780 } 781 else 782 { 783 color0 = RGB(0, 0, 0); 784 color1 = RGB(255, 255, 255); 785 } 786 787 hBrush = CreateSolidBrush(color0); 788 SetBkColor(hdcMem, color0); 789 790 hPen = CreatePen(PS_SOLID, 1, color1); 791 SetTextColor(hdcMem, color1); 792 793 /* Fill background */ 794 SetRect(&rect, 0, 0, paintSize.cx, paintSize.cy); 795 FillRect(hdcMem, &rect, hBrush); 796 797 DeleteObject(hBrush); 798 799 if (g_pImage == NULL) 800 { 801 WCHAR szText[128]; 802 LoadStringW(g_hInstance, IDS_NOPREVIEW, szText, _countof(szText)); 803 804 SelectObject(hdcMem, GetStockFont(DEFAULT_GUI_FONT)); 805 OffsetRect(&rcClient, -prcPaint->left, -prcPaint->top); 806 DrawTextW(hdcMem, szText, -1, &rcClient, DT_SINGLELINE | DT_CENTER | DT_VCENTER | 807 DT_NOPREFIX); 808 } 809 else 810 { 811 UINT ImageWidth, ImageHeight; 812 813 GdipGetImageWidth(g_pImage, &ImageWidth); 814 GdipGetImageHeight(g_pImage, &ImageHeight); 815 816 ZoomedWidth = (ImageWidth * pData->m_nZoomPercents) / 100; 817 ZoomedHeight = (ImageHeight * pData->m_nZoomPercents) / 100; 818 819 GdipCreateFromHDC(hdcMem, &graphics); 820 if (!graphics) 821 { 822 DPRINT1("error: GdipCreateFromHDC\n"); 823 return; 824 } 825 826 GdipGetImageFlags(g_pImage, &uFlags); 827 828 if (pData->m_nZoomPercents % 100 == 0) 829 { 830 GdipSetInterpolationMode(graphics, InterpolationModeNearestNeighbor); 831 GdipSetSmoothingMode(graphics, SmoothingModeNone); 832 } 833 else 834 { 835 GdipSetInterpolationMode(graphics, InterpolationModeHighQualityBilinear); 836 GdipSetSmoothingMode(graphics, SmoothingModeHighQuality); 837 } 838 839 rect.left = (rcClient.right - ZoomedWidth ) / 2; 840 rect.top = (rcClient.bottom - ZoomedHeight) / 2; 841 rect.right = rect.left + ZoomedWidth; 842 rect.bottom = rect.top + ZoomedHeight; 843 OffsetRect(&rect, 844 -prcPaint->left - pData->m_xScrollOffset, 845 -prcPaint->top - pData->m_yScrollOffset); 846 847 InflateRect(&rect, +1, +1); /* Add Rectangle() pen width */ 848 849 /* Draw a rectangle. Fill by checker board if necessary */ 850 if (uFlags & (ImageFlagsHasAlpha | ImageFlagsHasTranslucent)) 851 hbrOld = SelectObject(hdcMem, CreateCheckerBoardBrush()); 852 else 853 hbrOld = SelectObject(hdcMem, GetStockBrush(NULL_BRUSH)); 854 hPenOld = SelectObject(hdcMem, hPen); 855 Rectangle(hdcMem, rect.left, rect.top, rect.right, rect.bottom); 856 DeleteObject(SelectObject(hdcMem, hbrOld)); 857 DeleteObject(SelectObject(hdcMem, hPenOld)); 858 859 InflateRect(&rect, -1, -1); /* Subtract Rectangle() pen width */ 860 861 /* Image attributes are required to draw image correctly */ 862 GdipCreateImageAttributes(&imageAttributes); 863 GdipSetImageAttributesWrapMode(imageAttributes, WrapModeTile, 864 GetBkColor(hdcMem) | 0xFF000000, TRUE); 865 866 /* Draw image. -0.5f is used for interpolation */ 867 GdipDrawImageRectRect(graphics, g_pImage, 868 rect.left, rect.top, 869 rect.right - rect.left, rect.bottom - rect.top, 870 -0.5f, -0.5f, ImageWidth, ImageHeight, 871 UnitPixel, imageAttributes, NULL, NULL); 872 873 GdipDisposeImageAttributes(imageAttributes); 874 GdipDeleteGraphics(graphics); 875 } 876 877 BitBlt(hdc, prcPaint->left, prcPaint->top, paintSize.cx, paintSize.cy, hdcMem, 0, 0, SRCCOPY); 878 DeleteObject(SelectObject(hdcMem, hbmOld)); 879 DeleteDC(hdcMem); 880 } 881 882 static VOID 883 ZoomWnd_OnPaint(PPREVIEW_DATA pData, HWND hwnd) 884 { 885 PAINTSTRUCT ps; 886 HDC hDC; 887 RECT rcClient; 888 889 hDC = BeginPaint(hwnd, &ps); 890 if (hDC) 891 { 892 GetClientRect(hwnd, &rcClient); 893 ZoomWnd_OnDraw(pData, hDC, &ps.rcPaint, &rcClient); 894 EndPaint(hwnd, &ps); 895 } 896 } 897 898 static VOID 899 ImageView_ResetSettings(VOID) 900 { 901 g_Settings.Maximized = FALSE; 902 g_Settings.X = CW_USEDEFAULT; 903 g_Settings.Y = CW_USEDEFAULT; 904 g_Settings.Width = 520; 905 g_Settings.Height = 400; 906 } 907 908 static BOOL 909 ImageView_LoadSettings(VOID) 910 { 911 HKEY hKey; 912 DWORD dwSize; 913 LSTATUS nError; 914 915 nError = RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\ReactOS\\shimgvw", 0, KEY_READ, &hKey); 916 if (nError != ERROR_SUCCESS) 917 return FALSE; 918 919 dwSize = sizeof(g_Settings); 920 nError = RegQueryValueExW(hKey, L"Settings", NULL, NULL, (LPBYTE)&g_Settings, &dwSize); 921 RegCloseKey(hKey); 922 923 return ((nError == ERROR_SUCCESS) && (dwSize == sizeof(g_Settings))); 924 } 925 926 static VOID 927 ImageView_SaveSettings(VOID) 928 { 929 HKEY hKey; 930 LSTATUS nError; 931 932 nError = RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\ReactOS\\shimgvw", 933 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL); 934 if (nError != ERROR_SUCCESS) 935 return; 936 937 RegSetValueExW(hKey, L"Settings", 0, REG_BINARY, (LPBYTE)&g_Settings, sizeof(g_Settings)); 938 RegCloseKey(hKey); 939 } 940 941 static BOOL 942 Preview_CreateToolBar(PPREVIEW_DATA pData) 943 { 944 HWND hwndToolBar; 945 HIMAGELIST hImageList, hOldImageList; 946 DWORD style = WS_CHILD | WS_VISIBLE | TBSTYLE_FLAT | TBSTYLE_TOOLTIPS; 947 948 if (!Preview_IsMainWnd(pData->m_hwnd)) 949 return TRUE; /* FIXME */ 950 951 style |= CCS_BOTTOM; 952 hwndToolBar = CreateWindowExW(0, TOOLBARCLASSNAMEW, NULL, style, 953 0, 0, 0, 0, pData->m_hwnd, NULL, g_hInstance, NULL); 954 if (!hwndToolBar) 955 return FALSE; 956 957 pData->m_hwndToolBar = hwndToolBar; 958 959 SendMessageW(hwndToolBar, TB_BUTTONSTRUCTSIZE, sizeof(s_Buttons[0]), 0); 960 SendMessageW(hwndToolBar, TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_HIDECLIPPEDBUTTONS); 961 962 hImageList = ImageList_Create(TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, ILC_MASK | ILC_COLOR24, 1, 1); 963 if (hImageList == NULL) 964 return FALSE; 965 966 for (UINT n = 0; n < _countof(s_ButtonConfig); n++) 967 { 968 HBITMAP hBitmap = LoadBitmapW(g_hInstance, MAKEINTRESOURCEW(s_ButtonConfig[n].idb)); 969 ImageList_AddMasked(hImageList, hBitmap, RGB(255, 255, 255)); 970 DeleteObject(hBitmap); 971 } 972 973 hOldImageList = (HIMAGELIST)SendMessageW(hwndToolBar, TB_SETIMAGELIST, 0, (LPARAM)hImageList); 974 ImageList_Destroy(hOldImageList); 975 976 SendMessageW(hwndToolBar, TB_ADDBUTTONS, _countof(s_Buttons), (LPARAM)s_Buttons); 977 978 return TRUE; 979 } 980 981 static VOID 982 Preview_EndSlideShow(HWND hwnd) 983 { 984 if (Preview_IsMainWnd(hwnd)) 985 return; 986 987 KillTimer(hwnd, SLIDESHOW_TIMER_ID); 988 ShowWindow(hwnd, SW_HIDE); 989 ShowWindow(g_hMainWnd, SW_SHOWNORMAL); 990 Preview_ResetZoom(Preview_GetData(g_hMainWnd)); 991 } 992 993 static VOID 994 ZoomWnd_OnButtonDown(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 995 { 996 PPREVIEW_DATA pData = Preview_GetData(hwnd); 997 HWND hParent = GetParent(hwnd); 998 if ((uMsg == WM_LBUTTONDOWN) || (uMsg == WM_RBUTTONDOWN)) 999 { 1000 if (!Preview_IsMainWnd(hParent)) 1001 Preview_EndSlideShow(hParent); 1002 return; 1003 } 1004 1005 pData->m_nMouseDownMsg = uMsg; 1006 pData->m_ptOrigin.x = GET_X_LPARAM(lParam); 1007 pData->m_ptOrigin.y = GET_Y_LPARAM(lParam); 1008 SetCapture(hwnd); 1009 SetCursor(LoadCursorW(g_hInstance, MAKEINTRESOURCEW(IDC_HANDDRAG))); 1010 } 1011 1012 static VOID 1013 ZoomWnd_OnMouseMove(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 1014 { 1015 PPREVIEW_DATA pData = Preview_GetData(hwnd); 1016 POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; 1017 1018 if (pData->m_nMouseDownMsg == WM_MBUTTONDOWN) 1019 { 1020 INT x = GetScrollPos(hwnd, SB_HORZ) - (pt.x - pData->m_ptOrigin.x); 1021 INT y = GetScrollPos(hwnd, SB_VERT) - (pt.y - pData->m_ptOrigin.y); 1022 SendMessageW(hwnd, WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, x), 0); 1023 SendMessageW(hwnd, WM_VSCROLL, MAKEWPARAM(SB_THUMBPOSITION, y), 0); 1024 pData->m_ptOrigin = pt; 1025 } 1026 } 1027 1028 static BOOL 1029 ZoomWnd_OnSetCursor(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 1030 { 1031 PPREVIEW_DATA pData = Preview_GetData(hwnd); 1032 if (pData->m_nMouseDownMsg == WM_MBUTTONDOWN) 1033 { 1034 SetCursor(LoadCursorW(g_hInstance, MAKEINTRESOURCEW(IDC_HANDDRAG))); 1035 return TRUE; 1036 } 1037 return FALSE; 1038 } 1039 1040 static VOID 1041 ZoomWnd_OnButtonUp(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 1042 { 1043 PPREVIEW_DATA pData = Preview_GetData(hwnd); 1044 pData->m_nMouseDownMsg = 0; 1045 ReleaseCapture(); 1046 } 1047 1048 static VOID 1049 ZoomWnd_OnHVScroll(PPREVIEW_DATA pData, HWND hwnd, WPARAM wParam, BOOL bVertical) 1050 { 1051 UINT ImageWidth, ImageHeight, ZoomedWidth, ZoomedHeight; 1052 RECT rcClient; 1053 UINT nBar = (bVertical ? SB_VERT : SB_HORZ); 1054 SCROLLINFO si = { sizeof(si), SIF_ALL }; 1055 GetScrollInfo(hwnd, nBar, &si); 1056 1057 if (!g_pImage) 1058 return; 1059 1060 if (bVertical) 1061 { 1062 if (!(GetWindowLongPtrW(hwnd, GWL_STYLE) & WS_VSCROLL)) 1063 return; 1064 } 1065 else 1066 { 1067 if (!(GetWindowLongPtrW(hwnd, GWL_STYLE) & WS_HSCROLL)) 1068 return; 1069 } 1070 1071 switch (LOWORD(wParam)) 1072 { 1073 case SB_THUMBTRACK: 1074 case SB_THUMBPOSITION: 1075 si.nPos = (SHORT)HIWORD(wParam); 1076 break; 1077 case SB_LINELEFT: 1078 si.nPos -= 48; 1079 break; 1080 case SB_LINERIGHT: 1081 si.nPos += 48; 1082 break; 1083 case SB_PAGELEFT: 1084 si.nPos -= si.nPage; 1085 break; 1086 case SB_PAGERIGHT: 1087 si.nPos += si.nPage; 1088 break; 1089 } 1090 1091 si.fMask = SIF_POS; 1092 SetScrollInfo(hwnd, nBar, &si, TRUE); 1093 GetScrollInfo(hwnd, nBar, &si); 1094 1095 GetClientRect(hwnd, &rcClient); 1096 1097 if (bVertical) 1098 { 1099 GdipGetImageHeight(g_pImage, &ImageHeight); 1100 ZoomedHeight = (ImageHeight * pData->m_nZoomPercents) / 100; 1101 pData->m_yScrollOffset = si.nPos - (ZoomedHeight - rcClient.bottom) / 2; 1102 } 1103 else 1104 { 1105 GdipGetImageWidth(g_pImage, &ImageWidth); 1106 ZoomedWidth = (ImageWidth * pData->m_nZoomPercents) / 100; 1107 pData->m_xScrollOffset = si.nPos - (ZoomedWidth - rcClient.right) / 2; 1108 } 1109 1110 InvalidateRect(hwnd, NULL, TRUE); 1111 } 1112 1113 static VOID 1114 ZoomWnd_OnMouseWheel(HWND hwnd, INT x, INT y, INT zDelta, UINT fwKeys) 1115 { 1116 PPREVIEW_DATA pData = Preview_GetData(hwnd); 1117 if (zDelta == 0) 1118 return; 1119 1120 if (GetKeyState(VK_CONTROL) < 0) 1121 { 1122 Preview_ZoomInOrOut(pData, zDelta > 0); 1123 } 1124 else if (GetKeyState(VK_SHIFT) < 0) 1125 { 1126 if (zDelta > 0) 1127 SendMessageW(hwnd, WM_HSCROLL, SB_LINELEFT, 0); 1128 else 1129 SendMessageW(hwnd, WM_HSCROLL, SB_LINERIGHT, 0); 1130 } 1131 else 1132 { 1133 if (zDelta > 0) 1134 SendMessageW(hwnd, WM_VSCROLL, SB_LINEUP, 0); 1135 else 1136 SendMessageW(hwnd, WM_VSCROLL, SB_LINEDOWN, 0); 1137 } 1138 } 1139 1140 LRESULT CALLBACK 1141 ZoomWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 1142 { 1143 PPREVIEW_DATA pData = Preview_GetData(hwnd); 1144 switch (uMsg) 1145 { 1146 case WM_LBUTTONDOWN: 1147 case WM_MBUTTONDOWN: 1148 case WM_RBUTTONDOWN: 1149 { 1150 ZoomWnd_OnButtonDown(hwnd, uMsg, wParam, lParam); 1151 break; 1152 } 1153 case WM_MOUSEMOVE: 1154 { 1155 ZoomWnd_OnMouseMove(hwnd, uMsg, wParam, lParam); 1156 break; 1157 } 1158 case WM_SETCURSOR: 1159 { 1160 if (!ZoomWnd_OnSetCursor(hwnd, uMsg, wParam, lParam)) 1161 return DefWindowProcW(hwnd, uMsg, wParam, lParam); 1162 } 1163 case WM_LBUTTONUP: 1164 case WM_MBUTTONUP: 1165 case WM_RBUTTONUP: 1166 { 1167 ZoomWnd_OnButtonUp(hwnd, uMsg, wParam, lParam); 1168 break; 1169 } 1170 case WM_PAINT: 1171 { 1172 ZoomWnd_OnPaint(pData, hwnd); 1173 break; 1174 } 1175 case WM_MOUSEWHEEL: 1176 { 1177 ZoomWnd_OnMouseWheel(hwnd, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 1178 (SHORT)HIWORD(wParam), (UINT)LOWORD(wParam)); 1179 break; 1180 } 1181 case WM_HSCROLL: 1182 case WM_VSCROLL: 1183 ZoomWnd_OnHVScroll(pData, hwnd, wParam, uMsg == WM_VSCROLL); 1184 break; 1185 case WM_TIMER: 1186 { 1187 if (Anime_OnTimer(&pData->m_Anime, wParam)) 1188 InvalidateRect(hwnd, NULL, FALSE); 1189 break; 1190 } 1191 default: 1192 { 1193 return DefWindowProcW(hwnd, uMsg, wParam, lParam); 1194 } 1195 } 1196 return 0; 1197 } 1198 1199 static BOOL 1200 Preview_OnCreate(HWND hwnd, LPCREATESTRUCT pCS) 1201 { 1202 DWORD exstyle = 0; 1203 HWND hwndZoom; 1204 PPREVIEW_DATA pData = QuickAlloc(sizeof(PREVIEW_DATA), TRUE); 1205 pData->m_hwnd = hwnd; 1206 SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR)pData); 1207 1208 DragAcceptFiles(hwnd, TRUE); 1209 1210 if (g_hMainWnd == NULL) 1211 { 1212 g_hMainWnd = hwnd; 1213 exstyle |= WS_EX_CLIENTEDGE; 1214 } 1215 else if (g_hwndFullscreen == NULL) 1216 { 1217 g_hwndFullscreen = hwnd; 1218 } 1219 else 1220 { 1221 return FALSE; 1222 } 1223 1224 hwndZoom = CreateWindowExW(exstyle, WC_ZOOM, NULL, WS_CHILD | WS_VISIBLE, 1225 0, 0, 0, 0, hwnd, NULL, g_hInstance, NULL); 1226 if (!hwndZoom) 1227 { 1228 QuickFree(pData); 1229 return FALSE; 1230 } 1231 1232 pData->m_hwndZoom = hwndZoom; 1233 SetWindowLongPtrW(hwndZoom, GWLP_USERDATA, (LONG_PTR)pData); 1234 Anime_SetTimerWnd(&pData->m_Anime, pData->m_hwndZoom); 1235 1236 if (!Preview_CreateToolBar(pData)) 1237 { 1238 QuickFree(pData); 1239 return FALSE; 1240 } 1241 1242 if (pCS && pCS->lpCreateParams) 1243 { 1244 LPCWSTR pszFileName = (LPCWSTR)pCS->lpCreateParams; 1245 WCHAR szFile[MAX_PATH]; 1246 1247 /* Make sure the path has no quotes on it */ 1248 StringCchCopyW(szFile, _countof(szFile), pszFileName); 1249 PathUnquoteSpacesW(szFile); 1250 1251 g_pCurrentFile = pBuildFileList(szFile); 1252 Preview_pLoadImageFromNode(pData, g_pCurrentFile); 1253 Preview_UpdateImage(pData); 1254 Preview_UpdateUI(pData); 1255 } 1256 1257 return TRUE; 1258 } 1259 1260 static VOID 1261 Preview_OnMoveSize(HWND hwnd) 1262 { 1263 WINDOWPLACEMENT wp; 1264 RECT *prc; 1265 1266 if (IsIconic(hwnd) || !Preview_IsMainWnd(hwnd)) 1267 return; 1268 1269 wp.length = sizeof(WINDOWPLACEMENT); 1270 GetWindowPlacement(hwnd, &wp); 1271 1272 /* Remember window position and size */ 1273 prc = &wp.rcNormalPosition; 1274 g_Settings.X = prc->left; 1275 g_Settings.Y = prc->top; 1276 g_Settings.Width = prc->right - prc->left; 1277 g_Settings.Height = prc->bottom - prc->top; 1278 g_Settings.Maximized = IsZoomed(hwnd); 1279 } 1280 1281 static VOID 1282 Preview_OnSize(HWND hwnd) 1283 { 1284 RECT rc, rcClient; 1285 PPREVIEW_DATA pData = Preview_GetData(hwnd); 1286 HWND hToolBar = pData->m_hwndToolBar; 1287 INT cx, cy; 1288 1289 /* We want 32-bit values. Don't use WM_SIZE lParam */ 1290 GetClientRect(hwnd, &rcClient); 1291 cx = rcClient.right; 1292 cy = rcClient.bottom; 1293 1294 if (Preview_IsMainWnd(pData->m_hwnd)) 1295 { 1296 SendMessageW(hToolBar, TB_AUTOSIZE, 0, 0); 1297 GetWindowRect(hToolBar, &rc); 1298 1299 MoveWindow(pData->m_hwndZoom, 0, 0, cx, cy - (rc.bottom - rc.top), TRUE); 1300 1301 if (!IsIconic(hwnd)) /* Is it not minimized? */ 1302 Preview_ResetZoom(pData); 1303 1304 Preview_OnMoveSize(hwnd); 1305 } 1306 else 1307 { 1308 MoveWindow(pData->m_hwndZoom, 0, 0, cx, cy, TRUE); 1309 } 1310 } 1311 1312 static VOID 1313 Preview_Delete(PPREVIEW_DATA pData) 1314 { 1315 WCHAR szCurFile[MAX_PATH + 1], szNextFile[MAX_PATH]; 1316 HWND hwnd = pData->m_hwnd; 1317 SHFILEOPSTRUCTW FileOp = { hwnd, FO_DELETE }; 1318 1319 if (!pData->m_szFile[0]) 1320 return; 1321 1322 /* FileOp.pFrom must be double-null-terminated */ 1323 GetFullPathNameW(pData->m_szFile, _countof(szCurFile) - 1, szCurFile, NULL); 1324 szCurFile[_countof(szCurFile) - 2] = UNICODE_NULL; /* Avoid buffer overrun */ 1325 szCurFile[lstrlenW(szCurFile) + 1] = UNICODE_NULL; 1326 1327 szNextFile[0] = UNICODE_NULL; 1328 if (g_pCurrentFile) 1329 { 1330 GetFullPathNameW(g_pCurrentFile->Next->FileName, _countof(szNextFile), szNextFile, NULL); 1331 szNextFile[_countof(szNextFile) - 1] = UNICODE_NULL; /* Avoid buffer overrun */ 1332 } 1333 1334 /* Confirm file deletion and delete if allowed */ 1335 FileOp.pFrom = szCurFile; 1336 FileOp.fFlags = FOF_ALLOWUNDO; 1337 if (SHFileOperationW(&FileOp) != 0) 1338 { 1339 DPRINT("Preview_Delete: SHFileOperationW() failed or canceled\n"); 1340 return; 1341 } 1342 1343 /* Reload the file list and go next file */ 1344 pFreeFileList(g_pCurrentFile); 1345 g_pCurrentFile = pBuildFileList(szNextFile); 1346 Preview_pLoadImageFromNode(pData, g_pCurrentFile); 1347 } 1348 1349 static VOID 1350 Preview_Edit(HWND hwnd) 1351 { 1352 SHELLEXECUTEINFOW sei; 1353 PPREVIEW_DATA pData = Preview_GetData(hwnd); 1354 1355 if (!pData->m_szFile[0]) 1356 return; 1357 1358 ZeroMemory(&sei, sizeof(sei)); 1359 sei.cbSize = sizeof(sei); 1360 sei.lpVerb = L"edit"; 1361 sei.lpFile = pData->m_szFile; 1362 sei.nShow = SW_SHOWNORMAL; 1363 if (!ShellExecuteExW(&sei)) 1364 { 1365 DPRINT1("Preview_Edit: ShellExecuteExW() failed with code %ld\n", GetLastError()); 1366 } 1367 else 1368 { 1369 // Destroy the window to quit the application 1370 DestroyWindow(hwnd); 1371 } 1372 } 1373 1374 static VOID 1375 Preview_ToggleSlideShow(PPREVIEW_DATA pData) 1376 { 1377 if (!IsWindow(g_hwndFullscreen)) 1378 { 1379 DWORD style = WS_POPUP | WS_CLIPSIBLINGS, exstyle = WS_EX_TOPMOST; 1380 WCHAR szTitle[256]; 1381 LoadStringW(g_hInstance, IDS_APPTITLE, szTitle, _countof(szTitle)); 1382 g_hwndFullscreen = CreateWindowExW(exstyle, WC_PREVIEW, szTitle, style, 1383 0, 0, 0, 0, NULL, NULL, g_hInstance, NULL); 1384 } 1385 1386 if (IsWindowVisible(g_hwndFullscreen)) 1387 { 1388 ShowWindow(g_hwndFullscreen, SW_HIDE); 1389 ShowWindow(g_hMainWnd, SW_SHOWNORMAL); 1390 KillTimer(g_hwndFullscreen, SLIDESHOW_TIMER_ID); 1391 } 1392 else 1393 { 1394 ShowWindow(g_hMainWnd, SW_HIDE); 1395 ShowWindow(g_hwndFullscreen, SW_SHOWMAXIMIZED); 1396 Preview_RestartTimer(g_hwndFullscreen); 1397 } 1398 } 1399 1400 static VOID 1401 Preview_GoNextPic(PPREVIEW_DATA pData, BOOL bNext) 1402 { 1403 Preview_RestartTimer(pData->m_hwnd); 1404 if (g_pCurrentFile) 1405 { 1406 if (bNext) 1407 g_pCurrentFile = g_pCurrentFile->Next; 1408 else 1409 g_pCurrentFile = g_pCurrentFile->Prev; 1410 Preview_pLoadImageFromNode(pData, g_pCurrentFile); 1411 Preview_UpdateImage(pData); 1412 Preview_UpdateUI(pData); 1413 } 1414 } 1415 1416 static VOID 1417 Preview_OnCommand(HWND hwnd, UINT nCommandID) 1418 { 1419 PPREVIEW_DATA pData = Preview_GetData(hwnd); 1420 1421 switch (nCommandID) 1422 { 1423 case IDC_PREV_PIC: 1424 Preview_GoNextPic(pData, FALSE); 1425 break; 1426 1427 case IDC_NEXT_PIC: 1428 Preview_GoNextPic(pData, TRUE); 1429 break; 1430 1431 case IDC_BEST_FIT: 1432 Preview_ResetZoom(pData); 1433 break; 1434 1435 case IDC_REAL_SIZE: 1436 Preview_UpdateZoom(pData, 100, TRUE, FALSE); 1437 break; 1438 1439 case IDC_SLIDE_SHOW: 1440 Preview_ToggleSlideShow(pData); 1441 break; 1442 1443 case IDC_ZOOM_IN: 1444 Preview_ZoomInOrOut(pData, TRUE); 1445 break; 1446 1447 case IDC_ZOOM_OUT: 1448 Preview_ZoomInOrOut(pData, FALSE); 1449 break; 1450 1451 case IDC_ENDSLIDESHOW: 1452 Preview_EndSlideShow(hwnd); 1453 break; 1454 1455 default: 1456 break; 1457 } 1458 1459 if (!Preview_IsMainWnd(hwnd)) 1460 return; 1461 1462 // The following commands are for main window only: 1463 switch (nCommandID) 1464 { 1465 case IDC_SAVEAS: 1466 Preview_pSaveImageAs(pData); 1467 break; 1468 1469 case IDC_PRINT: 1470 Preview_pPrintImage(pData); 1471 break; 1472 1473 case IDC_ROT_CLOCKW: 1474 if (g_pImage) 1475 { 1476 GdipImageRotateFlip(g_pImage, Rotate270FlipNone); 1477 Preview_UpdateImage(pData); 1478 } 1479 break; 1480 1481 case IDC_ROT_COUNCW: 1482 if (g_pImage) 1483 { 1484 GdipImageRotateFlip(g_pImage, Rotate90FlipNone); 1485 Preview_UpdateImage(pData); 1486 } 1487 break; 1488 1489 case IDC_ROT_CWSAVE: 1490 if (g_pImage) 1491 { 1492 GdipImageRotateFlip(g_pImage, Rotate270FlipNone); 1493 Preview_pSaveImage(pData, pData->m_szFile); 1494 Preview_UpdateImage(pData); 1495 } 1496 break; 1497 1498 case IDC_ROT_CCWSAVE: 1499 if (g_pImage) 1500 { 1501 GdipImageRotateFlip(g_pImage, Rotate90FlipNone); 1502 Preview_pSaveImage(pData, pData->m_szFile); 1503 Preview_UpdateImage(pData); 1504 } 1505 break; 1506 1507 case IDC_DELETE: 1508 Preview_Delete(pData); 1509 Preview_UpdateImage(pData); 1510 Preview_UpdateUI(pData); 1511 break; 1512 1513 case IDC_MODIFY: 1514 Preview_Edit(hwnd); 1515 break; 1516 1517 default: 1518 break; 1519 } 1520 } 1521 1522 static LRESULT 1523 Preview_OnNotify(HWND hwnd, LPNMHDR pnmhdr) 1524 { 1525 switch (pnmhdr->code) 1526 { 1527 case TTN_GETDISPINFOW: 1528 { 1529 LPTOOLTIPTEXTW lpttt = (LPTOOLTIPTEXTW)pnmhdr; 1530 lpttt->hinst = g_hInstance; 1531 lpttt->lpszText = MAKEINTRESOURCEW(s_ButtonConfig[lpttt->hdr.idFrom - IDC_TOOL_BASE].ids); 1532 break; 1533 } 1534 } 1535 return 0; 1536 } 1537 1538 static VOID 1539 Preview_OnDestroy(HWND hwnd) 1540 { 1541 PPREVIEW_DATA pData = Preview_GetData(hwnd); 1542 1543 KillTimer(hwnd, SLIDESHOW_TIMER_ID); 1544 1545 pFreeFileList(g_pCurrentFile); 1546 g_pCurrentFile = NULL; 1547 1548 Preview_pFreeImage(pData); 1549 1550 SetWindowLongPtrW(pData->m_hwndZoom, GWLP_USERDATA, 0); 1551 DestroyWindow(pData->m_hwndZoom); 1552 pData->m_hwndZoom = NULL; 1553 1554 DestroyWindow(pData->m_hwndToolBar); 1555 pData->m_hwndToolBar = NULL; 1556 1557 SetWindowLongPtrW(pData->m_hwnd, GWLP_USERDATA, 0); 1558 QuickFree(pData); 1559 1560 PostQuitMessage(0); 1561 } 1562 1563 static VOID 1564 Preview_OnDropFiles(HWND hwnd, HDROP hDrop) 1565 { 1566 WCHAR szFile[MAX_PATH]; 1567 PPREVIEW_DATA pData = Preview_GetData(hwnd); 1568 1569 DragQueryFileW(hDrop, 0, szFile, _countof(szFile)); 1570 1571 pFreeFileList(g_pCurrentFile); 1572 g_pCurrentFile = pBuildFileList(szFile); 1573 Preview_pLoadImageFromNode(pData, g_pCurrentFile); 1574 1575 DragFinish(hDrop); 1576 } 1577 1578 LRESULT CALLBACK 1579 PreviewWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 1580 { 1581 switch (uMsg) 1582 { 1583 case WM_CREATE: 1584 { 1585 if (!Preview_OnCreate(hwnd, (LPCREATESTRUCT)lParam)) 1586 return -1; 1587 break; 1588 } 1589 case WM_COMMAND: 1590 { 1591 Preview_OnCommand(hwnd, LOWORD(wParam)); 1592 break; 1593 } 1594 case WM_NOTIFY: 1595 { 1596 return Preview_OnNotify(hwnd, (LPNMHDR)lParam); 1597 } 1598 case WM_GETMINMAXINFO: 1599 { 1600 MINMAXINFO *pMMI = (MINMAXINFO*)lParam; 1601 pMMI->ptMinTrackSize.x = 350; 1602 pMMI->ptMinTrackSize.y = 290; 1603 break; 1604 } 1605 case WM_MOVE: 1606 { 1607 Preview_OnMoveSize(hwnd); 1608 break; 1609 } 1610 case WM_SIZE: 1611 { 1612 Preview_OnSize(hwnd); 1613 break; 1614 } 1615 case WM_DROPFILES: 1616 { 1617 Preview_OnDropFiles(hwnd, (HDROP)wParam); 1618 break; 1619 } 1620 case WM_SYSCOLORCHANGE: 1621 { 1622 PPREVIEW_DATA pData = Preview_GetData(hwnd); 1623 InvalidateRect(pData->m_hwnd, NULL, TRUE); 1624 InvalidateRect(pData->m_hwndZoom, NULL, TRUE); 1625 break; 1626 } 1627 case WM_DESTROY: 1628 { 1629 Preview_OnDestroy(hwnd); 1630 break; 1631 } 1632 case WM_TIMER: 1633 { 1634 if (wParam == SLIDESHOW_TIMER_ID) 1635 { 1636 PPREVIEW_DATA pData = Preview_GetData(hwnd); 1637 Preview_GoNextPic(pData, TRUE); 1638 } 1639 break; 1640 } 1641 default: 1642 { 1643 return DefWindowProcW(hwnd, uMsg, wParam, lParam); 1644 } 1645 } 1646 1647 return 0; 1648 } 1649 1650 LONG 1651 ImageView_Main(HWND hwnd, LPCWSTR szFileName) 1652 { 1653 struct GdiplusStartupInput gdiplusStartupInput; 1654 ULONG_PTR gdiplusToken; 1655 WNDCLASSW WndClass; 1656 WCHAR szTitle[256]; 1657 HWND hMainWnd; 1658 MSG msg; 1659 HACCEL hAccel; 1660 HRESULT hrCoInit; 1661 INITCOMMONCONTROLSEX Icc = { .dwSize = sizeof(Icc), .dwICC = ICC_WIN95_CLASSES }; 1662 1663 InitCommonControlsEx(&Icc); 1664 1665 /* Initialize COM */ 1666 hrCoInit = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); 1667 if (FAILED(hrCoInit)) 1668 DPRINT1("Warning, CoInitializeEx failed with code=%08X\n", (int)hrCoInit); 1669 1670 if (!ImageView_LoadSettings()) 1671 ImageView_ResetSettings(); 1672 1673 /* Initialize GDI+ */ 1674 ZeroMemory(&gdiplusStartupInput, sizeof(gdiplusStartupInput)); 1675 gdiplusStartupInput.GdiplusVersion = 1; 1676 GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); 1677 1678 /* Register window classes */ 1679 ZeroMemory(&WndClass, sizeof(WndClass)); 1680 WndClass.lpszClassName = WC_PREVIEW; 1681 WndClass.lpfnWndProc = PreviewWndProc; 1682 WndClass.hInstance = g_hInstance; 1683 WndClass.style = CS_HREDRAW | CS_VREDRAW; 1684 WndClass.hIcon = LoadIconW(g_hInstance, MAKEINTRESOURCEW(IDI_APP_ICON)); 1685 WndClass.hCursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW); 1686 WndClass.hbrBackground = (HBRUSH)UlongToHandle(COLOR_3DFACE + 1); 1687 if (!RegisterClassW(&WndClass)) 1688 return -1; 1689 WndClass.lpszClassName = WC_ZOOM; 1690 WndClass.lpfnWndProc = ZoomWndProc; 1691 WndClass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; 1692 WndClass.hbrBackground = GetStockBrush(NULL_BRUSH); /* less flicker */ 1693 if (!RegisterClassW(&WndClass)) 1694 return -1; 1695 1696 /* Create the main window */ 1697 LoadStringW(g_hInstance, IDS_APPTITLE, szTitle, _countof(szTitle)); 1698 hMainWnd = CreateWindowExW(WS_EX_WINDOWEDGE, WC_PREVIEW, szTitle, 1699 WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS, 1700 g_Settings.X, g_Settings.Y, g_Settings.Width, g_Settings.Height, 1701 NULL, NULL, g_hInstance, (LPVOID)szFileName); 1702 1703 /* Create accelerator table for keystrokes */ 1704 hAccel = LoadAcceleratorsW(g_hInstance, MAKEINTRESOURCEW(IDR_ACCELERATOR)); 1705 1706 /* Show the main window now */ 1707 if (g_Settings.Maximized) 1708 ShowWindow(hMainWnd, SW_SHOWMAXIMIZED); 1709 else 1710 ShowWindow(hMainWnd, SW_SHOWNORMAL); 1711 1712 UpdateWindow(hMainWnd); 1713 1714 /* Message Loop */ 1715 while (GetMessageW(&msg, NULL, 0, 0) > 0) 1716 { 1717 if (g_hwndFullscreen && TranslateAcceleratorW(g_hwndFullscreen, hAccel, &msg)) 1718 continue; 1719 if (TranslateAcceleratorW(hMainWnd, hAccel, &msg)) 1720 continue; 1721 1722 TranslateMessage(&msg); 1723 DispatchMessageW(&msg); 1724 } 1725 1726 /* Destroy accelerator table */ 1727 DestroyAcceleratorTable(hAccel); 1728 1729 ImageView_SaveSettings(); 1730 1731 GdiplusShutdown(gdiplusToken); 1732 1733 /* Release COM resources */ 1734 if (SUCCEEDED(hrCoInit)) 1735 CoUninitialize(); 1736 1737 return 0; 1738 } 1739 1740 VOID WINAPI 1741 ImageView_FullscreenW(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow) 1742 { 1743 ImageView_Main(hwnd, path); 1744 } 1745 1746 VOID WINAPI 1747 ImageView_Fullscreen(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow) 1748 { 1749 ImageView_Main(hwnd, path); 1750 } 1751 1752 VOID WINAPI 1753 ImageView_FullscreenA(HWND hwnd, HINSTANCE hInst, LPCSTR path, int nShow) 1754 { 1755 WCHAR szFile[MAX_PATH]; 1756 1757 if (MultiByteToWideChar(CP_ACP, 0, path, -1, szFile, _countof(szFile))) 1758 { 1759 ImageView_Main(hwnd, szFile); 1760 } 1761 } 1762 1763 VOID WINAPI 1764 ImageView_PrintTo(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow) 1765 { 1766 DPRINT("ImageView_PrintTo() not implemented\n"); 1767 } 1768 1769 VOID WINAPI 1770 ImageView_PrintToA(HWND hwnd, HINSTANCE hInst, LPCSTR path, int nShow) 1771 { 1772 DPRINT("ImageView_PrintToA() not implemented\n"); 1773 } 1774 1775 VOID WINAPI 1776 ImageView_PrintToW(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow) 1777 { 1778 DPRINT("ImageView_PrintToW() not implemented\n"); 1779 } 1780 1781 BOOL WINAPI 1782 DllMain(IN HINSTANCE hinstDLL, 1783 IN DWORD dwReason, 1784 IN LPVOID lpvReserved) 1785 { 1786 switch (dwReason) 1787 { 1788 case DLL_PROCESS_ATTACH: 1789 g_hInstance = hinstDLL; 1790 break; 1791 } 1792 1793 return TRUE; 1794 } 1795