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: