snes9x/win32/CSaveLoadWithPreviewDlg.cpp

323 lines
11 KiB
C++

/*****************************************************************************\
Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
This file is licensed under the Snes9x License.
For further information, consult the LICENSE file in the root directory.
\*****************************************************************************/
#include "CSaveLoadWithPreviewDlg.h"
#include "wsnes9x.h"
#include "../snes9x.h"
#include "../ppu.h"
#include "../display.h"
#include <vector>
#include <string>
#include <shlwapi.h>
CSaveLoadWithPreviewDlg::CSaveLoadWithPreviewDlg(bool is_save_dialog)
{
for(int i = 0; i < NUM_DIALOG_SLOTS; i++)
{
previewHbmps[i] = NULL;
}
this->is_save_dialog = is_save_dialog;
}
CSaveLoadWithPreviewDlg::~CSaveLoadWithPreviewDlg()
{
delete_preview_bmps();
}
bool CreatePreviewHbitmap(HBITMAP *hbmp)
{
// create a HBITMAP to store the preview images in 32bit RGB
uint8_t* buffer = NULL;
BITMAPINFO *bm = (BITMAPINFO *)calloc(sizeof(BITMAPINFOHEADER), 1);
bm->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bm->bmiHeader.biWidth = PREVIEW_IMAGE_WIDTH;
bm->bmiHeader.biHeight = -PREVIEW_IMAGE_HEIGHT; // negative is top-down
bm->bmiHeader.biPlanes = 1;
bm->bmiHeader.biBitCount = 32;
bm->bmiHeader.biCompression = BI_RGB;
bm->bmiHeader.biSizeImage = 0;
*hbmp = CreateDIBSection(NULL, bm, DIB_RGB_COLORS, (void**)&buffer, 0, 0);
if(*hbmp == NULL) {
return false;
}
return true;
}
void CSaveLoadWithPreviewDlg::delete_preview_bmps()
{
for(int i = 0; i < NUM_DIALOG_SLOTS; i++)
{
if(previewHbmps[i])
{
DeleteObject(previewHbmps[i]);
}
}
}
bool CSaveLoadWithPreviewDlg::init_preview_bmps()
{
for(int i = 0; i < NUM_DIALOG_SLOTS; i++)
{
if(!CreatePreviewHbitmap(&previewHbmps[i]))
return false;
}
return true;
}
void CSaveLoadWithPreviewDlg::load_slot_image_text(int slot, int bank, HWND hDlg)
{
uint16 *image_buffer;
int width, height;
// load the saved screenshot from a snapshot
if(UnfreezeScreenshotSlot(bank * SAVE_SLOTS_PER_BANK + slot, &image_buffer, width, height))
{
// create temporary bitmap storage for screenshot, 16bit RGB
uint8_t* buffer = NULL;
BITMAPINFO *bm = (BITMAPINFO *)calloc(sizeof(BITMAPINFOHEADER) + 3 * sizeof(RGBQUAD), 1);
bm->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bm->bmiHeader.biWidth = width;
bm->bmiHeader.biHeight = -height; // negative is top-down
bm->bmiHeader.biPlanes = 1;
bm->bmiHeader.biBitCount = 16;
bm->bmiHeader.biCompression = BI_BITFIELDS;
bm->bmiHeader.biSizeImage = width * height * 2;
*(unsigned long *)&bm->bmiColors[0] = FIRST_COLOR_MASK_RGB565;
*(unsigned long *)&bm->bmiColors[1] = SECOND_COLOR_MASK_RGB565;
*(unsigned long *)&bm->bmiColors[2] = THIRD_COLOR_MASK_RGB565;
HBITMAP imageBmp = CreateDIBSection(NULL, bm, DIB_RGB_COLORS, (void**)&buffer, 0, 0);
if(imageBmp == NULL)
{
free(bm);
return;
}
int row_bytes = width * 2 / 4 * 4; // DIBs always have 4-byte aligned rows
if(width * 2 % 4)
row_bytes += 4;
// copy saved screenshot into temporary bitmap
uint16 *screen = image_buffer;
for(int h = 0; h < height; h++, screen += width)
{
uint16_t *row_start = (uint16_t*)(buffer + (h * row_bytes));
for(int w = 0; w < width; w++)
{
row_start[w] = screen[w];
}
}
// strech temporary bitmap into HBIMAP for button
HDC cdc = CreateCompatibleDC(GetDC(NULL));
HGDIOBJ old = SelectObject(cdc, previewHbmps[slot]);
int ret = StretchDIBits(cdc, 0, 0, PREVIEW_IMAGE_WIDTH, PREVIEW_IMAGE_HEIGHT, 0, 0, width, height, buffer, bm, DIB_RGB_COLORS, SRCCOPY);
SelectObject(cdc, old);
DeleteDC(cdc);
DeleteObject(imageBmp);
free(bm);
// set image to button
SendMessage(GetDlgItem(hDlg, IDC_BUTTON_SLOT_1 + slot), BM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)previewHbmps[slot]);
char filename[_MAX_PATH + 1];
GetSlotFilename(slot, filename);
Utf8ToWide filenameW(filename);
// text with filename and last write time
std::wstring static_text(PathFindFileName(filenameW));
// get file time details
HANDLE file_handle = CreateFile(filenameW, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if(file_handle != INVALID_HANDLE_VALUE)
{
FILETIME ft;
SYSTEMTIME stUTC, stLocal;
// transform from file time to local time
if(GetFileTime(file_handle, NULL, NULL, &ft) &&
FileTimeToSystemTime(&ft, &stUTC) &&
SystemTimeToTzSpecificLocalTime(NULL, &stUTC, &stLocal))
{
// reserve space for date and time (both received individually)
std::vector<wchar_t> date_string;
date_string.resize(max(
GetDateFormat(LOCALE_USER_DEFAULT, DATE_AUTOLAYOUT | DATE_LONGDATE, &stLocal, NULL, NULL, 0),
GetTimeFormat(LOCALE_USER_DEFAULT, 0, &stLocal, NULL, NULL, 0)
));
GetDateFormat(LOCALE_USER_DEFAULT, DATE_AUTOLAYOUT | DATE_LONGDATE, &stLocal, NULL, &date_string[0], 100);
static_text.append(L"\n").append(&date_string[0]);
GetTimeFormat(LOCALE_USER_DEFAULT, 0, &stLocal, NULL, &date_string[0], 100);
static_text.append(L" ").append(&date_string[0]);
}
}
// set the description text
SetWindowText(GetDlgItem(hDlg, IDC_STATIC_SLOT_1 + slot), static_text.c_str());
free(image_buffer);
}
else
{
// clear image and text
SendMessage(GetDlgItem(hDlg, IDC_BUTTON_SLOT_1 + slot), BM_SETIMAGE, (WPARAM)IMAGE_BITMAP, NULL);
SetWindowText(GetDlgItem(hDlg, IDC_STATIC_SLOT_1 + slot), _T(""));
}
}
void CSaveLoadWithPreviewDlg::load_current_bank(HWND hDlg)
{
int bank = ComboBox_GetCurSel(GetDlgItem(hDlg, IDC_COMBO_BANK));
for (int i = 0; i < NUM_DIALOG_SLOTS; i++)
{
// load one slot
load_slot_image_text(i, bank, hDlg);
}
}
void CSaveLoadWithPreviewDlg::init_window(HWND hDlg)
{
if(is_save_dialog)
SetWindowText(hDlg, L"Save with Preview");
int x_pos = 0;
int y_pos = 0;
for(int i = 0; i < NUM_DIALOG_SLOTS; i++)
{
// second row
if(i == NUM_DIALOG_SLOTS / 2)
{
x_pos = 0;
y_pos = PREVIEW_HEIGHT + PREVIEW_TEXT_STATIC_HEIGHT;
}
// create button and static for one slot
CreateWindow(TEXT("BUTTON"), NULL, WS_CHILDWINDOW | WS_VISIBLE | BS_BITMAP, x_pos, y_pos, PREVIEW_WIDHT, PREVIEW_HEIGHT, hDlg, (HMENU)(UINT_PTR)(IDC_BUTTON_SLOT_1 + i), GUI.hInstance, NULL);
HWND hStatic = CreateWindow(TEXT("STATIC"), TEXT(""), WS_CHILDWINDOW | WS_VISIBLE | SS_CENTER, x_pos, y_pos + PREVIEW_HEIGHT, PREVIEW_WIDHT, PREVIEW_TEXT_STATIC_HEIGHT, hDlg, (HMENU)(UINT_PTR)(IDC_STATIC_SLOT_1 + i), GUI.hInstance, NULL);
x_pos += PREVIEW_WIDHT;
// set dialog font to static
HFONT dlg_font = (HFONT)SendMessage(hDlg, WM_GETFONT, 0, 0);
SendMessage(hStatic, WM_SETFONT, (WPARAM)dlg_font, MAKELPARAM(FALSE, 0));
}
// resize dialog to fit all buttons and text
int dialog_width = NUM_DIALOG_SLOTS / 2 * PREVIEW_WIDHT;
int dialog_height = 2 * (PREVIEW_HEIGHT + PREVIEW_TEXT_STATIC_HEIGHT) + 40; // +40 for cancel button
// reposition cancel button
RECT rect_cancel, rect_bank, client_rect;
GetWindowRect(GetDlgItem(hDlg, IDCANCEL), &rect_cancel);
POINT topleft = { rect_cancel.left, rect_cancel.top };
ScreenToClient(hDlg, &topleft);
GetClientRect(hDlg, &client_rect);
MoveWindow(GetDlgItem(hDlg, IDCANCEL), dialog_width - (client_rect.right - topleft.x), dialog_height - 30, rect_cancel.right - rect_cancel.left, rect_cancel.bottom - rect_cancel.top, TRUE);
// reposition dropdown
HWND combo_hwnd = GetDlgItem(hDlg, IDC_COMBO_BANK);
GetWindowRect(combo_hwnd, &rect_bank);
topleft.x = rect_bank.left;
topleft.y = rect_bank.top;
ScreenToClient(hDlg, &topleft);
MoveWindow(combo_hwnd, 10, dialog_height - 30, rect_bank.right - rect_bank.left, rect_bank.bottom - rect_bank.top, TRUE);
// fill bank strings
TCHAR temp[20];
for (int i = 0; i < NUM_SAVE_BANKS; i++)
{
_stprintf(temp, _T("Bank #%d"), i);
ComboBox_AddString(combo_hwnd, temp);
}
ComboBox_SetCurSel(combo_hwnd, GUI.CurrentSaveBank);
// get monitor dimensions
HMONITOR hm;
MONITORINFO mi;
hm = MonitorFromWindow(GUI.hWnd, MONITOR_DEFAULTTONEAREST);
mi.cbSize = sizeof(mi);
GetMonitorInfo(hm, &mi);
// get title bar and borders
RECT rcMargins = { 0,0,0,0 };
AdjustWindowRectEx(&rcMargins, GetWindowStyle(hDlg), FALSE, GetWindowExStyle(hDlg));
rcMargins.left = abs(rcMargins.left);
rcMargins.top = abs(rcMargins.top);
// add margins to window dimensions and position window in center of screen
dialog_height += rcMargins.top + rcMargins.bottom;
dialog_width += rcMargins.left + rcMargins.right;
int left = (mi.rcWork.right - mi.rcWork.left - dialog_width) / 2;
int top = (mi.rcWork.bottom - mi.rcWork.top - dialog_height) / 2;
SetWindowPos(hDlg, NULL, left, top, dialog_width, dialog_height, SWP_SHOWWINDOW);
load_current_bank(hDlg);
}
INT_PTR CALLBACK CSaveLoadWithPreviewDlg::DlgLoadWithPreview(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
CSaveLoadWithPreviewDlg* dlg = (CSaveLoadWithPreviewDlg*)GetWindowLongPtr(hDlg, GWLP_USERDATA);
switch(msg)
{
case WM_INITDIALOG:
{
SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam);
dlg = (CSaveLoadWithPreviewDlg*)lParam;
dlg->init_window(hDlg);
}
return true;
case WM_COMMAND:
{
// return which button was pressed, or -1 for cancel
int id = LOWORD(wParam);
if(id >= IDC_BUTTON_SLOT_1 && id < IDC_BUTTON_SLOT_1 + NUM_DIALOG_SLOTS)
{
int bank = ComboBox_GetCurSel(GetDlgItem(hDlg, IDC_COMBO_BANK));
int slot = id - IDC_BUTTON_SLOT_1;
EndDialog(hDlg, bank * SAVE_SLOTS_PER_BANK + slot);
return true;
}
else if(id == IDCANCEL)
{
EndDialog(hDlg, -1);
return true;
}
else if (id == IDC_COMBO_BANK)
{
if (CBN_SELCHANGE == HIWORD(wParam))
{
dlg->load_current_bank(hDlg);
}
}
}
default:
return false;
}
}
int CSaveLoadWithPreviewDlg::show()
{
// disable text and confirm for the duration of the dialog
uint32 save_timeout = Settings.InitialInfoStringTimeout;
bool save_confirm = GUI.ConfirmSaveLoad;
Settings.InitialInfoStringTimeout = 0;
GUI.ConfirmSaveLoad = false;
int ret = -1;
delete_preview_bmps();
if(init_preview_bmps())
{
RedrawWindow(GUI.hWnd, 0, 0, RDW_INTERNALPAINT); // workaround to get the dialog to show in OpenGL fullscreen / emulated fullscreen
ret = DialogBoxParam(GUI.hInstance, MAKEINTRESOURCE(IDD_DIALOG_LOAD_PREVIEW), GUI.hWnd, DlgLoadWithPreview, (LPARAM)this);
}
GUI.ConfirmSaveLoad = save_confirm;
Settings.InitialInfoStringTimeout = save_timeout;
return ret;
}