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