[RAPPS] Screenshot preview and other trivial fixes (#2894)

* add one more layer of window
* using Path* API to operate path
* always use rappmgr.cab as file name when downloading db. ignore the URL
* add snapshot preview window
* show a broken-image icon when failed to load image
* add a padding between image and content, and make sure always some space is reserved for richedit
* hide the padding if snapshot window does not have a width
* some work to avoid blinking when window resizing
* add WM_PRINTCLIENT handling
This commit is contained in:
He Yang 2020-06-14 21:22:58 +08:00 committed by Mark Jansen
parent eacaf65459
commit fbf119fde1
No known key found for this signature in database
GPG key ID: B39240EE84BEAE8B
11 changed files with 574 additions and 69 deletions

View file

@ -17,15 +17,48 @@
#include <atlbase.h>
#include <atlcom.h>
#include <atltypes.h>
#include <atlwin.h>
#include <wininet.h>
#include <shellutils.h>
#include <rosctrls.h>
#include <gdiplus.h>
#include <math.h>
using namespace Gdiplus;
#define SEARCH_TIMER_ID 'SR'
#define LISTVIEW_ICON_SIZE 24
#define TREEVIEW_ICON_SIZE 24
// default broken-image icon size
#define BROKENIMG_ICON_SIZE 96
// the boundary of w/h ratio of snapshot preview window
#define SNPSHT_MAX_ASPECT_RAT 2.5
// padding between snapshot preview and richedit (in pixel)
#define INFO_DISPLAY_PADDING 10
// minimum width of richedit
#define RICHEDIT_MIN_WIDTH 160
enum SNPSHT_STATUS
{
SNPSHTPREV_EMPTY, // show nothing
SNPSHTPREV_LOADING, // image is loading (most likely downloading)
SNPSHTPREV_FILE, // display image from a file
SNPSHTPREV_FAILED // image can not be shown (download failure or wrong image)
};
#define TIMER_LOADING_ANIMATION 1 // Timer ID
#define LOADING_ANIMATION_PERIOD 3 // Animation cycling period (in seconds)
#define LOADING_ANIMATION_FPS 18 // Animation Frame Per Second
#define PI 3.1415927
INT GetSystemColorDepth()
{
DEVMODEW pDevMode;
@ -257,6 +290,322 @@ public:
}
};
class CAppSnapshotPreview :
public CWindowImpl<CAppSnapshotPreview>
{
private:
SNPSHT_STATUS SnpshtPrevStauts = SNPSHTPREV_EMPTY;
Image* pImage = NULL;
HICON hBrokenImgIcon = NULL;
BOOL bLoadingTimerOn = FALSE;
int LoadingAnimationFrame = 0;
int BrokenImgSize = BROKENIMG_ICON_SIZE;
BOOL ProcessWindowMessage(HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam, LRESULT& theResult, DWORD dwMapId)
{
theResult = 0;
switch (Msg)
{
case WM_CREATE:
hBrokenImgIcon = (HICON)LoadImage(hInst, MAKEINTRESOURCE(IDI_BROKEN_IMAGE), IMAGE_ICON, BrokenImgSize, BrokenImgSize, 0);
break;
case WM_SIZE:
{
if (BrokenImgSize != min(min(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)), BROKENIMG_ICON_SIZE))
{
BrokenImgSize = min(min(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)), BROKENIMG_ICON_SIZE);
if (hBrokenImgIcon)
{
DeleteObject(hBrokenImgIcon);
hBrokenImgIcon = (HICON)LoadImage(hInst, MAKEINTRESOURCE(IDI_BROKEN_IMAGE), IMAGE_ICON, BrokenImgSize, BrokenImgSize, 0);
}
}
break;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(&ps);
CRect rect;
GetClientRect(&rect);
PaintOnDC(hdc,
rect.Width(),
rect.Height(),
ps.fErase);
EndPaint(&ps);
break;
}
case WM_PRINTCLIENT:
{
if (lParam & PRF_CHECKVISIBLE)
{
if (!IsWindowVisible()) break;
}
CRect rect;
GetClientRect(&rect);
PaintOnDC((HDC)wParam,
rect.Width(),
rect.Height(),
lParam & PRF_ERASEBKGND);
break;
}
case WM_ERASEBKGND:
{
return TRUE; // do not erase to avoid blinking
}
case WM_TIMER:
{
switch (wParam)
{
case TIMER_LOADING_ANIMATION:
LoadingAnimationFrame++;
LoadingAnimationFrame %= (LOADING_ANIMATION_PERIOD * LOADING_ANIMATION_FPS);
HDC hdc = GetDC();
CRect rect;
GetClientRect(&rect);
PaintOnDC(hdc,
rect.Width(),
rect.Height(),
TRUE);
ReleaseDC(hdc);
}
break;
}
case WM_DESTROY:
{
PreviousDisplayCleanup();
DeleteObject(hBrokenImgIcon);
hBrokenImgIcon = NULL;
break;
}
}
return FALSE;
}
VOID SetStatus(SNPSHT_STATUS Status)
{
SnpshtPrevStauts = Status;
}
VOID PaintOnDC(HDC hdc, int width, int height, BOOL bDrawBkgnd)
{
// use an off screen dc to avoid blinking
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hBitmap = CreateCompatibleBitmap(hdc, width, height);
SelectObject(hdcMem, hBitmap);
if (bDrawBkgnd)
{
HBRUSH hOldBrush = (HBRUSH)SelectObject(hdcMem, (HGDIOBJ)GetSysColorBrush(COLOR_BTNFACE));
PatBlt(hdcMem, 0, 0, width, height, PATCOPY);
SelectObject(hdcMem, hOldBrush);
}
switch (SnpshtPrevStauts)
{
case SNPSHTPREV_EMPTY:
{
}
break;
case SNPSHTPREV_LOADING:
{
Graphics graphics(hdcMem);
Color color(255, 0, 0);
SolidBrush dotBrush(Color(255, 100, 100, 100));
graphics.SetSmoothingMode(SmoothingMode::SmoothingModeAntiAlias);
// Paint three dot
float DotWidth = GetLoadingDotWidth(width, height);
graphics.FillEllipse((Brush*)(&dotBrush),
(REAL)width / 2.0 - min(width, height) * 2.0 / 16.0 - DotWidth / 2.0,
(REAL)height / 2.0 - GetFrameDotShift(LoadingAnimationFrame + LOADING_ANIMATION_FPS / 4, width, height) - DotWidth / 2.0,
DotWidth,
DotWidth);
graphics.FillEllipse((Brush*)(&dotBrush),
(REAL)width / 2.0 - DotWidth / 2.0,
(REAL)height / 2.0 - GetFrameDotShift(LoadingAnimationFrame, width, height) - DotWidth / 2.0,
DotWidth,
DotWidth);
graphics.FillEllipse((Brush*)(&dotBrush),
(REAL)width / 2.0 + min(width, height) * 2.0 / 16.0 - DotWidth / 2.0,
(REAL)height / 2.0 - GetFrameDotShift(LoadingAnimationFrame - LOADING_ANIMATION_FPS / 4, width, height) - DotWidth / 2.0,
DotWidth,
DotWidth);
}
break;
case SNPSHTPREV_FILE:
{
if (pImage)
{
// always draw entire image inside the window.
Graphics graphics(hdcMem);
float ZoomRatio = min(((float)width / (float)pImage->GetWidth()), ((float)height / (float)pImage->GetHeight()));
float ZoomedImgWidth = ZoomRatio * (float)pImage->GetWidth();
float ZoomedImgHeight = ZoomRatio * (float)pImage->GetHeight();
graphics.DrawImage(pImage,
((float)width - ZoomedImgWidth) / 2.0, ((float)height - ZoomedImgHeight) / 2.0,
ZoomedImgWidth, ZoomedImgHeight);
}
}
break;
case SNPSHTPREV_FAILED:
{
DrawIconEx(hdcMem,
(width - BrokenImgSize) / 2,
(height - BrokenImgSize) / 2,
hBrokenImgIcon,
BrokenImgSize,
BrokenImgSize,
NULL,
NULL,
DI_NORMAL | DI_COMPAT);
}
break;
}
// copy the content form off-screen dc to hdc
BitBlt(hdc, 0, 0, width, height, hdcMem, 0, 0, SRCCOPY);
DeleteDC(hdcMem);
DeleteObject(hBitmap);
}
float GetLoadingDotWidth(int width, int height)
{
return min(width, height) / 20.0;
}
float GetFrameDotShift(int Frame, int width, int height)
{
return min(width, height) *
(1.0 / 16.0) *
(2.0 / (2.0 - sqrt(3.0))) *
(max(sin((float)Frame * 2 * PI / (LOADING_ANIMATION_PERIOD * LOADING_ANIMATION_FPS)), sqrt(3.0) / 2.0) - sqrt(3.0) / 2.0);
}
public:
static ATL::CWndClassInfo& GetWndClassInfo()
{
DWORD csStyle = CS_VREDRAW | CS_HREDRAW;
static ATL::CWndClassInfo wc =
{
{
sizeof(WNDCLASSEX),
csStyle,
StartWindowProc,
0,
0,
NULL,
0,
LoadCursorW(NULL, IDC_ARROW),
(HBRUSH)(COLOR_BTNFACE + 1),
0,
L"RAppsSnapshotPreview",
NULL
},
NULL, NULL, IDC_ARROW, TRUE, 0, _T("")
};
return wc;
}
HWND Create(HWND hParent)
{
RECT r = { 0,0,0,0 };
return CWindowImpl::Create(hParent, r, L"", WS_CHILD | WS_VISIBLE);
}
VOID PreviousDisplayCleanup()
{
if (bLoadingTimerOn)
{
KillTimer(TIMER_LOADING_ANIMATION);
bLoadingTimerOn = FALSE;
}
LoadingAnimationFrame = 0;
if (pImage)
{
delete pImage;
pImage = NULL;
}
}
VOID DisplayEmpty()
{
SetStatus(SNPSHTPREV_EMPTY);
PreviousDisplayCleanup();
}
VOID DisplayLoading()
{
SetStatus(SNPSHTPREV_LOADING);
PreviousDisplayCleanup();
bLoadingTimerOn = TRUE;
SetTimer(TIMER_LOADING_ANIMATION, 1000 / LOADING_ANIMATION_FPS, 0);
}
BOOL DisplayFile(LPCWSTR lpszFileName)
{
SetStatus(SNPSHTPREV_FILE);
PreviousDisplayCleanup();
pImage = Bitmap::FromFile(lpszFileName, 0);
if (pImage->GetLastStatus() != Ok)
{
DisplayFailed();
return FALSE;
}
return TRUE;
}
VOID DisplayFailed()
{
SetStatus(SNPSHTPREV_FAILED);
PreviousDisplayCleanup();
}
int GetRequestedWidth(int Height) // calculate requested window width by given height
{
switch (SnpshtPrevStauts)
{
case SNPSHTPREV_EMPTY:
return 0;
case SNPSHTPREV_LOADING:
return 200;
case SNPSHTPREV_FILE:
if (pImage)
{
// return the width needed to display image inside the window.
// and always keep window w/h ratio inside [ 1/SNPSHT_MAX_ASPECT_RAT, SNPSHT_MAX_ASPECT_RAT ]
return (int)floor((float)Height *
max(min((float)pImage->GetWidth() / (float)pImage->GetHeight(), (float)SNPSHT_MAX_ASPECT_RAT), 1.0/ (float)SNPSHT_MAX_ASPECT_RAT));
}
return 0;
case SNPSHTPREV_FAILED:
return 200;
default:
return 0;
}
}
~CAppSnapshotPreview()
{
PreviousDisplayCleanup();
}
};
class CAppInfoDisplay :
public CUiWindow<CWindowImpl<CAppInfoDisplay>>
{
@ -272,16 +621,18 @@ private:
{
RichEdit = new CAppRichEdit();
RichEdit->Create(hwnd);
SnpshtPrev = new CAppSnapshotPreview();
SnpshtPrev->Create(hwnd);
break;
}
case WM_SIZE:
{
::MoveWindow(RichEdit->m_hWnd, 0, 0, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), TRUE);
ResizeChildren(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
break;
}
case WM_COMMAND:
{
OnCommand(wParam, lParam);
break;
}
@ -304,9 +655,94 @@ private:
return FALSE;
}
VOID ResizeChildren()
{
CRect rect;
GetWindowRect(&rect);
ResizeChildren(rect.Width(), rect.Height());
}
VOID ResizeChildren(int Width, int Height)
{
int SnpshtWidth = SnpshtPrev->GetRequestedWidth(Height);
// make sure richedit always have room to display
SnpshtWidth = min(SnpshtWidth, Width - INFO_DISPLAY_PADDING - RICHEDIT_MIN_WIDTH);
DWORD dwError = ERROR_SUCCESS;
HDWP hDwp = BeginDeferWindowPos(2);
if (hDwp)
{
hDwp = ::DeferWindowPos(hDwp, SnpshtPrev->m_hWnd, NULL,
0, 0, SnpshtWidth, Height, 0);
if (hDwp)
{
// hide the padding if snapshot window width == 0
int RicheditPosX = SnpshtWidth ? (SnpshtWidth + INFO_DISPLAY_PADDING) : 0;
hDwp = ::DeferWindowPos(hDwp, RichEdit->m_hWnd, NULL,
RicheditPosX, 0, Width - RicheditPosX, Height, 0);
if (hDwp)
{
EndDeferWindowPos(hDwp);
}
else
{
dwError = GetLastError();
}
}
else
{
dwError = GetLastError();
}
}
else
{
dwError = GetLastError();
}
#if DBG
ATLASSERT(dwError == ERROR_SUCCESS);
#endif
UpdateWindow();
}
VOID OnLink(ENLINK* Link)
{
switch (Link->msg)
{
case WM_LBUTTONUP:
case WM_RBUTTONUP:
{
if (pLink) HeapFree(GetProcessHeap(), 0, pLink);
pLink = (LPWSTR)HeapAlloc(GetProcessHeap(), 0,
(max(Link->chrg.cpMin, Link->chrg.cpMax) -
min(Link->chrg.cpMin, Link->chrg.cpMax) + 1) * sizeof(WCHAR));
if (!pLink)
{
/* TODO: Error message */
return;
}
RichEdit->SendMessageW(EM_SETSEL, Link->chrg.cpMin, Link->chrg.cpMax);
RichEdit->SendMessageW(EM_GETSELTEXT, 0, (LPARAM)pLink);
ShowPopupMenuEx(m_hWnd, m_hWnd, IDR_LINKMENU, -1);
}
break;
}
}
public:
CAppRichEdit * RichEdit;
CAppSnapshotPreview * SnpshtPrev;
static ATL::CWndClassInfo& GetWndClassInfo()
{
@ -336,51 +772,38 @@ public:
{
RECT r = { 0,0,0,0 };
return CWindowImpl::Create(hwndParent, r, L"", WS_CHILD | WS_VISIBLE);
return CWindowImpl::Create(hwndParent, r, L"", WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
}
BOOL ShowAvailableAppInfo(CAvailableApplicationInfo* Info)
{
ATL::CStringW SnapshotFilename;
if (Info->RetrieveSnapshot(0, SnapshotFilename))
{
SnpshtPrev->DisplayFile(SnapshotFilename);
}
else
{
SnpshtPrev->DisplayEmpty();
}
ResizeChildren();
return RichEdit->ShowAvailableAppInfo(Info);
}
BOOL ShowInstalledAppInfo(PINSTALLED_INFO Info)
{
SnpshtPrev->DisplayEmpty();
ResizeChildren();
return RichEdit->ShowInstalledAppInfo(Info);
}
VOID SetWelcomeText()
{
SnpshtPrev->DisplayEmpty();
ResizeChildren();
RichEdit->SetWelcomeText();
}
VOID OnLink(ENLINK* Link)
{
switch (Link->msg)
{
case WM_LBUTTONUP:
case WM_RBUTTONUP:
{
if (pLink) HeapFree(GetProcessHeap(), 0, pLink);
pLink = (LPWSTR)HeapAlloc(GetProcessHeap(), 0,
(max(Link->chrg.cpMin, Link->chrg.cpMax) -
min(Link->chrg.cpMin, Link->chrg.cpMax) + 1) * sizeof(WCHAR));
if (!pLink)
{
/* TODO: Error message */
return;
}
RichEdit->SendMessageW(EM_SETSEL, Link->chrg.cpMin, Link->chrg.cpMax);
RichEdit->SendMessageW(EM_GETSELTEXT, 0, (LPARAM)pLink);
ShowPopupMenuEx(m_hWnd, m_hWnd, IDR_LINKMENU, -1);
}
break;
}
}
VOID OnCommand(WPARAM wParam, LPARAM lParam)
{
WORD wCommand = LOWORD(wParam);
@ -1804,8 +2227,12 @@ private:
}
/* Load icon from file */
ATL::CStringW szIconPath;
szIconPath.Format(L"%lsicons\\%ls.ico", szFolderPath, Info->m_szName.GetString());
ATL::CStringW szIconPath = szFolderPath;
PathAppendW(szIconPath.GetBuffer(MAX_PATH), L"icons");
PathAppendW(szIconPath.GetBuffer(), Info->m_szName.GetString());
PathAddExtensionW(szIconPath.GetBuffer(), L".ico");
szIconPath.ReleaseBuffer();
hIcon = (HICON) LoadImageW(NULL,
szIconPath.GetString(),
IMAGE_ICON,