mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1886 lines
46 KiB
1886 lines
46 KiB
#define NO_INCLUDE_UNION
|
|
|
|
#include "shellprv.h"
|
|
#include "ids.h"
|
|
#include "ole2dup.h"
|
|
#include <regstr.h>
|
|
#include "shlobjp.h"
|
|
#include "idlcomm.h"
|
|
#include "fstreex.h"
|
|
#include "views.h"
|
|
#include <mmsystem.h>
|
|
#include <shguidp.h>
|
|
#include "ids.h"
|
|
|
|
// I'm disabling this warning (truncation of constant value) because I don't
|
|
// want to touch this code.
|
|
|
|
#pragma warning(disable: 4310)
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
static DWORD gdwRandSeed = 5610996;
|
|
|
|
__inline void
|
|
Seed_dwRand(DWORD dwSeed)
|
|
{
|
|
gdwRandSeed = dwSeed;
|
|
}
|
|
|
|
__inline DWORD idwRand()
|
|
{
|
|
// who cares if another thread clobbers us
|
|
gdwRandSeed *= 69069;
|
|
return ++gdwRandSeed;
|
|
}
|
|
|
|
DWORD dwRand()
|
|
{
|
|
return idwRand();
|
|
}
|
|
|
|
DWORD dwRandom(DWORD dwRange)
|
|
{
|
|
return idwRand() % dwRange;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Helpers and constants
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#define CLVM_ANIMATE WM_APP
|
|
|
|
#define CLOUD_TITLE_FONT (0)
|
|
#define CLOUD_NORMAL_FONT (1)
|
|
#define CLOUD_GOODIE_FONT (2)
|
|
|
|
#define NUM_CLOUD_FONTS (3)
|
|
|
|
#define CLOUD_MIN_FONTSIZE (8)
|
|
#define CLOUD_MAX_FONTSIZE (72)
|
|
|
|
#define CLOUD_MIN_ELEMENTS (1)
|
|
#define CLOUD_MAX_ELEMENTS (10)
|
|
|
|
#define CLOUD_FONT_WNDSIZE_DIVISOR (10)
|
|
|
|
#define ACTIVE_COLORS (240)
|
|
#define ROW_SIZE (16)
|
|
|
|
#define NUM_ROWS (ACTIVE_COLORS / ROW_SIZE)
|
|
#define TOP_ROW_START (ACTIVE_COLORS - ROW_SIZE)
|
|
|
|
#define MAX_OPACITY (999)
|
|
|
|
#define OFFSCREEN_STRIPS (4)
|
|
|
|
inline BYTE
|
|
CalcRowData(int row, BYTE master)
|
|
{
|
|
return (BYTE)(((NUM_ROWS - row) * master) / (2 * NUM_ROWS)) + (master / 2);
|
|
}
|
|
|
|
inline BYTE
|
|
OpacityRow(int opacity)
|
|
{
|
|
return (BYTE)(((UINT)opacity * NUM_ROWS) / (1 + MAX_OPACITY));
|
|
}
|
|
|
|
inline BYTE
|
|
InterRowEffect(BYTE dstrow, BYTE srcrow)
|
|
{
|
|
return (dstrow ^ srcrow) << 4;
|
|
}
|
|
|
|
inline BYTE
|
|
StdOpacityEffect(int opacity)
|
|
{
|
|
return InterRowEffect(OpacityRow(opacity), OpacityRow(0));
|
|
}
|
|
|
|
HPALETTE PaletteFromDS(HDC hdc)
|
|
{
|
|
DWORD adw[257];
|
|
int i,n;
|
|
|
|
n = GetDIBColorTable(hdc, 0, 256, (LPRGBQUAD)&adw[1]);
|
|
|
|
for (i=1; i<=n; i++)
|
|
adw[i] = RGB(GetBValue(adw[i]),GetGValue(adw[i]),GetRValue(adw[i]));
|
|
|
|
adw[0] = MAKELONG(0x300, n);
|
|
|
|
return CreatePalette((LPLOGPALETTE)&adw[0]);
|
|
}
|
|
|
|
typedef DWORD (WINAPI *DWVOIDFN)();
|
|
typedef MCIERROR (WINAPI *MCISSFN)(LPCTSTR, LPTSTR, UINT, HWND);
|
|
|
|
extern TCHAR const c_szWinMMDll[];
|
|
|
|
CHAR const c_sztimeGetTime[] = "timeGetTime"; // No TEXT()
|
|
CHAR const c_szmciSendString[] = "mciSendStringA"; // No TEXT()
|
|
|
|
static DWORD g_dwCloudGroup = 0xFFFFFFFF;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// syntactic crutches
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
template<WORD BPP, DWORD CLRUSED>
|
|
class BitmapInfo
|
|
{
|
|
public:
|
|
BITMAPINFOHEADER header;
|
|
RGBQUAD colors[CLRUSED];
|
|
|
|
BitmapInfo();
|
|
BitmapInfo(LONG cx, LONG cy, DWORD important = 0);
|
|
|
|
operator BITMAPINFO *() { return (BITMAPINFO *)&header; }
|
|
};
|
|
|
|
template<WORD BPP, DWORD CLRUSED>
|
|
BitmapInfo<BPP, CLRUSED>::BitmapInfo()
|
|
{
|
|
ZeroMemory(&header, SIZEOF(BITMAPINFOHEADER));
|
|
header.biSize = SIZEOF(BITMAPINFOHEADER);
|
|
header.biPlanes = 1;
|
|
header.biBitCount = BPP;
|
|
header.biClrUsed = CLRUSED;
|
|
}
|
|
|
|
template< WORD BPP, DWORD CLRUSED >
|
|
BitmapInfo< BPP, CLRUSED >::BitmapInfo( LONG cx, LONG cy, DWORD important )
|
|
{
|
|
header.biSize = SIZEOF(BITMAPINFOHEADER);
|
|
header.biWidth = cx;
|
|
header.biHeight = cy;
|
|
header.biPlanes = 1;
|
|
header.biBitCount = BPP;
|
|
header.biCompression = BI_RGB;
|
|
header.biSizeImage = 0;
|
|
header.biXPelsPerMeter = 0;
|
|
header.biYPelsPerMeter = 0;
|
|
header.biClrUsed = CLRUSED;
|
|
header.biClrImportant = important;
|
|
}
|
|
|
|
class Image
|
|
{
|
|
public:
|
|
Image(HBITMAP newbmp = NULL);
|
|
~Image() { *this = NULL; }
|
|
|
|
operator HDC () const { return dc; }
|
|
Image &operator = (HBITMAP newbmp);
|
|
|
|
HBITMAP Bitmap() const { return bmp; }
|
|
|
|
private:
|
|
HBITMAP bmp, defbmp;
|
|
HDC dc;
|
|
};
|
|
|
|
Image::Image(HBITMAP newbmp) :
|
|
bmp(NULL), defbmp(NULL), dc(NULL)
|
|
{
|
|
*this = newbmp;
|
|
}
|
|
|
|
Image &
|
|
Image::operator = (HBITMAP newbmp)
|
|
{
|
|
if (newbmp != bmp)
|
|
{
|
|
if (bmp)
|
|
{
|
|
SelectBitmap(dc, defbmp);
|
|
DeleteBitmap(bmp);
|
|
}
|
|
|
|
if (newbmp)
|
|
{
|
|
if (!dc)
|
|
dc = CreateCompatibleDC(NULL);
|
|
|
|
if (dc)
|
|
{
|
|
HBITMAP old = SelectBitmap(dc, newbmp);
|
|
|
|
if (!defbmp)
|
|
defbmp = old;
|
|
}
|
|
else
|
|
{
|
|
DeleteBitmap(newbmp);
|
|
newbmp = NULL;
|
|
}
|
|
}
|
|
else if (dc)
|
|
{
|
|
DeleteDC(dc);
|
|
dc = NULL;
|
|
}
|
|
|
|
bmp = newbmp;
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// CloudView
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
class CloudView :
|
|
public IShellView
|
|
{
|
|
struct Element
|
|
{
|
|
Image image;
|
|
COLORREF coloreffect;
|
|
SIZE size;
|
|
POINT position;
|
|
int velocity;
|
|
int opacity;
|
|
int vopacity;
|
|
DWORD fadebegin;
|
|
DWORD mark; // last time animation state was evaluated
|
|
};
|
|
|
|
public:
|
|
struct Gap
|
|
{
|
|
int top, size;
|
|
Gap *next;
|
|
|
|
class Manager
|
|
{
|
|
public:
|
|
Manager(int size = 0);
|
|
~Manager();
|
|
|
|
BOOL Split(Gap *gap, int top, int size);
|
|
void Join(int top, int size);
|
|
|
|
Gap *FindLargestThatFits(int size);
|
|
void Reset(int size);
|
|
|
|
private:
|
|
Gap *head;
|
|
Gap *free;
|
|
};
|
|
};
|
|
|
|
CloudView(HWND _parent);
|
|
~CloudView();
|
|
|
|
STDMETHODIMP QueryInterface(REFIID, LPVOID *);
|
|
STDMETHODIMP_(ULONG) AddRef();
|
|
STDMETHODIMP_(ULONG) Release();
|
|
|
|
STDMETHODIMP GetWindow(HWND *);
|
|
STDMETHODIMP ContextSensitiveHelp(BOOL);
|
|
STDMETHODIMP TranslateAccelerator(LPMSG);
|
|
STDMETHODIMP EnableModeless(BOOL);
|
|
STDMETHODIMP UIActivate(UINT);
|
|
STDMETHODIMP Refresh();
|
|
STDMETHODIMP CreateViewWindow(IShellView *, LPCFOLDERSETTINGS,
|
|
IShellBrowser *, RECT *, HWND *);
|
|
STDMETHODIMP DestroyViewWindow();
|
|
STDMETHODIMP GetCurrentInfo(LPFOLDERSETTINGS);
|
|
STDMETHODIMP AddPropertySheetPages(DWORD, LPFNADDPROPSHEETPAGE, LPARAM);
|
|
STDMETHODIMP SaveViewState();
|
|
STDMETHODIMP SelectItem(LPCITEMIDLIST, UINT);
|
|
STDMETHODIMP GetItemObject(UINT, REFIID, LPVOID *);
|
|
|
|
private:
|
|
HWND window, parent;
|
|
ULONG refs;
|
|
|
|
HINSTANCE mmlib;
|
|
DWVOIDFN pfntimeGetTime;
|
|
MCISSFN pfnmciSendString;
|
|
|
|
void Init(LPCREATESTRUCT);
|
|
void Die();
|
|
BOOL Erase(HDC);
|
|
void Paint();
|
|
void Realize(HDC = NULL);
|
|
void RestartAnimation();
|
|
void KillAnimation(BOOL repaint_on_kill = FALSE);
|
|
|
|
BOOL InitElementForDisplay(Element *element, Gap *gap, const char *text);
|
|
void DrawElement(HDC, Element *, int delta = 0);
|
|
void AnimationLoop();
|
|
static DWORD ThreadEntryPoint(CloudView *);
|
|
|
|
void StartMusic();
|
|
BOOL NextMusicLoop();
|
|
void StopMusic();
|
|
|
|
LRESULT MsgHandler(UINT msg, WPARAM wp, LPARAM lp);
|
|
static LRESULT _export CALLBACK WndProc(HWND wnd, UINT msg, WPARAM wp, LPARAM lp);
|
|
|
|
//
|
|
// stuff for the animation
|
|
//
|
|
CRITICAL_SECTION lock;
|
|
Image image;
|
|
SIZE tile, imgsize, wndsize;
|
|
HBRUSH eraser, oldbsh;
|
|
HPALETTE palette;
|
|
HANDLE animation;
|
|
Element *elements;
|
|
UINT count;
|
|
HFONT fonts[NUM_CLOUD_FONTS];
|
|
int maxheight;
|
|
Gap::Manager gaps;
|
|
DWORD state;
|
|
|
|
int music; // this view playing music? (0:no/1:yes/-1:was)
|
|
static LONG anymusic; // is any view playing music right now?
|
|
};
|
|
|
|
static const TCHAR c_szCloudViewClassname[] = TEXT("CloudedShellView");
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// CloudView::Gap::Manager
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
CloudView::Gap::Manager::Manager(int size) :
|
|
head(new Gap), free(NULL)
|
|
{
|
|
if (head)
|
|
{
|
|
head->top = 0;
|
|
head->size = size;
|
|
head->next = NULL;
|
|
}
|
|
}
|
|
|
|
CloudView::Gap::Manager::~Manager()
|
|
{
|
|
while (head)
|
|
{
|
|
Gap *t = head->next;
|
|
delete head;
|
|
head = t;
|
|
}
|
|
while (free)
|
|
{
|
|
Gap *t = free->next;
|
|
delete free;
|
|
free = t;
|
|
}
|
|
}
|
|
|
|
BOOL
|
|
CloudView::Gap::Manager::Split(Gap *gap, int pos, int size)
|
|
{
|
|
Gap *t;
|
|
|
|
if (free)
|
|
{
|
|
t = free;
|
|
free = t->next;
|
|
}
|
|
else
|
|
t = new Gap;
|
|
|
|
if (t)
|
|
{
|
|
t->top = pos + size;
|
|
t->size = gap->top + gap->size - t->top;
|
|
t->next = gap->next;
|
|
gap->size = pos - gap->top;
|
|
gap->next = t;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
CloudView::Gap::Manager::Join(int pos, int size)
|
|
{
|
|
//BUGBUG: this routine _really_ trusts its caller not to fuck up
|
|
Gap *above = head;
|
|
|
|
while (above && ((above->top + above->size) < pos))
|
|
above = above->next;
|
|
|
|
Gap *next = above->next;
|
|
above->size += size + next->size;
|
|
|
|
above->next = next->next;
|
|
next->next = free;
|
|
free = next;
|
|
}
|
|
|
|
CloudView::Gap *
|
|
CloudView::Gap::Manager::FindLargestThatFits(int size)
|
|
{
|
|
Gap *gap = head;
|
|
Gap *best = NULL;
|
|
|
|
size++; // must be larger by 2 pixels
|
|
|
|
while (gap)
|
|
{
|
|
if (gap->size > size)
|
|
{
|
|
best = gap;
|
|
size = best->size;
|
|
}
|
|
|
|
gap = gap->next;
|
|
}
|
|
|
|
return best;
|
|
}
|
|
|
|
void
|
|
CloudView::Gap::Manager::Reset(int size)
|
|
{
|
|
if (head)
|
|
{
|
|
while (head->next)
|
|
{
|
|
Gap *gap = head->next;
|
|
head->next = gap->next;
|
|
gap->next = free;
|
|
free = gap;
|
|
}
|
|
|
|
head->top = 0;
|
|
head->size = size;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// CloudView
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
LONG CloudView::anymusic = 0;
|
|
|
|
CloudView::CloudView(HWND _p) :
|
|
window(NULL), parent(_p), state(0), refs(1), pfntimeGetTime(NULL),
|
|
pfnmciSendString(NULL), elements(NULL), count(0), palette(NULL),
|
|
animation(NULL), music(0), eraser(NULL), oldbsh(NULL)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CloudView::CloudView()"));
|
|
|
|
UINT left = NUM_CLOUD_FONTS;
|
|
while (left--)
|
|
fonts[left] = NULL;
|
|
|
|
mmlib = LoadLibrary(c_szWinMMDll);
|
|
if (mmlib)
|
|
{
|
|
pfntimeGetTime = (DWVOIDFN)GetProcAddress(mmlib, c_sztimeGetTime);
|
|
pfnmciSendString = (MCISSFN)GetProcAddress(mmlib, c_szmciSendString);
|
|
}
|
|
|
|
if (!pfntimeGetTime)
|
|
pfntimeGetTime = (DWVOIDFN)GetTickCount;
|
|
|
|
Seed_dwRand(pfntimeGetTime());
|
|
InitializeCriticalSection(&lock);
|
|
}
|
|
|
|
CloudView::~CloudView()
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CloudView::~CloudView()"));
|
|
|
|
DestroyViewWindow();
|
|
DeleteCriticalSection(&lock);
|
|
|
|
if (mmlib)
|
|
FreeLibrary(mmlib);
|
|
}
|
|
|
|
void
|
|
CloudView::Init(LPCREATESTRUCT cs)
|
|
{
|
|
wndsize.cx = cs->cx;
|
|
wndsize.cy = cs->cy;
|
|
|
|
Image source((HBITMAP)LoadImage(HINST_THISDLL, MAKEINTRESOURCE(IDB_CLOUDS),
|
|
IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION));
|
|
|
|
if (source)
|
|
{
|
|
BITMAP bm;
|
|
|
|
if (GetObject(source.Bitmap(), SIZEOF(BITMAP), &bm))
|
|
{
|
|
HDC screen = GetDC(NULL);
|
|
|
|
tile.cx = bm.bmWidth;
|
|
tile.cy = bm.bmHeight;
|
|
|
|
imgsize.cx = GetSystemMetrics(SM_CXSCREEN) + tile.cx;
|
|
imgsize.cy = OFFSCREEN_STRIPS * tile.cy;
|
|
|
|
BitmapInfo<8, ACTIVE_COLORS> info(imgsize.cx, imgsize.cy);
|
|
// set the lower 16 up with the source colors
|
|
GetDIBColorTable(source, 0, ROW_SIZE, info.colors);
|
|
|
|
// fill in the gradient
|
|
RGBQUAD *pdst = info.colors + ROW_SIZE;
|
|
for (int row = 1; row < NUM_ROWS; row++)
|
|
{
|
|
RGBQUAD *psrc = info.colors;
|
|
|
|
for (int col = 0; col < ROW_SIZE; col++, psrc++, pdst++)
|
|
{
|
|
pdst->rgbBlue = CalcRowData(row, psrc->rgbBlue );
|
|
pdst->rgbGreen = CalcRowData(row, psrc->rgbGreen);
|
|
pdst->rgbRed = CalcRowData(row, psrc->rgbRed );
|
|
}
|
|
}
|
|
*(DWORD *)info.colors = 0;
|
|
|
|
eraser = CreateSolidBrush(DIBINDEX(0x0F));
|
|
if (eraser)
|
|
{
|
|
// create the working background image
|
|
image = CreateDIBSection(screen, info, DIB_RGB_COLORS, NULL,
|
|
NULL, 0);
|
|
|
|
if (image)
|
|
{
|
|
// tile the source bitmap onto the working image
|
|
for (int y = 0; y <= imgsize.cy; y += tile.cy)
|
|
{
|
|
for (int x = 0; x <= info.header.biWidth; x += tile.cx)
|
|
{
|
|
BitBlt(image, x, y, tile.cx, tile.cy, source, 0, 0,
|
|
SRCCOPY);
|
|
}
|
|
}
|
|
|
|
oldbsh = SelectBrush(image, eraser);
|
|
|
|
// create a palette for the image if required
|
|
if (GetDeviceCaps(screen, RASTERCAPS) & RC_PALETTE)
|
|
palette = PaletteFromDS(image);
|
|
|
|
// do this now for 'transparency' during mono->color XORs
|
|
SetBkColor(image, DIBINDEX(0));
|
|
}
|
|
|
|
ReleaseDC(NULL, screen);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
CloudView::Die()
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CloudView::Die()"));
|
|
|
|
if (oldbsh)
|
|
{
|
|
if (image)
|
|
SelectObject(image, oldbsh);
|
|
oldbsh = NULL;
|
|
}
|
|
|
|
if (eraser)
|
|
{
|
|
DeleteObject(eraser);
|
|
eraser = NULL;
|
|
}
|
|
|
|
if (palette)
|
|
{
|
|
DeleteObject(palette);
|
|
palette = NULL;
|
|
}
|
|
|
|
KillAnimation();
|
|
StopMusic();
|
|
|
|
window = NULL;
|
|
image = NULL;
|
|
}
|
|
|
|
BOOL
|
|
CloudView::Erase(HDC dc)
|
|
{
|
|
if (!image)
|
|
return FALSE;
|
|
|
|
DebugMsg(DM_TRACE, TEXT("CloudView::Erase(%08X)"), dc);
|
|
|
|
EnterCriticalSection(&lock);
|
|
|
|
RECT update;
|
|
if(!GetUpdateRect(window, &update, FALSE))
|
|
GetClientRect(window, &update);
|
|
|
|
Realize(dc);
|
|
|
|
int y = update.top / tile.cy * tile.cy;
|
|
do
|
|
{
|
|
BitBlt(dc, 0, y, imgsize.cx, imgsize.cy, image, 0, 0, SRCCOPY);
|
|
y += imgsize.cy;
|
|
|
|
} while (y <= update.bottom);
|
|
|
|
LeaveCriticalSection(&lock);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
CloudView::Paint()
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CloudView::Paint()"));
|
|
|
|
EnterCriticalSection(&lock);
|
|
|
|
PAINTSTRUCT ps;
|
|
BeginPaint(window, &ps);
|
|
|
|
if (elements)
|
|
{
|
|
Realize(ps.hdc);
|
|
|
|
UINT left = count;
|
|
Element *element = elements;
|
|
|
|
while (left--)
|
|
{
|
|
if (element->image)
|
|
DrawElement(ps.hdc, element);
|
|
|
|
element++;
|
|
}
|
|
}
|
|
|
|
EndPaint(window, &ps);
|
|
|
|
LeaveCriticalSection(&lock);
|
|
}
|
|
|
|
void
|
|
CloudView::Realize(HDC theirdc)
|
|
{
|
|
if (palette)
|
|
{
|
|
EnterCriticalSection(&lock);
|
|
|
|
HDC dc = theirdc? theirdc : GetDC(window);
|
|
if (dc)
|
|
{
|
|
SelectPalette(dc, palette, FALSE);
|
|
BOOL repaint = (RealizePalette(dc) > 0);
|
|
|
|
if (!theirdc)
|
|
ReleaseDC(window, dc);
|
|
|
|
if (repaint)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CloudView::Realize() changed entries, invalidating"));
|
|
InvalidateRect(window, NULL, TRUE);
|
|
}
|
|
}
|
|
|
|
LeaveCriticalSection(&lock);
|
|
}
|
|
}
|
|
|
|
static const TCHAR c_szArial[] = TEXT("Arial");
|
|
static const TCHAR c_szTimesNewRoman[] = TEXT("Times New Roman");
|
|
static const TCHAR c_szWingdings[] = TEXT("Wingdings");
|
|
|
|
void
|
|
CloudView::RestartAnimation()
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CloudView::RestartAnimation()"));
|
|
|
|
//
|
|
// put ourselves in a known state before dorking with stuff
|
|
//
|
|
KillAnimation(TRUE);
|
|
gaps.Reset(wndsize.cy);
|
|
|
|
//
|
|
// compute the 'ideal' font height and weight for the window size
|
|
//
|
|
int cxadjusted = (4 * wndsize.cx) / 5; //text is generally wide and short
|
|
LONG tmp = min(cxadjusted, wndsize.cy) / CLOUD_FONT_WNDSIZE_DIVISOR;
|
|
maxheight = min(max(tmp, CLOUD_MIN_FONTSIZE), CLOUD_MAX_FONTSIZE);
|
|
LONG weight = ((FW_BLACK - FW_THIN) * (maxheight - CLOUD_MIN_FONTSIZE) /
|
|
(CLOUD_MAX_FONTSIZE - CLOUD_MIN_FONTSIZE)) + FW_THIN;
|
|
|
|
//
|
|
// create the display fonts for animation
|
|
//
|
|
LOGFONT lf = { maxheight, 0, 0, 0, weight, FALSE, FALSE, FALSE,
|
|
ANSI_CHARSET, OUT_STROKE_PRECIS, CLIP_DEFAULT_PRECIS,
|
|
PROOF_QUALITY, VARIABLE_PITCH | FF_DONTCARE, 0 };
|
|
lstrcpy(lf.lfFaceName, c_szTimesNewRoman);
|
|
|
|
// we MUST have at least the name font
|
|
fonts[CLOUD_NORMAL_FONT] = CreateFontIndirect(&lf);
|
|
if (fonts[CLOUD_NORMAL_FONT])
|
|
{
|
|
// use different font for group titles
|
|
lf.lfHeight = maxheight = (maxheight * 4) / 3;
|
|
lf.lfItalic = TRUE;
|
|
lstrcpy(lf.lfFaceName, c_szArial);
|
|
// we can live without the title font if it fails
|
|
fonts[CLOUD_TITLE_FONT] = CreateFontIndirect(&lf);
|
|
|
|
// create a font for displaying the 'windows logo' glyph
|
|
lf.lfHeight = wndsize.cy - tile.cy;
|
|
tmp = max(maxheight, (tile.cy / 2));
|
|
if (lf.lfHeight < tmp)
|
|
lf.lfHeight = tmp;
|
|
tmp = (tile.cy * (OFFSCREEN_STRIPS-1)) - 2;
|
|
if (lf.lfHeight > tmp)
|
|
lf.lfHeight = tmp;
|
|
lf.lfEscapement = -200;
|
|
lf.lfWeight = FW_NORMAL;
|
|
lf.lfItalic = FALSE;
|
|
lf.lfCharSet = SYMBOL_CHARSET;
|
|
lstrcpy(lf.lfFaceName, c_szWingdings);
|
|
// we can also live without the goodie font if it fails
|
|
fonts[CLOUD_GOODIE_FONT] = CreateFontIndirect(&lf);
|
|
|
|
//
|
|
// compute max elements for simultaneous display
|
|
//
|
|
maxheight *= 5;
|
|
maxheight /= 4;
|
|
tmp = wndsize.cy / maxheight;
|
|
count = (UINT)min(max(tmp, CLOUD_MIN_ELEMENTS), CLOUD_MAX_ELEMENTS);
|
|
|
|
//
|
|
// allocate display elements
|
|
//
|
|
elements = new Element[count];
|
|
if (elements)
|
|
{
|
|
//
|
|
// kick off the animation thread
|
|
//
|
|
DWORD dummy;
|
|
animation = CreateThread( NULL, 0,
|
|
(LPTHREAD_START_ROUTINE)ThreadEntryPoint, (LPVOID)this, 0,
|
|
&dummy );
|
|
}
|
|
}
|
|
|
|
//
|
|
// did we succeed?
|
|
//
|
|
if (animation)
|
|
{
|
|
// go ahead an kick off the music, too
|
|
StartMusic();
|
|
}
|
|
else
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CloudView::RestartAnimation() FAILED"));
|
|
|
|
// clean up after ourselves
|
|
KillAnimation();
|
|
}
|
|
}
|
|
|
|
void
|
|
CloudView::KillAnimation(BOOL repaint_on_kill)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CloudView::KillAnimation(%d)"), repaint_on_kill);
|
|
|
|
if (!elements)
|
|
return;
|
|
|
|
//
|
|
// grab the critical section and free the animation elements
|
|
//
|
|
EnterCriticalSection(&lock);
|
|
delete[] elements;
|
|
elements = NULL;
|
|
count = 0;
|
|
|
|
UINT left = NUM_CLOUD_FONTS;
|
|
while (left--)
|
|
{
|
|
if (fonts[left])
|
|
{
|
|
DeleteObject(fonts[left]);
|
|
fonts[left] = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// release the critical section and wait for the animation thread to die
|
|
//
|
|
LeaveCriticalSection(&lock);
|
|
if (animation)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CloudView::KillAnimation() waiting on thread"));
|
|
WaitForSingleObject(animation, INFINITE);
|
|
CloseHandle(animation);
|
|
animation = NULL;
|
|
|
|
if (repaint_on_kill)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CloudView::KillAnimation() repainting after killing an animation"));
|
|
InvalidateRect(window, NULL, TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL
|
|
CloudView::InitElementForDisplay(Element *element, Gap *gap, const char *text)
|
|
{
|
|
char temp[2];
|
|
//
|
|
// choose a font
|
|
//
|
|
HFONT font = fonts[state % 2];
|
|
if (!font)
|
|
font = fonts[CLOUD_NORMAL_FONT];
|
|
|
|
// if this is the *very* first item use the windows logo instead
|
|
if (!state && fonts[CLOUD_GOODIE_FONT])
|
|
{
|
|
font = fonts[CLOUD_GOODIE_FONT];
|
|
temp[0] = (char)(BYTE)0xFF;
|
|
temp[1] = 0;
|
|
text = temp;
|
|
}
|
|
|
|
//
|
|
// compute the extents
|
|
//
|
|
POINT origin;
|
|
int len = lstrlenA(text);
|
|
|
|
HFONT oldfont = SelectFont(image, font);
|
|
UINT align;
|
|
|
|
if (font == fonts[CLOUD_GOODIE_FONT])
|
|
{
|
|
// special windows flag title
|
|
MAT2 mat = { 0 }; mat.eM11.value = mat.eM22.value = 1;
|
|
GLYPHMETRICS gm;
|
|
if (GetGlyphOutlineA(image, (UINT)(BYTE)0xFF, GGO_METRICS, &gm, 0,
|
|
NULL, &mat) != GDI_ERROR)
|
|
{
|
|
element->size.cx = gm.gmBlackBoxX;
|
|
element->size.cy = gm.gmBlackBoxY;
|
|
origin.x = -gm.gmptGlyphOrigin.x;
|
|
origin.y = gm.gmptGlyphOrigin.y;
|
|
align = TA_LEFT | TA_BASELINE;
|
|
}
|
|
else
|
|
element->size.cx = element->size.cy = 0;
|
|
}
|
|
else
|
|
{
|
|
// normal horizontal text
|
|
GetTextExtentPointA(image, text, len, &element->size);
|
|
ABC abc;
|
|
abc.abcA = 0;
|
|
GetCharABCWidthsA(image, (BYTE)text[0], (BYTE)text[0], &abc);
|
|
int firstA = -min(abc.abcA, 0);
|
|
abc.abcC = 0;
|
|
GetCharABCWidthsA(image, (BYTE)text[len-1], (BYTE)text[len-1], &abc);
|
|
element->size.cx += firstA - min(abc.abcC, 0);
|
|
origin.x = firstA;
|
|
origin.y = 0;
|
|
align = TA_LEFT | TA_TOP;
|
|
}
|
|
|
|
SelectFont(image, oldfont);
|
|
|
|
//
|
|
// create the bitmap
|
|
//
|
|
if (!element->size.cx || !element->size.cy)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CloudView::InitElementForDisplay() could not compute bitmap dimensions"));
|
|
return FALSE;
|
|
}
|
|
|
|
element->image = CreateBitmap(element->size.cx, element->size.cy,
|
|
1, 1, NULL);
|
|
|
|
if (!element->image)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CloudView::InitElementForDisplay() could not create bitmap"));
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// render the text
|
|
//
|
|
RECT rect = { 0, 0, element->size.cx + 1, element->size.cy + 1 };
|
|
SetTextColor(element->image, RGB(0, 0, 0));
|
|
SetBkColor(element->image, RGB(255, 255, 255));
|
|
oldfont = SelectFont(element->image, font);
|
|
UINT oldalign = SetTextAlign(element->image, align);
|
|
ExtTextOutA(element->image, origin.x, origin.y, ETO_OPAQUE, &rect, text,
|
|
(UINT)len, NULL);
|
|
SetTextAlign(element->image, oldalign);
|
|
SelectFont(element->image, oldfont);
|
|
|
|
//
|
|
// position the element and initialize its motion state
|
|
//
|
|
int mag = (1000 * element->size.cx) / wndsize.cx;
|
|
DWORD fadewait = 2000 + mag;
|
|
|
|
if (font == fonts[CLOUD_GOODIE_FONT])
|
|
{
|
|
// special windows flag title
|
|
element->position.x = (wndsize.cx - element->size.cx) / 2;
|
|
element->position.y = (wndsize.cy - element->size.cy) / 2;
|
|
element->velocity = 0;
|
|
fadewait *= 2;
|
|
}
|
|
else
|
|
{
|
|
int cpos = dwRandom((wndsize.cx * 3) / 4) + wndsize.cx / 8;
|
|
element->position.x = cpos - (element->size.cx / 2);
|
|
|
|
if (gap->size == wndsize.cy)
|
|
{
|
|
element->position.y = (gap->size / 4) +
|
|
dwRandom((gap->size - element->size.cy) / 2);
|
|
}
|
|
else
|
|
{
|
|
element->position.y = gap->top +
|
|
dwRandom(gap->size - element->size.cy);
|
|
}
|
|
|
|
int magdiv = 3 + (state & 1);
|
|
element->velocity = 50 + dwRandom(mag / magdiv);
|
|
|
|
// scale/flip velocities as appropriate
|
|
element->velocity *= wndsize.cx;
|
|
element->velocity /= 1024; // factor my (fmh) display out of the velocity
|
|
if (cpos >= (wndsize.cx / 2))
|
|
element->velocity *= -1;
|
|
}
|
|
|
|
// reserve this space on screen
|
|
if (!gaps.Split(gap, element->position.y, element->size.cy))
|
|
return FALSE;
|
|
|
|
// init opacity, fade speed, Blt color and time stamps
|
|
element->vopacity = 500 + dwRandom(mag / 2);
|
|
element->opacity = 0;
|
|
element->coloreffect = DIBINDEX(0);
|
|
element->mark = pfntimeGetTime();
|
|
element->fadebegin = element->mark + fadewait;
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
CloudView::DrawElement(HDC dc, Element *element, int delta)
|
|
{
|
|
//
|
|
// set up coordinates for drawing
|
|
//
|
|
POINT dst = element->position;
|
|
SIZE size = element->size;
|
|
int ex = 0;
|
|
if (dst.x < 0)
|
|
{
|
|
size.cx += dst.x;
|
|
ex = -dst.x;
|
|
dst.x = 0;
|
|
|
|
if (delta > 0)
|
|
delta = 0;
|
|
}
|
|
POINT tmp = { dst.x % tile.cx, dst.y % tile.cy };
|
|
if (delta > 0)
|
|
tmp.x += (1 + (delta / tile.cx)) * tile.cx;
|
|
|
|
//
|
|
// apply translucency effect to composition area
|
|
//
|
|
SetTextColor(image, element->coloreffect);
|
|
BitBlt(image, tmp.x, tmp.y, size.cx, size.cy, element->image, ex, 0,
|
|
SRCINVERT);
|
|
|
|
//
|
|
// draw composed image to destination
|
|
//
|
|
if (delta)
|
|
{
|
|
// include the 'tail' when bltting to the screen
|
|
if (delta > 0)
|
|
{
|
|
dst.x -= delta;
|
|
tmp.x -= delta;
|
|
size.cx += delta;
|
|
}
|
|
else
|
|
size.cx -= delta;
|
|
}
|
|
BitBlt(dc, dst.x, dst.y, size.cx, size.cy, image, tmp.x, tmp.y,
|
|
SRCCOPY);
|
|
|
|
//
|
|
// clean up after ourselves by ANDing erase pattern over image
|
|
//
|
|
PatBlt(image, tmp.x, tmp.y, size.cx, size.cy, (DWORD)0x00A000C9);
|
|
}
|
|
|
|
static const TCHAR c_szBIN[] = TEXT("BIN");
|
|
|
|
// change this to a real mask to (barely) shroud the data
|
|
#define NAME_MAGIC ((char)0x95)
|
|
|
|
// specail pseudo-names mark extra places where we should let the screen clear
|
|
#define NAME_WAIT_FOR_IDLE_MAGIC ((char)('*' ^ NAME_MAGIC))
|
|
|
|
void
|
|
CloudView::AnimationLoop()
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CloudView::AnimationLoop()"));
|
|
|
|
//
|
|
// get the list of names
|
|
//
|
|
HANDLE hnames = LoadResource(HINST_THISDLL, FindResource(HINST_THISDLL, MAKEINTRESOURCE(IDB_CLOUDS), c_szBIN));
|
|
Assert(hnames != NULL);
|
|
if (hnames == NULL)
|
|
return;
|
|
|
|
const char *names = (char *)LockResource(hnames);
|
|
const char *lastgroup = names + g_dwCloudGroup;
|
|
const char *name = lastgroup;
|
|
|
|
//
|
|
// some local state information
|
|
//
|
|
UINT index = 0;
|
|
UINT visible = 0;
|
|
DWORD namebegin = 0;
|
|
|
|
//
|
|
// don't let the gui thread mess with our data structures now
|
|
// (don't worry, we relese the critsec inside the loop below :)
|
|
//
|
|
EnterCriticalSection(&lock);
|
|
state = g_dwCloudGroup? 2 : 0;
|
|
|
|
//
|
|
// are we strarting at the *very* start?
|
|
//
|
|
if (!state)
|
|
namebegin = 2500 + pfntimeGetTime(); // initial delay
|
|
|
|
//
|
|
// main animation loop
|
|
//
|
|
while (elements)
|
|
{
|
|
//
|
|
// get the current element and process it
|
|
//
|
|
Element *element = elements + index;
|
|
|
|
// if the element is not in use, do special stuff
|
|
if (!element->image)
|
|
{
|
|
//
|
|
// advance to next group of names if required
|
|
//
|
|
if ((*name == NAME_MAGIC) && !visible)
|
|
{
|
|
if (*++name != NAME_MAGIC)
|
|
state++;
|
|
else
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CloudView::AnimationLoop() restarting sequence"));
|
|
name = names;
|
|
state = 0;
|
|
|
|
// pause at the end before starting over
|
|
namebegin = 4000 + pfntimeGetTime();
|
|
}
|
|
|
|
// remember the last "title" group we started to show
|
|
if (!(state & 1))
|
|
lastgroup = name;
|
|
}
|
|
|
|
//
|
|
// check for the next name
|
|
//
|
|
if (*name != NAME_MAGIC)
|
|
{
|
|
BOOL waitforidle = (*name == NAME_WAIT_FOR_IDLE_MAGIC);
|
|
DWORD time;
|
|
|
|
//
|
|
// decide if we should process it now or later
|
|
//
|
|
if (((time = pfntimeGetTime()) >= namebegin) &&
|
|
(!waitforidle || !visible))
|
|
{
|
|
//
|
|
// are we restarting after an idle?
|
|
//
|
|
if (waitforidle)
|
|
{
|
|
//
|
|
// idles are pseuso-names in the data stream
|
|
// we can only proceed if there's a real name here
|
|
//
|
|
if (*++name == NAME_MAGIC)
|
|
{
|
|
// nope, we can't deal with this
|
|
// skip the delimiter and restart the loop
|
|
name++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//
|
|
// we have a *real* name, search for a space on the screen
|
|
//
|
|
Gap *gap = gaps.FindLargestThatFits(maxheight);
|
|
if (gap)
|
|
{
|
|
//
|
|
// found a space -- decode the name
|
|
//
|
|
CHAR decoded[128], *p = decoded - 1;
|
|
do { *++p = *name++ ^ NAME_MAGIC; } while (*p);
|
|
|
|
// prepare it for painting
|
|
if (!InitElementForDisplay(element, gap, decoded))
|
|
break;
|
|
|
|
visible++;
|
|
|
|
// set a small wait before starting another item
|
|
namebegin = 333 + dwRandom(417) + pfntimeGetTime();
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// if we're not doing anything, find an element that needs cycles
|
|
//
|
|
if (!element->image && visible)
|
|
{
|
|
do
|
|
{
|
|
if (++index < count)
|
|
element++;
|
|
else
|
|
{
|
|
index = 0;
|
|
element = elements;
|
|
}
|
|
}
|
|
while (!element->image);
|
|
}
|
|
}
|
|
|
|
//
|
|
// if there are visble elements, 'element' should point at one of them
|
|
//
|
|
if (visible)
|
|
{
|
|
//
|
|
// animate the current element
|
|
//
|
|
DWORD time = pfntimeGetTime();
|
|
int elapsed = (int)(time - element->mark);
|
|
int delta = (element->velocity * elapsed) / (int)1000;
|
|
|
|
//
|
|
// has this element moved yet (or is it stationary...)
|
|
//
|
|
if (delta || !element->velocity)
|
|
{
|
|
//
|
|
// recompute the rest of its state
|
|
//
|
|
element->mark = time;
|
|
element->position.x += delta;
|
|
int temp = element->opacity +
|
|
((element->vopacity * elapsed) / (int)1000);
|
|
element->opacity = min(max(temp,0),MAX_OPACITY);
|
|
element->coloreffect =
|
|
DIBINDEX(StdOpacityEffect(element->opacity));
|
|
|
|
//
|
|
// draw it at its new home
|
|
//
|
|
HDC dc = GetDC(window);
|
|
if (dc)
|
|
{
|
|
Realize(dc);
|
|
DrawElement(dc, element, delta);
|
|
ReleaseDC(window, dc);
|
|
}
|
|
|
|
//
|
|
// check to see if the fade state is changing on this dude
|
|
//
|
|
if (time > element->fadebegin)
|
|
{
|
|
if (element->opacity)
|
|
{
|
|
if (element->vopacity > 0)
|
|
{
|
|
// begin fading down
|
|
element->vopacity *= -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// element is done - mark it as such
|
|
element->image = NULL;
|
|
|
|
// free up display space
|
|
gaps.Join(element->position.y, element->size.cy);
|
|
visible--;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// queue the next element
|
|
//
|
|
if (++index >= count)
|
|
index = 0;
|
|
}
|
|
|
|
//
|
|
// prepare to sleep -- minimally giving up the rest of this timeslice
|
|
//
|
|
DWORD sleeptime = 0;
|
|
|
|
//
|
|
// if we have nothing to do then sleep a little longer
|
|
//
|
|
if (!visible)
|
|
{
|
|
DWORD time = pfntimeGetTime();
|
|
if (namebegin > time)
|
|
{
|
|
sleeptime = namebegin - time;
|
|
|
|
//
|
|
// we should still wake up periodically
|
|
// since the gui thread may be trying to kill us
|
|
//
|
|
if (sleeptime > 200)
|
|
sleeptime = 200;
|
|
}
|
|
}
|
|
|
|
//
|
|
// drop the critical section so the gui thread can communicate with us
|
|
//
|
|
LeaveCriticalSection(&lock);
|
|
Sleep(sleeptime);
|
|
EnterCriticalSection(&lock);
|
|
}
|
|
|
|
//
|
|
// check to see if we broke while there was still work to do
|
|
//
|
|
if (count)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CloudView::AnimationLoop() error exit"));
|
|
|
|
// an error must have occurred, clean up
|
|
delete[] elements;
|
|
elements = NULL;
|
|
count = 0;
|
|
InvalidateRect(window, NULL, TRUE);
|
|
}
|
|
|
|
//
|
|
// we're done messing around with the common data structures
|
|
//
|
|
LeaveCriticalSection(&lock);
|
|
|
|
//
|
|
// remember the last group we showed the user so we can restart there
|
|
//
|
|
g_dwCloudGroup = (DWORD)lastgroup - (DWORD)names;
|
|
}
|
|
|
|
DWORD
|
|
CloudView::ThreadEntryPoint(CloudView *view)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CloudView::ThreadEntryPoint() enter view %08X thread %08X"), view, GetCurrentThreadId());
|
|
view->AnimationLoop();
|
|
DebugMsg(DM_TRACE, TEXT("CloudView::ThreadEntryPoint() exit view %08X thread %08X"), view, GetCurrentThreadId());
|
|
return 0;
|
|
}
|
|
|
|
static const TCHAR c_szMID[] = TEXT("BYN");
|
|
static const TCHAR c_szMidiOpen[] = TEXT("open ");
|
|
static const TCHAR c_szMidiSequencer[] = TEXT("sequencer");
|
|
static const TCHAR c_szMidiFile[] = TEXT("Clouds.mid");
|
|
static const TCHAR c_szMidiAlias[] = TEXT(" alias shell");
|
|
static const TCHAR c_szMidiPlay[] = TEXT("play shell from 0 notify");
|
|
static const TCHAR c_szMidiClose[] = TEXT("close shell wait");
|
|
static const TCHAR c_szMediaPath[] = REGSTR_VAL_MEDIA;
|
|
|
|
void
|
|
CloudView::StartMusic()
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CloudView::StartMusic()"));
|
|
|
|
if (!pfnmciSendString || music || InterlockedExchange(&anymusic, 1))
|
|
return;
|
|
|
|
TCHAR opencmd[MAX_PATH + ARRAYSIZE(c_szMidiOpen)], *filename;
|
|
DWORD lencmd, lenbuf, temp;
|
|
HANDLE file;
|
|
HKEY hk;
|
|
|
|
//
|
|
// first try to open the sequencer device to see that its available
|
|
//
|
|
lstrcpy(opencmd, c_szMidiOpen);
|
|
lstrcat(opencmd, c_szMidiSequencer);
|
|
lstrcat(opencmd, c_szMidiAlias);
|
|
if (pfnmciSendString(opencmd, NULL, 0, NULL) != 0)
|
|
goto bail;
|
|
pfnmciSendString(c_szMidiClose, NULL, 0, NULL);
|
|
|
|
//
|
|
// figure out where to put the file
|
|
// sneak in the open command ahead of the filename (for later)
|
|
//
|
|
if (RegOpenKey(HKEY_LOCAL_MACHINE, c_szRegSetup, &hk) != ERROR_SUCCESS)
|
|
goto bail;
|
|
lstrcpy(opencmd, c_szMidiOpen);
|
|
lencmd = lstrlen(opencmd);
|
|
lenbuf = ARRAYSIZE(opencmd) - lencmd;
|
|
filename = opencmd + lencmd;
|
|
temp = RegQueryValueEx(hk, c_szMediaPath, NULL, NULL,
|
|
(LPBYTE)filename, &lenbuf);
|
|
RegCloseKey(hk);
|
|
if ((temp != ERROR_SUCCESS) || !*filename)
|
|
goto bail;
|
|
|
|
//
|
|
// now create it (or verify it already exists)
|
|
//
|
|
PathAppend(filename, c_szMidiFile);
|
|
|
|
file = CreateFile(filename, GENERIC_WRITE, 0, NULL, CREATE_NEW,
|
|
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
|
|
|
|
if (file != INVALID_HANDLE_VALUE)
|
|
{
|
|
HRSRC res = FindResource(HINST_THISDLL,
|
|
MAKEINTRESOURCE(IDB_CLOUDS), c_szMID);
|
|
HANDLE midi = res? LoadResource(HINST_THISDLL, res) : NULL;
|
|
LPCVOID data = midi? LockResource(midi) : NULL;
|
|
|
|
// okay so we'll write out a little extra - who cares
|
|
if (data && !WriteFile(file, data, SizeofResource(HINST_THISDLL, res),
|
|
&temp, NULL))
|
|
{
|
|
data = NULL;
|
|
}
|
|
|
|
CloseHandle(file);
|
|
|
|
if (!data)
|
|
{
|
|
DeleteFile(filename);
|
|
goto bail;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DWORD err = GetLastError();
|
|
if ((err != ERROR_ALREADY_EXISTS) && (err != ERROR_FILE_EXISTS))
|
|
goto bail;
|
|
}
|
|
|
|
//
|
|
// now play it
|
|
//
|
|
lstrcat(opencmd, c_szMidiAlias);
|
|
if (pfnmciSendString(opencmd, NULL, 0, NULL) == 0)
|
|
{
|
|
music = 1;
|
|
if (!NextMusicLoop())
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CloudView::StartMusic() failed"));
|
|
music = -1;
|
|
pfnmciSendString(c_szMidiClose, NULL, 0, NULL);
|
|
}
|
|
}
|
|
|
|
bail:
|
|
if (music < 1)
|
|
InterlockedExchange(&anymusic, 0);
|
|
}
|
|
|
|
BOOL
|
|
CloudView::NextMusicLoop()
|
|
{
|
|
BOOL result = FALSE;
|
|
|
|
//
|
|
// make sure we are actually playing music before we do something rash
|
|
//
|
|
if (music > 0)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CloudView::NextMusicLoop() starting async play"));
|
|
|
|
//
|
|
// starting midi tends to spike and momentarily starve the animation
|
|
// fight back against the tryranny of mci!
|
|
//
|
|
DWORD oldpri;
|
|
if (animation)
|
|
{
|
|
oldpri = GetThreadPriority(animation);
|
|
SetThreadPriority(animation, THREAD_PRIORITY_HIGHEST);
|
|
}
|
|
|
|
//
|
|
// ask mci to start playing the file
|
|
//
|
|
if (pfnmciSendString(c_szMidiPlay, NULL, 0, window) == 0)
|
|
result = TRUE;
|
|
|
|
//
|
|
// after a moment, restore the animation thread's original priority
|
|
//
|
|
if (animation)
|
|
{
|
|
Sleep(500);
|
|
SetThreadPriority(animation, oldpri);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void
|
|
CloudView::StopMusic()
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CloudView::StopMusic()"));
|
|
|
|
if (music > 0)
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CloudView::StopMusic() actually stopping"));
|
|
music = -1;
|
|
pfnmciSendString(c_szMidiClose, NULL, 0, NULL);
|
|
InterlockedExchange(&anymusic, 0);
|
|
}
|
|
}
|
|
|
|
LRESULT
|
|
CloudView::MsgHandler(UINT msg, WPARAM wp, LPARAM lp)
|
|
{
|
|
switch (msg)
|
|
{
|
|
case WM_NCCREATE:
|
|
Init((LPCREATESTRUCT)lp);
|
|
goto DoDefault;
|
|
|
|
case WM_DESTROY:
|
|
case WM_NCDESTROY:
|
|
Die();
|
|
goto DoDefault;
|
|
|
|
case WM_ERASEBKGND:
|
|
if (Erase((HDC)wp))
|
|
return TRUE;
|
|
goto DoDefault;
|
|
|
|
case WM_PAINT:
|
|
Paint();
|
|
break;
|
|
|
|
case WM_SIZE:
|
|
KillAnimation(TRUE);
|
|
if ((wp != SIZE_MINIMIZED) && (wp != SIZE_MAXHIDE))
|
|
{
|
|
wndsize.cx = (int)(short)LOWORD(lp);
|
|
wndsize.cy = (int)(short)HIWORD(lp);
|
|
SetTimer(window, 1, 500, NULL);
|
|
}
|
|
break;
|
|
|
|
case WM_TIMER:
|
|
if (wp == 1)
|
|
{
|
|
KillTimer(window, 1);
|
|
PostMessage(window, CLVM_ANIMATE, 0, 0);
|
|
}
|
|
break;
|
|
|
|
case WM_PALETTECHANGED:
|
|
if ((HWND)wp == window)
|
|
break;
|
|
// fall through
|
|
case WM_QUERYNEWPALETTE:
|
|
Realize();
|
|
break;
|
|
|
|
case CLVM_ANIMATE:
|
|
RestartAnimation();
|
|
break;
|
|
|
|
case MM_MCINOTIFY:
|
|
if ((wp == MCI_NOTIFY_SUCCESSFUL) && !NextMusicLoop())
|
|
{
|
|
DebugMsg(DM_TRACE, TEXT("CloudView::NextMusicLoop() failed"));
|
|
StopMusic();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
DoDefault:
|
|
return DefWindowProc(window, msg, wp, lp);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
LRESULT _export CALLBACK
|
|
CloudView::WndProc(HWND window, UINT msg, WPARAM wp, LPARAM lp)
|
|
{
|
|
CloudView *view;
|
|
|
|
if (msg == WM_NCCREATE)
|
|
{
|
|
view = (CloudView *)((LPCREATESTRUCT)lp)->lpCreateParams;
|
|
SetWindowLong(window, GWL_USERDATA, (LONG)view);
|
|
view->window = window;
|
|
}
|
|
else
|
|
view = (CloudView *)GetWindowLong(window, GWL_USERDATA);
|
|
|
|
return view->MsgHandler(msg, wp, lp);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// CloudView IShellView implementation
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
STDMETHODIMP
|
|
CloudView::QueryInterface(REFIID id, LPVOID *out)
|
|
{
|
|
if ((id == IID_IUnknown) || (id == IID_IShellView))
|
|
{
|
|
*out = (IShellView *)this;
|
|
refs++;
|
|
return NOERROR;
|
|
}
|
|
|
|
*out = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG)
|
|
CloudView::AddRef()
|
|
{
|
|
return ++refs;
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG)
|
|
CloudView::Release()
|
|
{
|
|
if (!--refs)
|
|
{
|
|
delete this;
|
|
return 0UL;
|
|
}
|
|
|
|
return refs;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CloudView::GetWindow(HWND *out)
|
|
{
|
|
*out = window;
|
|
return NOERROR;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CloudView::ContextSensitiveHelp(BOOL)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CloudView::TranslateAccelerator(LPMSG)
|
|
{
|
|
return S_FALSE;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CloudView::EnableModeless(BOOL)
|
|
{
|
|
return NOERROR;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CloudView::UIActivate(UINT)
|
|
{
|
|
return NOERROR;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CloudView::Refresh()
|
|
{
|
|
return NOERROR;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CloudView::CreateViewWindow(IShellView *, LPCFOLDERSETTINGS lpfs,
|
|
IShellBrowser *, RECT *rect, HWND *out)
|
|
{
|
|
WNDCLASS wc;
|
|
|
|
*out = NULL;
|
|
|
|
if (!window)
|
|
{
|
|
if (!GetClassInfo(HINST_THISDLL, c_szCloudViewClassname, &wc))
|
|
{
|
|
// if two pages put one up, share one dc
|
|
wc.style = 0;
|
|
wc.lpfnWndProc = (WNDPROC)CloudView::WndProc;
|
|
wc.cbClsExtra = wc.cbWndExtra = 0;
|
|
wc.hInstance = HINST_THISDLL;
|
|
wc.hIcon = NULL;
|
|
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
|
|
wc.lpszMenuName = NULL;
|
|
wc.lpszClassName = c_szCloudViewClassname;
|
|
|
|
if (!RegisterClass(&wc))
|
|
return E_UNEXPECTED;
|
|
}
|
|
|
|
// reassign here in case the nccreate failed...
|
|
window = CreateWindowEx(WS_EX_CLIENTEDGE, c_szCloudViewClassname,
|
|
szNULL, WS_CHILD | WS_VISIBLE, rect->left, rect->top,
|
|
rect->right-rect->left, rect->bottom-rect->top,
|
|
parent, NULL, HINST_THISDLL, this);
|
|
|
|
if (!window)
|
|
return E_OUTOFMEMORY;
|
|
|
|
// schedule animation -- paranoia
|
|
SetTimer(window, 1, 500, NULL);
|
|
}
|
|
|
|
*out = window;
|
|
return NOERROR;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CloudView::DestroyViewWindow()
|
|
{
|
|
if (window && IsWindow(window))
|
|
DestroyWindow(window);
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CloudView::GetCurrentInfo(LPFOLDERSETTINGS)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CloudView::AddPropertySheetPages(DWORD, LPFNADDPROPSHEETPAGE, LPARAM)
|
|
{
|
|
return NOERROR;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CloudView::SaveViewState()
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CloudView::SelectItem(LPCITEMIDLIST, UINT)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CloudView::GetItemObject(UINT, REFIID, LPVOID *out)
|
|
{
|
|
*out = NULL;
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// CloudFolder_CreateViewObject (see fstreex.c)
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// from fstreex.c
|
|
//extern "C" STDMETHODIMP
|
|
// CFSFolder_CreateViewObject(LPSHELLFOLDER, HWND, REFIID, LPVOID *);
|
|
|
|
extern "C" STDMETHODIMP
|
|
CloudFolder_CreateViewObject(LPSHELLFOLDER folder, HWND parent, REFIID id,
|
|
LPVOID *out)
|
|
{
|
|
#ifdef DEBUG
|
|
//
|
|
// so all you weenies can build it and see it run without hacking...
|
|
//
|
|
if (g_dwCloudGroup == 0xFFFFFFFF)
|
|
g_dwCloudGroup = 0;
|
|
#endif
|
|
|
|
if ((id == IID_IShellView) && (g_dwCloudGroup != 0xFFFFFFFF))
|
|
{
|
|
CloudView *view = new CloudView(parent);
|
|
|
|
if (view)
|
|
{
|
|
*out = (IShellView *)view;
|
|
return S_OK;
|
|
}
|
|
}
|
|
|
|
return CFSFolder_CreateViewObject(folder, parent, id, out);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// CloudHook stuff (how we get to the cloud folder in the first place...)
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
inline int PASCAL
|
|
CloudHookHashText(LPTSTR text)
|
|
{
|
|
int data = 0;
|
|
int count = 0;
|
|
|
|
while (*text)
|
|
{
|
|
int temp = (data << 3) ^ (data >> 23);
|
|
int crap = count++ - *text++;
|
|
|
|
temp ^= crap;
|
|
crap <<= 5;
|
|
temp ^= crap;
|
|
crap <<= 6;
|
|
temp ^= crap;
|
|
crap <<= 6;
|
|
temp ^= crap;
|
|
|
|
data = temp;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
LPSHFILEOPSTRUCT HackedRename = NULL;
|
|
UINT CloudHookState = 0;
|
|
|
|
#ifdef WINNT
|
|
static const int CloudHookData[] = { -1375898046, 1722450348, -1184499671 };
|
|
#else
|
|
static const int CloudHookData[] = { -1375898046, 1722450348, -263390037 };
|
|
#endif
|
|
|
|
#define CLOUD_HOOK_NAMES (SIZEOF(CloudHookData) / SIZEOF(CloudHookData[0]))
|
|
#define CLOUD_HOOK_STATES ((2 * CLOUD_HOOK_NAMES) - 1)
|
|
|
|
extern "C" void PASCAL
|
|
CloudHookBeginRename(LPTSTR newname, HWND listview, int item)
|
|
{
|
|
if (!(CloudHookState & 1) && (CloudHookState < CLOUD_HOOK_STATES))
|
|
{
|
|
TCHAR buffer[2 * MAX_PATH + 1];
|
|
|
|
ListView_GetItemText(listview, item, 0, buffer, ARRAYSIZE(buffer));
|
|
lstrcat(buffer, newname);
|
|
|
|
if (CloudHookHashText(buffer) == CloudHookData[CloudHookState / 2])
|
|
{
|
|
CloudHookState++;
|
|
return;
|
|
}
|
|
}
|
|
|
|
CloudHookState = 0;
|
|
}
|
|
|
|
extern "C" void PASCAL
|
|
CloudHookEndRename(void)
|
|
{
|
|
if (CloudHookState & 1)
|
|
CloudHookState = 0;
|
|
|
|
if (HackedRename)
|
|
{
|
|
LocalFree(HackedRename);
|
|
HackedRename = NULL;
|
|
}
|
|
}
|
|
|
|
extern "C" void PASCAL
|
|
CloudHookFileOperation(LPSHFILEOPSTRUCT *ppop)
|
|
{
|
|
if (((*ppop)->wFunc == FO_RENAME) && (CloudHookState & 1))
|
|
{
|
|
if (CloudHookState == CLOUD_HOOK_STATES)
|
|
{
|
|
g_dwCloudGroup = 0;
|
|
|
|
HackedRename = (SHFILEOPSTRUCT *)
|
|
LocalAlloc(LPTR, SIZEOF(SHFILEOPSTRUCT) + MAX_PATH);
|
|
|
|
if (HackedRename)
|
|
{
|
|
hmemcpy(HackedRename, *ppop, SIZEOF(SHFILEOPSTRUCT));
|
|
|
|
LPTSTR newname =
|
|
(LPTSTR)( (LPBYTE)HackedRename + SIZEOF(SHFILEOPSTRUCT));
|
|
lstrcpy(newname, (*ppop)->pTo);
|
|
|
|
TCHAR szClass[GUIDSTR_MAX + 1];
|
|
*szClass = TEXT('.');
|
|
StringFromGUID2A(CLSID_Clouds, szClass + 1,
|
|
ARRAYSIZE(szClass) - 1);
|
|
lstrcat(newname, szClass);
|
|
|
|
HackedRename->pTo = newname;
|
|
*ppop = HackedRename;
|
|
}
|
|
|
|
CloudHookState = 0;
|
|
}
|
|
else
|
|
CloudHookState++;
|
|
}
|
|
else
|
|
CloudHookState = 0;
|
|
}
|