forked from ephemeralViolette/ClassicExplorer
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathThrobberBand.cpp
More file actions
611 lines (513 loc) · 15.7 KB
/
ThrobberBand.cpp
File metadata and controls
611 lines (513 loc) · 15.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
/*
* ThrobberBand.cpp: Implements the throbber/brand band. This displays the Windows logo
* in the upper-right corner of the Explorer window.
*/
#include "stdafx.h"
#include "framework.h"
#include "resource.h"
#include "ClassicExplorer_i.h"
#include "dllmain.h"
#include <commoncontrols.h>
#include "util.h"
#include "ThrobberBand.h"
void ThrobberBand::ClearResources()
{
DeleteObject(m_hBitmap);
m_pWebBrowser.Release();
}
/*
* OnPaint: Paint the background and the logo.
*/
LRESULT ThrobberBand::OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
PAINTSTRUCT paintInfo;
RECT clientRect;
HDC dc = BeginPaint(&paintInfo);
GetClientRect(&clientRect);
// Calculate destination point:
POINT destinationPoint;
destinationPoint.x = (clientRect.right - clientRect.left - m_cxCurBmp) / 2;
destinationPoint.y = (clientRect.bottom - clientRect.top - m_cyCurBmp) / 2;
COLORREF background = m_theme == CLASSIC_EXPLORER_2K ? RGB(0, 0, 0) : RGB(255, 255, 255);
SetBkColor(dc, background);
// Draw the background
HBRUSH bgBrush = CreateSolidBrush(background);
FillRect(dc, &clientRect, bgBrush);
DeleteObject(bgBrush);
HDC sourceDc = CreateCompatibleDC(dc);
HBITMAP oldBitmap = (HBITMAP)SelectObject(sourceDc, m_hBitmap);
BitBlt(
dc,
destinationPoint.x,
destinationPoint.y,
m_cxCurBmp,
m_cyCurBmp,
sourceDc,
0,
0,
SRCCOPY
);
SelectObject(sourceDc, oldBitmap);
DeleteDC(sourceDc);
EndPaint(&paintInfo);
return S_OK;
}
/*
* OnSize: Handle size messages and reload the desired logo bitmap for the current size.
*/
LRESULT ThrobberBand::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
LoadBitmapForSize();
return DefWindowProcW(uMsg, wParam, lParam);
}
/*
* OnEraseBackground: We always paint our own background, so there's no need to ever do this.
*/
LRESULT ThrobberBand::OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
{
return 1;
}
LRESULT ThrobberBand::OnClick(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
CEUtil::CESettings currentSettings = CEUtil::GetCESettings();
HMENU hMenu = CreatePopupMenu();
AppendMenuW(hMenu, (m_theme == CLASSIC_EXPLORER_2K ? MF_CHECKED : MF_UNCHECKED) | MF_STRING, m_theme == CLASSIC_EXPLORER_2K ? 0 : 7000, L"2K Skin");
AppendMenuW(hMenu, (m_theme == CLASSIC_EXPLORER_XP ? MF_CHECKED : MF_UNCHECKED) | MF_STRING, m_theme == CLASSIC_EXPLORER_XP ? 0 : 7001, L"XP Skin");
AppendMenuW(hMenu, (m_theme == CLASSIC_EXPLORER_10 ? MF_CHECKED : MF_UNCHECKED) | MF_STRING, m_theme == CLASSIC_EXPLORER_10 ? 0 : 7002, L"10 Skin");
AppendMenuW(hMenu, MF_SEPARATOR, 0, 0);
AppendMenuW(hMenu, (currentSettings.showGoButton ? MF_CHECKED : MF_UNCHECKED) | MF_STRING, 7010, L"Show Go button");
AppendMenuW(hMenu, (currentSettings.showAddressLabel ? MF_CHECKED : MF_UNCHECKED) | MF_STRING, 7011, L"Show Address label");
AppendMenuW(hMenu, (currentSettings.showFullAddress ? MF_CHECKED : MF_UNCHECKED) | MF_STRING, 7012, L"Show full address");
POINT p;
p.x = GET_X_LPARAM(lParam);
p.y = GET_Y_LPARAM(lParam);
HWND hWnd;
GetWindow(&hWnd);
ClientToScreen(&p);
int sel = TrackPopupMenu(hMenu, TPM_RETURNCMD, p.x, p.y, 0, hWnd, NULL);
DestroyMenu(hMenu);
if(sel == 0) // Current theme selected, or outside click, nothing changes
return S_OK;
switch (sel)
{
case 7000:
CEUtil::WriteCESettings(CEUtil::CESettings(CLASSIC_EXPLORER_2K, -1, -1, -1));
break;
case 7001:
CEUtil::WriteCESettings(CEUtil::CESettings(CLASSIC_EXPLORER_XP, -1, -1, -1));
break;
case 7002:
CEUtil::WriteCESettings(CEUtil::CESettings(CLASSIC_EXPLORER_10, -1, -1, -1));
break;
case 7010:
CEUtil::WriteCESettings(CEUtil::CESettings(CLASSIC_EXPLORER_NONE, !currentSettings.showGoButton, -1, -1));
break;
case 7011:
CEUtil::WriteCESettings(CEUtil::CESettings(CLASSIC_EXPLORER_NONE, -1, !currentSettings.showAddressLabel, -1));
break;
case 7012:
CEUtil::WriteCESettings(CEUtil::CESettings(CLASSIC_EXPLORER_NONE, -1, -1, !currentSettings.showFullAddress));
break;
}
MessageBeep(0);
MessageBox(L"Open a new file explorer window to see the changes.");
return S_OK;
}
/*
* LoadBitmapForSize: Load the desired throbber icon for the current size of the band.
*/
LRESULT ThrobberBand::LoadBitmapForSize()
{
RECT curRect;
GetClientRect(&curRect);
DeleteObject(m_hBitmap);
int cySelf = curRect.bottom - curRect.top;
int resourceId = IDB_10_THROBBER_SIZE_SMALL;
if (cySelf >= 38)
{
switch (m_theme)
{
default:
case CLASSIC_EXPLORER_10:
resourceId = IDB_10_THROBBER_SIZE_LARGE;
break;
case CLASSIC_EXPLORER_2K:
resourceId = IDB_2K_THROBBER_SIZE_LARGE;
break;
case CLASSIC_EXPLORER_XP:
resourceId = IDB_XP_THROBBER_SIZE_LARGE;
break;
}
}
else if (cySelf >= 26)
{
switch (m_theme)
{
default:
case CLASSIC_EXPLORER_10:
resourceId = IDB_10_THROBBER_SIZE_MID;
break;
case CLASSIC_EXPLORER_2K:
resourceId = IDB_2K_THROBBER_SIZE_MID;
break;
case CLASSIC_EXPLORER_XP:
resourceId = IDB_XP_THROBBER_SIZE_MID;
break;
}
}
else
{
switch (m_theme)
{
default:
case CLASSIC_EXPLORER_10:
resourceId = IDB_10_THROBBER_SIZE_SMALL;
break;
case CLASSIC_EXPLORER_2K:
resourceId = IDB_2K_THROBBER_SIZE_SMALL;
break;
case CLASSIC_EXPLORER_XP:
resourceId = IDB_XP_THROBBER_SIZE_SMALL;
break;
}
}
m_hBitmap = LoadBitmapW(
_AtlBaseModule.GetResourceInstance(),
MAKEINTRESOURCEW(resourceId)
);
BITMAP bmp;
GetObject(m_hBitmap, sizeof(bmp), &bmp);
m_cxCurBmp = bmp.bmWidth;
m_cyCurBmp = bmp.bmHeight;
return S_OK;
}
/*
* CorrectBandSize: Correct the height of the band to be displayed.
*
* This may seem like a hacky solution, but it is actually necessary, as in my testing, using
* the default integral scaling would only scale up and never scale back down to fit sibling
* toolbars on the same row.
*
* Using this solution, the band is resized by sending some messages to the ReBar API which is
* owned by Explorer.
*
* I spent several days straight trying to figure out how to do this; please don't make fun of
* me too much...
*/
LRESULT ThrobberBand::CorrectBandSize()
{
RECT curRect;
GetClientRect(&curRect);
UINT initialTrHeight = ::SendMessageW(m_parentRebar, RB_GETROWHEIGHT, 0, 0);
REBARBANDINFOW curBandInfo;
curBandInfo.cbSize = sizeof(REBARBANDINFOW);
curBandInfo.fMask = RBBIM_CHILD;
// This was never an officially-supported feature of shell rebars in Windows, so there's
// no function to just get the interface for our own rebar band.
for (int i = 0; i < ::SendMessageW(m_parentRebar, RB_GETBANDCOUNT, 0, 0); i++)
{
::SendMessageW(m_parentRebar, RB_GETBANDINFO, i, (LPARAM)&curBandInfo);
if (::IsWindow(curBandInfo.hwndChild) && curBandInfo.hwndChild == m_hWnd)
{
// Get the rebar info:
REBARBANDINFOW curBandInfo2;
curBandInfo2.cbSize = sizeof(REBARBANDINFOW);
curBandInfo2.fMask = RBBIM_SIZE | RBBIM_CHILDSIZE;
::SendMessageW(m_parentRebar, RB_GETBANDINFO, i, (LPARAM)&curBandInfo2);
// Set the bar to the minimum size possible.
// Yes, it was necessary to define all of these, or it would half in horizontal size
// every time this function was called.
curBandInfo2.fMask = RBBIM_CHILDSIZE;
curBandInfo2.cxMinChild = 38;
curBandInfo2.cyMinChild = 22;
curBandInfo2.cyChild = 22;
curBandInfo2.cyIntegral = 1;
::SendMessageW(m_parentRebar, RB_SETBANDINFOW, i, (LPARAM)&curBandInfo2);
// Resize the bar back up to what it should be: the size of the topmost row.
UINT topRowHeight = ::SendMessageW(m_parentRebar, RB_GETROWHEIGHT, 0, 0);
curBandInfo2.cyChild = topRowHeight;
::SendMessageW(m_parentRebar, RB_SETBANDINFOW, i, (LPARAM)&curBandInfo2);
// Explorer isn't notified of this resize, so we need to manually invalidate the
// visual or a vertical gap may be left under the rebar.
if (initialTrHeight > topRowHeight)
{
m_shouldManuallyCorrectHeight = true;
}
}
}
return S_OK;
}
/*
* ShouldRefreshVisual: Determines if the visual needs manual correction from our code.
*/
bool ThrobberBand::ShouldRefreshVisual()
{
if (::IsWindow(m_parentRebar))
{
int numRebars = ::SendMessageW(m_parentRebar, RB_GETBANDCOUNT, 0, 0);
// If there is only one rebar control for whatever reason, then we don't want to try
// redrawing or a redraw loop may occur.
if (numRebars > 1)
{
RECT rcOffset;
GetClientRect(&rcOffset);
::MapWindowPoints(m_hWnd, m_parentRebar, (LPPOINT)&rcOffset, 2);
WCHAR buf[512];
wsprintf(buf, L"%d, %d", rcOffset.left, rcOffset.top);
//MessageBoxW(buf, L"hi!", MB_OK);
if (rcOffset.left < 12)
{
return true;
}
}
}
return false;
}
void ThrobberBand::PerformRedrawCheck()
{
const int checkDuration = 500;
const int allowedRedrawsInTimeSpan = 50;
unsigned int previousRedrawTime = m_latestRedrawTime;
m_latestRedrawTime = GetTickCount();
if (m_latestRedrawTime < previousRedrawTime + checkDuration)
{
m_redrawCounter++;
if (m_redrawCounter > allowedRedrawsInTimeSpan)
{
m_disableRedraws = true;
#ifdef DEBUG
MessageBoxW(L"Redraw loop mitigated.");
#endif
}
}
else
{
m_redrawCounter = 0;
}
}
/*
* RebarParentSubclassProc: This reads notifications (not messages) of the rebar, which is done by hooking
* its parent (the shell WorkerW).
*
* This is used to initialise the size-correction routine, since it works in a predictable manner, sending
* notifications about changes to the parent before enacting those changes on the child.
*/
LRESULT CALLBACK ThrobberBand::RebarParentSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
ThrobberBand *self = (ThrobberBand *)dwRefData;
if (uMsg == WM_NOTIFY)
{
LPNMHDR hdr = (LPNMHDR)lParam;
switch (hdr->code)
{
case RBN_HEIGHTCHANGE:
case RBN_LAYOUTCHANGED:
{
self->CorrectBandSize();
break;
}
}
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
/*
* RebarSubclassProc: This reads messages sent to the rebar itself, which is used to send out the sizing
* command when it is needed.
*/
LRESULT CALLBACK ThrobberBand::RebarSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
ThrobberBand *self = (ThrobberBand *)dwRefData;
if (uMsg == WM_SIZE)
{
if (self->m_shouldManuallyCorrectHeight)
{
// Mark the correction as no longer necessary as we handle it here:
self->m_shouldManuallyCorrectHeight = false;
LRESULT result = DefSubclassProc(hWnd, uMsg, wParam, lParam);
CEUtil::FixExplorerSizesIfNecessary(self->m_parentRebar);
return result;
}
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
//================================================================================================================
// implement IDeskBand:
//
STDMETHODIMP ThrobberBand::GetBandInfo(DWORD dwBandId, DWORD dwViewMode, DESKBANDINFO *pDbi)
{
/*if (!m_subclassedRebar)
{
m_parentRebar = GetParent();
SetWindowSubclass(::GetParent(m_parentRebar), RebarParentSubclassProc, (UINT_PTR)this, (UINT_PTR)this);
SetWindowSubclass(m_parentRebar, RebarSubclassProc, (UINT_PTR)this, (UINT_PTR)this);
m_subclassedRebar = true;
}*/
if (pDbi)
{
if (pDbi->dwMask & DBIM_MINSIZE)
{
pDbi->ptMinSize.x = 38;
pDbi->ptMinSize.y = 22;
}
if (pDbi->dwMask & DBIM_MAXSIZE)
{
// The throbber should be able to adjust to the size of any sibling rebar, but
// should always be locked to a size of 38 pixels horizontally.
pDbi->ptMaxSize.x = 38;
pDbi->ptMaxSize.y = -1;
}
if (pDbi->dwMask & DBIM_INTEGRAL)
{
// We want the throbber to grow organically alongside sibling rebars, without any
// blank spaces. As such, it is to grow every 1 pixel vertically.
pDbi->ptIntegral.x = 0;
pDbi->ptIntegral.y = 1;
}
if (pDbi->dwMask & DBIM_ACTUAL)
{
pDbi->ptActual.x = 38;
pDbi->ptActual.y = -1;
}
if (pDbi->dwMask & DBIM_TITLE)
{
// No title.
wcscpy_s(pDbi->wszTitle, L"");
}
if (pDbi->dwMask & DBIM_MODEFLAGS)
{
pDbi->dwModeFlags = DBIMF_FIXED | DBIMF_TOPALIGN | DBIMF_VARIABLEHEIGHT;
}
if (pDbi->dwMask & DBIM_BKCOLOR)
{
// We draw the background ourselves (in OnPaint), so there's no need to bother
// with it here.
pDbi->crBkgnd = 0;
}
}
return S_OK;
}
//================================================================================================================
// implement IOleWindow:
//
STDMETHODIMP ThrobberBand::GetWindow(HWND *hWnd)
{
if (!hWnd)
{
return E_INVALIDARG;
}
*hWnd = m_hWnd;
return S_OK;
}
STDMETHODIMP ThrobberBand::ContextSensitiveHelp(BOOL fEnterMode)
{
return S_OK;
}
//================================================================================================================
// implement IDockingWindow:
//
STDMETHODIMP ThrobberBand::CloseDW(unsigned long dwReserved)
{
ShowDW(FALSE);
if (IsWindow())
DestroyWindow();
m_hWnd = NULL;
return S_OK;
}
STDMETHODIMP ThrobberBand::ResizeBorderDW(const RECT *pRcBorder, IUnknown *pUnkToolbarSite, BOOL fReserved)
{
return E_NOTIMPL;
}
STDMETHODIMP ThrobberBand::ShowDW(BOOL fShow)
{
if (m_hWnd)
ShowWindow(fShow ? SW_SHOW : SW_HIDE);
return S_OK;
}
//================================================================================================================
// implement IObjectWithSite:
//
/**
* SetSite: Responsible for installation or removal of the toolbar band from a location
* provided by Explorer.
*
* This function is additionally responsible for obtaining the shell control APIs and creating
* the inner toolbar window.
*/
STDMETHODIMP ThrobberBand::SetSite(IUnknown *pUnkSite)
{
IObjectWithSiteImpl<ThrobberBand>::SetSite(pUnkSite);
HRESULT hr;
CComPtr<IOleWindow> oleWindow;
if (pUnkSite == NULL)
{
ClearResources();
return S_OK;
}
HWND hWndParent = NULL;
CComQIPtr<IOleWindow> pOleWindow = pUnkSite;
if (pOleWindow)
pOleWindow->GetWindow(&hWndParent);
if (!::IsWindow(hWndParent))
{
return E_FAIL;
}
this->Create(
hWndParent,
NULL,
NULL,
WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN
);
if (!IsWindow())
return E_FAIL;
CComQIPtr<IServiceProvider> pProvider = pUnkSite;
if (pProvider)
{
// No error handling; this can fail safely.
pProvider->QueryService(SID_SWebBrowserApp, IID_IWebBrowser2, (void **)&m_pWebBrowser);
if (m_pWebBrowser)
{
if (m_dwEventCookie == 0xFEFEFEFE)
{
//MessageBox(L"d");
DispEventAdvise(m_pWebBrowser, &DIID_DWebBrowserEvents2);
}
}
}
LoadBitmapForSize();
// Subclass helpers:
m_parentRebar = GetParent();
SetWindowSubclass(::GetParent(m_parentRebar), RebarParentSubclassProc, (UINT_PTR)this, (UINT_PTR)this);
SetWindowSubclass(m_parentRebar, RebarSubclassProc, (UINT_PTR)this, (UINT_PTR)this);
m_subclassedRebar = true;
// Read settings from registry
CEUtil::CESettings cS = CEUtil::GetCESettings();
m_theme = cS.theme;
// Explorer may initialise our position onto a separate rebar until the sizes are
// invalidated, so let's manually invalidate to correct the position:
CEUtil::FixExplorerSizes(this->m_hWnd);
return S_OK;
}
//================================================================================================================
// implement DWebBrowserEvents2:
//
STDMETHODIMP ThrobberBand::OnNavigateComplete(IDispatch *pDisp, VARIANT *url)
{
//MessageBox(L"fuck you");
CEUtil::FixExplorerSizes(this->m_hWnd);
//::SendMessageW(m_parentRebar, WM_SIZE, 0, 1);
return S_OK;
}
/**
* OnQuit: Called when the user attempts to quit the browser.
*
* Copied from Open-Shell implementation here:
* https://github.com/Open-Shell/Open-Shell-Menu/blob/master/Src/ClassicExplorer/ExplorerBand.cpp#L2280-L2285
*/
STDMETHODIMP ThrobberBand::OnQuit()
{
if (m_pWebBrowser && m_dwEventCookie != 0xFEFEFEFE)
{
return DispEventUnadvise(m_pWebBrowser, &DIID_DWebBrowserEvents2);
}
return S_OK;
}