1: /*
     2:  ============================================================================
     3:  xoblite™ -> an advanced "extended shell" for Microsoft® Windows® 11
     4:  Copyright © 2002-2026 Karl Henrik Henriksson [qwilk/@xoblite]
     5:  Copyright © 2001-2004 The Blackbox for Windows Development Team
     6:  http://xoblite.net/ + https://github.com/xoblite/
     7:  ============================================================================
     8: 
     9:   Blackbox for Windows is free software, released under the
    10:   GNU General Public License (GPL version 2 or later), with an extension
    11:   that allows linking of proprietary modules under a controlled interface.
    12:   What this means is that plugins etc. are allowed to be released
    13:   under any license the author wishes. Please note, however, that the
    14:   original Blackbox gradient math code used in Blackbox for Windows
    15:   is available under the BSD license.
    16: 
    17:   http://www.fsf.org/licenses/gpl.html
    18:   http://www.fsf.org/licenses/gpl-faq.html#LinkingOverControlledInterface
    19:   http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
    20: 
    21:   This program is distributed in the hope that it will be useful,
    22:   but WITHOUT ANY WARRANTY; without even the implied warranty of
    23:   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    24:   GNU General Public License for more details.
    25: 
    26:   For additional license information, please read the included license.html.
    27: 
    28:  ============================================================================
    29: */
    30: 
    31: #include "PreviewItem.h" 
    32: 
    33: const char szPreviewItemName[] = "BBPreviewItem"; // Window class etc.
    34: 
    35: extern PreviewItem* pPreviewItem;
    36: extern Settings* pSettings;
    37: 
    38: int previewItemMessageSubscription[] = { BB_RECONFIGURE, 0 };
    39: 
    40: //===========================================================================
    41: 
    42: PreviewItem::PreviewItem(HINSTANCE hInstance)
    43: {
    44:     hPreviewItemWnd = NULL;
    45:     hBlackboxWnd = GetBBWnd();
    46:     hPreviewItemInstance = hInstance;
    47:     cachedBackground = CreateCompatibleDC(NULL);
    48:     PreviewItemX = PreviewItemY = PreviewItemHeight = newItemX = newItemY = 0;
    49:     PreviewItemWidth = 150 * pSettings->scalingFactorHiDPI;
    50:     PreviewItemHidden = true;
    51: 
    52:     // Start GDI+...
    53: //  GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
    54: 
    55:     //====================
    56: 
    57:     // Register window class...
    58:     WNDCLASS wc;
    59:     ZeroMemory(&wc, sizeof(wc));
    60:     wc.hInstance = hPreviewItemInstance;        // hInstance
    61:     wc.lpfnWndProc = PreviewItemWindowProc;     // Window procedure
    62:     wc.lpszClassName = szPreviewItemName;       // Window class name
    63:     wc.hbrBackground = NULL;                    // No class background brush
    64:     wc.style = NULL;                            // Class styles
    65:     wc.hCursor = LoadCursor(NULL, IDC_ARROW);   // Always display the regular arrow cursor
    66: 
    67:     if (!RegisterClass(&wc))
    68:     {
    69:         MessageBox(0, "Error registering preview item window class!", szPreviewItemName, MB_OK | MB_ICONERROR | MB_TOPMOST);
    70:         Log("PreviewItem: Error registering window class!", NULL);
    71:         return;
    72:     }
    73: 
    74:     // Create preview item window...
    75:     hPreviewItemWnd = CreateWindowEx(
    76:         WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE | WS_EX_LAYERED,    // window style
    77:         szPreviewItemName,                                      // window class
    78:         NULL,                                                   // window name
    79:         WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,           // window parameters
    80:         PreviewItemX,                                           // x position
    81:         PreviewItemY,                                           // y position
    82:         PreviewItemWidth,                                       // window width
    83:         PreviewItemHeight,                                      // window height
    84:         NULL, //pDesktop->hDesktopWnd,                          // owner window
    85:         NULL,                                                   // no menu
    86:         hPreviewItemInstance,                                   // hInstance
    87:         NULL                                                    // no window creation data
    88:     );
    89: 
    90:     if (!hPreviewItemWnd)
    91:     {
    92:         UnregisterClass(szPreviewItemName, hPreviewItemInstance); // Unregister window class
    93:         MessageBox(0, "Error creating preview item window!", szPreviewItemName, MB_OK | MB_ICONERROR | MB_TOPMOST);
    94:         Log("PreviewItem: Error creating window!", NULL);
    95:         return;
    96:     }
    97: 
    98:     ShowWindow(hPreviewItemWnd, SW_HIDE);
    99:     SetWindowPos(hPreviewItemWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE);
   100:     MakeSticky(hPreviewItemWnd); // Make the preview item window sticky... (i.e. shown regardless of the current workspace)
   101: 
   102:     //====================
   103: 
   104:     // Subscribe to Blackbox messages applicable to menu preview items...
   105:     SendMessage(hBlackboxWnd, BB_REGISTERMESSAGE, (WPARAM)hPreviewItemWnd, (LPARAM)previewItemMessageSubscription);
   106: }
   107: 
   108: //===========================================================================
   109: 
   110: PreviewItem::~PreviewItem()
   111: {
   112:     // Unsubscribe to previously subscribed Blackbox messages...
   113:     SendMessage(hBlackboxWnd, BB_UNREGISTERMESSAGE, (WPARAM)hPreviewItemWnd, (LPARAM)previewItemMessageSubscription);
   114: 
   115:     if (hPreviewItemWnd) DestroyWindow(hPreviewItemWnd); // Destroy window....
   116:     UnregisterClass(szPreviewItemName, hPreviewItemInstance); // Unregister window class...
   117:     if (cachedBackground) DeleteDC(cachedBackground); // Delete the cached gradient...
   118: 
   119:     // Shutdown GDI+...
   120: //  GdiplusShutdown(gdiplusToken);
   121: }
   122: 
   123: //===========================================================================
   124: 
   125: LRESULT CALLBACK PreviewItemWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
   126: {
   127: 
   128:     switch (message)
   129:     {
   130:         case WM_TIMER:
   131:         {
   132:             if (wParam == PREVIEW_ITEM_TIMER)
   133:             {
   134:                 KillTimer(pPreviewItem->hPreviewItemWnd, PREVIEW_ITEM_TIMER);
   135:                 pPreviewItem->PreviewItemHidden = false;
   136:                 pPreviewItem->UpdatePreviewItemWindow(pPreviewItem->newItemX, pPreviewItem->newItemY, pPreviewItem->newItemWidth, pPreviewItem->newItemHeight, pPreviewItem->newItemPath);
   137:                 ShowWindow(pPreviewItem->hPreviewItemWnd, SW_SHOWNOACTIVATE);
   138:                 SetWindowPos(pPreviewItem->hPreviewItemWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE | SWP_NOOWNERZORDER);
   139:             }
   140: 
   141:             return 0;
   142:         }
   143:         break;
   144: 
   145:         //====================
   146: 
   147:         case BB_RECONFIGURE:
   148:         {
   149:             pPreviewItem->Hide();
   150:         }
   151:         break;
   152: 
   153:         //====================
   154: 
   155:         case WM_CLOSE:
   156:             return 0;
   157: 
   158:         //====================
   159: 
   160:         case WM_NCHITTEST:
   161:             return HTCLIENT;
   162: 
   163:         //====================
   164: 
   165:         default:
   166:             return DefWindowProc(hwnd, message, wParam, lParam);
   167: 
   168:             //====================
   169:     }
   170:     return 0;
   171: }
   172: 
   173: //===========================================================================
   174: // Function: UpdatePreviewItemWindow
   175: // Purpose: ...
   176: //===========================================================================
   177: 
   178: void PreviewItem::UpdatePreviewItemWindow(int x, int y, int width, int height, char* path)
   179: {
   180:     if (PreviewItemHidden) return;
   181: 
   182:     // Load the image using GDI+ to add support for JPG, PNG etc...
   183:     // (nb. regular GDI LoadImage() only supports BMP images. GDI+ startup/shutdown is performed by MenuCommon.)
   184:     HDC buf = CreateCompatibleDC(cachedBackground);
   185:     HBITMAP image = NULL;
   186:     int imageWidth = 0, imageHeight = 0;
   187:     if (strlen(path) > 0)
   188:     {
   189:         wchar_t imagePathWC[MAX_PATH];
   190: //      mbstowcs(imagePathWC, path, MAX_PATH);
   191:         MultiByteToWideChar(CP_ACP, 0, path, strlen(path) + 1, imagePathWC, MAX_PATH);
   192:         Bitmap* bitmap = new Bitmap(imagePathWC, FALSE);
   193:         imageWidth = bitmap->GetWidth();
   194:         imageHeight = bitmap->GetHeight();
   195:         bitmap->GetHBITMAP(0, &image);
   196:         delete bitmap;
   197:     }
   198: 
   199:     //====================
   200: 
   201:     PreviewItemX = x;
   202:     PreviewItemWidth = 150 * pSettings->scalingFactorHiDPI;
   203: 
   204:     if (image != NULL) // Image loading succeeded!
   205:     {
   206:         PreviewItemHeight = (PreviewItemWidth * imageHeight) / imageWidth;
   207:     }
   208:     else // Image loading failed!
   209:     {
   210:         if ((width > 0) && (height > 0)) // Are we to make room for a DWM-rendered task button live preview thumbnail instead?
   211:         {
   212:             PreviewItemWidth = width;
   213:             PreviewItemHeight = height;
   214:         }
   215:         else PreviewItemHeight = (PreviewItemWidth * 9) / 16; // ...otherwise let's use our default 16:9 aspect ratio
   216:     }
   217: 
   218:     PreviewItemY = y - (PreviewItemHeight / 2);
   219: 
   220:     int screenY = GetSystemMetrics(SM_YVIRTUALSCREEN);
   221:     if (PreviewItemY < screenY) PreviewItemY = screenY;
   222:     int screenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
   223:     if ((PreviewItemY + PreviewItemHeight) > screenHeight) PreviewItemY = screenHeight - PreviewItemHeight;
   224: 
   225:     //====================
   226: 
   227:     RECT r;
   228:     SetRect(&r, 0, 0, PreviewItemWidth, PreviewItemHeight);
   229: 
   230:     HDC hdc = GetWindowDC(hPreviewItemWnd);
   231:     HBITMAP bufbmp = CreateCompatibleBitmap(hdc, (r.right-r.left), (r.bottom-r.top));
   232:     DeleteObject(SelectObject(cachedBackground, bufbmp));
   233:     ReleaseDC(hPreviewItemWnd, hdc);
   234: 
   235:     //====================
   236: 
   237:     // Draw background...
   238:     SetRect(&r, 0, 0, PreviewItemWidth, PreviewItemHeight);
   239:     HBRUSH brush = CreateSolidBrush(0x000000);
   240:     FillRect(cachedBackground, &r, brush);
   241:     DeleteObject(brush);
   242: 
   243:     //====================
   244: 
   245:     int previewBorder = 2 * pSettings->scalingFactorHiDPI;
   246: 
   247:     if (image != NULL)
   248:     {
   249:         // Draw scaled down image...
   250:         DeleteObject(SelectObject(buf, image));
   251:         SetStretchBltMode(cachedBackground, HALFTONE);
   252:         StretchBlt(cachedBackground, previewBorder, previewBorder, PreviewItemWidth-(previewBorder*2), PreviewItemHeight-(previewBorder*2), buf, 0, 0, imageWidth, imageHeight, SRCCOPY);
   253:         // ### Below: While AlphaBlend() can be used to draw 32bpp ARGB images with per pixel alpha transparency, it also scales images down
   254:         // much less smoothly than StretchBlt() as it reverts from HALFTONE (i.e. pixel averaging) to COLORONCOLOR (i.e. pixel deleting) mode;
   255:         // for more information see https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-alphablend ###
   256: //      BLENDFUNCTION bf;
   257: //      ZeroMemory(&bf, sizeof(BLENDFUNCTION));
   258: //      bf.BlendOp = AC_SRC_OVER;
   259: //      bf.BlendFlags = 0;
   260: //      bf.SourceConstantAlpha = 255;
   261: //      bf.AlphaFormat = AC_SRC_ALPHA;
   262: //      AlphaBlend(cachedBackground, previewBorder, previewBorder, PreviewItemWidth-(previewBorder*2), PreviewItemHeight-(previewBorder*2), buf, 0, 0, imageWidth, imageHeight, bf);
   263:         DeleteObject(image);
   264:     }
   265:     else if (strlen(path) > 0)
   266:     {
   267:         // Draw the path string only if the image couldn't be loaded...
   268:         HFONT font = CreateFont((10*pSettings->scalingFactorHiDPI), 0, 0, 0, FW_NORMAL, false, false, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, "Segoe UI");
   269:         HGDIOBJ oldfont = SelectObject(cachedBackground, font);
   270:         SetBkMode(cachedBackground, TRANSPARENT);
   271:         InflateRect(&r, -previewBorder, -previewBorder);
   272:         DrawTextWithEffects(cachedBackground, r, path, DT_LEFT | DT_VCENTER | DT_NOPREFIX | DT_PATH_ELLIPSIS, 0xffffff, false, 0, false, 0, 0, 0);
   273:         DeleteObject(SelectObject(cachedBackground, oldfont));
   274:     }
   275: 
   276:     DeleteDC(buf);
   277: 
   278:     //====================
   279: 
   280:     if (bufbmp) DeleteObject(bufbmp);
   281: 
   282:     POINT pt;
   283:     pt.x = PreviewItemX, pt.y = PreviewItemY;
   284:     POINT ptSrc;
   285:     ptSrc.x = 0, ptSrc.y = 0;
   286: 
   287:     BLENDFUNCTION bf;
   288:     bf.BlendOp = AC_SRC_OVER;
   289:     bf.BlendFlags = 0;
   290:     bf.AlphaFormat = AC_SRC_ALPHA;
   291:     bf.SourceConstantAlpha = (unsigned char)255;
   292: 
   293:     SIZE windowSize = { PreviewItemWidth, PreviewItemHeight };
   294:     BOOL result = UpdateLayeredWindow(hPreviewItemWnd, NULL, &pt, &windowSize, cachedBackground, &ptSrc, 0, &bf, ULW_OPAQUE);
   295: /*
   296:     if (!cachedBackground) SendMessage(GetBBWnd(), BB_CONSOLEMESSAGE, (WPARAM)CONSOLE_INFORMATION_MESSAGE, (LPARAM)"xoblite -> PreviewItem -> Invalid cachedBackground!");
   297: 
   298:     if (result == 0)
   299:     {
   300:         int error = GetLastError();
   301:         char msg[255];
   302:         sprintf_s(msg, sizeofArray(msg), "xoblite -> PreviewItem -> UpdateLayeredWindow failed! [error code %d]", error);
   303:         SendMessage(GetBBWnd(), BB_CONSOLEMESSAGE, (WPARAM)CONSOLE_ERROR_MESSAGE, (LPARAM)msg);
   304:     }
   305: */
   306: }
   307: 
   308: //===========================================================================
   309: 
   310: void PreviewItem::Show(int x, int y, int width, int height, char* path, int delay)
   311: {
   312:     KillTimer(pPreviewItem->hPreviewItemWnd, PREVIEW_ITEM_TIMER);
   313: 
   314:     // Save the new preview item parameters...
   315:     newItemX = x;
   316:     newItemY = y;
   317:     newItemWidth = width;
   318:     newItemHeight = height;
   319: 
   320:     if ((path != NULL) && (strlen(path) > 0)) strcpy_s(newItemPath, sizeofArray(newItemPath), path);
   321:     else newItemPath[0] = '\0';
   322: 
   323:     // ... and display it after a configurable short delay...
   324:     // (nb. we do this to get smoother fast scrolling through parent menus,
   325:     //  i.e. no loading of preview images on every single entry while scrolling)
   326:     SetTimer(pPreviewItem->hPreviewItemWnd, PREVIEW_ITEM_TIMER, delay, (TIMERPROC)NULL);
   327: }
   328: 
   329: void PreviewItem::Hide()
   330: {
   331: //  if (!PreviewItemHidden)
   332:     {
   333:         KillTimer(pPreviewItem->hPreviewItemWnd, PREVIEW_ITEM_TIMER);
   334:         PreviewItemHidden = true;
   335:         ShowWindow(hPreviewItemWnd, SW_HIDE);
   336:     }
   337: }
   338: 
   339: int PreviewItem::GetWidth()
   340: {
   341:     return PreviewItemWidth;
   342: }
   343: 
   344: int PreviewItem::GetHeight()
   345: {
   346:     return PreviewItemHeight;
   347: }
   348: 
   349: //===========================================================================
   350: