
// BKMEMDlg.cpp : файл реализации
//

#include "pch.h"
#include "Config.h"
#include "BKMEMDlg.h"
#include "BKMessageBox.h"
#include "Screen.h"
#include "SprWnd.h"

#include "Platform.h"
#include "BK001001.h"
#include "BKMSTD10.h"
#include "FDDStd326.h"
#include "FDD_A16M.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

IMPLEMENT_DYNAMIC(CBKMEMDlg, CBaseDialog)

// диалоговое окно CBKMEMDlg
CBKMEMDlg::CBKMEMDlg(CWnd *pParent /*=nullptr*/)
	: CBaseDialog(CBKMEMDlg::IDD, pParent)
	, m_nTabsCount(0)
	, m_nSelectedTab(0)
{
	ZeroMemory(m_Container, sizeof(m_Container));
	m_Screen[MM_FIRST_PAGE] = nullptr;
	m_Screen[MM_SECOND_PAGE] = nullptr;
}

CBKMEMDlg::~CBKMEMDlg()
{
}

void CBKMEMDlg::DoDataExchange(CDataExchange *pDX)
{
	CBaseDialog::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_TAB_MM_MEMTABS, m_tab);
}

BEGIN_MESSAGE_MAP(CBKMEMDlg, CBaseDialog)
	ON_WM_QUERYDRAGICON()
	ON_NOTIFY(TCN_SELCHANGE, IDC_TAB_MM_MEMTABS, &CBKMEMDlg::OnTcnSelchangeTab1)
	ON_BN_CLICKED(ID_BUTTON_MM_COLOR_MODE_P1, &CBKMEMDlg::OnBnClickedButtonMmColorModeP1)
	ON_BN_CLICKED(ID_BUTTON_MM_BW_MODE_P1, &CBKMEMDlg::OnBnClickedButtonMmBwModeP1)
	ON_BN_CLICKED(ID_BUTTON_MM_LOAD_P1, &CBKMEMDlg::OnBnClickedButtonMmLoadP1)
	ON_BN_CLICKED(ID_BUTTON_MM_SAVE_P1, &CBKMEMDlg::OnBnClickedButtonMmSaveP1)
	ON_BN_CLICKED(ID_BUTTON_MM_SPRITE_P1, &CBKMEMDlg::OnBnClickedButtonMmSpriteP1)
	ON_BN_CLICKED(ID_BUTTON_MM_COLOR_MODE_P2, &CBKMEMDlg::OnBnClickedButtonMmColorModeP2)
	ON_BN_CLICKED(ID_BUTTON_MM_BW_MODE_P2, &CBKMEMDlg::OnBnClickedButtonMmBwModeP2)
	ON_BN_CLICKED(ID_BUTTON_MM_LOAD_P2, &CBKMEMDlg::OnBnClickedButtonMmLoadP2)
	ON_BN_CLICKED(ID_BUTTON_MM_SAVE_P2, &CBKMEMDlg::OnBnClickedButtonMmSaveP2)
	ON_BN_CLICKED(ID_BUTTON_MM_SPRITE_P2, &CBKMEMDlg::OnBnClickedButtonMmSpriteP2)
	ON_WM_DESTROY()
END_MESSAGE_MAP()

struct btnList
{
	UINT nBtnID;
	UINT nToolTipID;
	int nIcon;
};

static btnList mmButtons[10] =
{
	{ ID_BUTTON_MM_COLOR_MODE_P1,   IDS_BUTTON_COLORMODE_STR, 0 },
	{ ID_BUTTON_MM_COLOR_MODE_P2,   IDS_BUTTON_COLORMODE_STR, 0 },
	{ ID_BUTTON_MM_BW_MODE_P1,      IDS_BUTTON_ADAPTIVEBWMODE_STR, 1 },
	{ ID_BUTTON_MM_BW_MODE_P2,      IDS_BUTTON_ADAPTIVEBWMODE_STR, 1 },
	{ ID_BUTTON_MM_LOAD_P1,         IDS_BUTTON_LOAD_STR, 2 },
	{ ID_BUTTON_MM_LOAD_P2,         IDS_BUTTON_LOAD_STR, 2 },
	{ ID_BUTTON_MM_SAVE_P1,         IDS_BUTTON_SAVE_STR, 3 },
	{ ID_BUTTON_MM_SAVE_P2,         IDS_BUTTON_SAVE_STR, 3 },
	{ ID_BUTTON_MM_SPRITE_P1,       IDS_BUTTON_SPRITE_STR, 4 },
	{ ID_BUTTON_MM_SPRITE_P2,       IDS_BUTTON_SPRITE_STR, 4 }
};

BOOL CBKMEMDlg::OnInitDialog()
{
	CBaseDialog::OnInitDialog();
	CreateScreen(MM_FIRST_PAGE, IDC_STATIC_MM_P1);
	CreateScreen(MM_SECOND_PAGE, IDC_STATIC_MM_P2);
	// Подсказки
	m_ToolTip.Create(this, WS_POPUP | TTS_NOPREFIX | TTS_USEVISUALSTYLE | TTS_ALWAYSTIP | TTS_NOANIMATE);
	m_ToolTip.SetDelayTime(TTDT_AUTOPOP, 20000);
	m_ToolTip.SetDelayTime(TTDT_INITIAL, 300);
	// *** you could change the default settings ***
	CMFCToolTipInfo params;
	params.m_bBoldLabel = TRUE;
	params.m_bDrawDescription = TRUE;
	params.m_bDrawIcon = TRUE;
	params.m_bRoundedCorners = TRUE;
	params.m_bBalloonTooltip = TRUE;
	params.m_bDrawSeparator = FALSE;
	params.m_bVislManagerTheme = TRUE;
	params.m_clrFill = RGB(255, 255, 255);
	params.m_clrFillGradient = RGB(228, 228, 240);
	params.m_clrText = RGB(61, 83, 80);
	params.m_clrBorder = RGB(144, 149, 168);
	m_ToolTip.SetParams(&params);
	m_ToolTip.SetMaxTipWidth(400);
	m_ToolTip.SetFixedWidth(400, 450);
	m_ToolTip.Activate(TRUE);
	CBitmap m_bmparr;
	m_bmparr.LoadBitmap(IDB_MM_BUTTONS);
	CImageList m_imagelist;
	m_imagelist.Create(16, 15, ILC_COLORDDB | ILC_MASK, 0, 0);
	m_imagelist.Add(&m_bmparr, RGB(192, 192, 192));

	for (auto &n : mmButtons)
	{
		CWnd *p = GetDlgItem(n.nBtnID);
		m_ToolTip.AddTool(p, n.nToolTipID);
		reinterpret_cast<CButton *>(p)->SetIcon(m_imagelist.ExtractIcon(n.nIcon));
	}

	m_imagelist.DeleteImageList();
	m_bmparr.DeleteObject();
	// создаём вкладки
	CBK001001 *pBoard = g_Platform.GetBoardPtr();

	if (pBoard)
	{
		switch (pBoard->GetBoardModel())
		{
			case BK_DEV_MPI::BK001001:
				CreateTabs_10(pBoard->GetMainMemory());
				break;

			case BK_DEV_MPI::BK0011:
			case BK_DEV_MPI::BK0011M:
				CreateTabs_11M(pBoard->GetMainMemory());
				break;
		}
	}

	CBKMSTD10 *pMSTD = g_Platform.GetMSTDPtr();

	if (pMSTD)
	{
		switch (pMSTD->GetBoardModel())
		{
			case BK_DEV_MPI::MSTD10_EXT32:
				AddTabs_EXT32(pMSTD->GetMainMemory());
				break;
		}
	}

	CFDDStd326 *pFDD = g_Platform.GetFDDPtr();

	if (pFDD)
	{
		switch (pFDD->GetBoardModel())
		{
			case BK_DEV_MPI::STD_MEM:
				AddTabs_EXT16(pFDD->GetMainMemory());
				break;

			case BK_DEV_MPI::A16M:
				AddTabsA16M(pFDD->GetMainMemory());
				break;

			case BK_DEV_MPI::SMK512:
				AddTabsSMK512(pFDD->GetMainMemory());
				break;
		}
	}

	SelectTab(); // выберем какую-нибудь вкладку (по умолчанию - нулевую)
	return TRUE; // возврат значения TRUE, если фокус не передан элементу управления
}

void CBKMEMDlg::SelectTab()
{
	if (m_Container[m_nSelectedTab][MM_FIRST_PAGE].bExist)
	{
		SetTabParam(MM_FIRST_PAGE);
	}

	if (m_Container[m_nSelectedTab][MM_SECOND_PAGE].bExist)
	{
		m_Screen[MM_SECOND_PAGE]->ShowWindow(SW_SHOW);
		SetTabParam(MM_SECOND_PAGE);
	}
	else
	{
		m_Screen[MM_SECOND_PAGE]->ShowWindow(SW_HIDE);
	}
}

void CBKMEMDlg::SetTabParam(int nPage)
{
	MM16kPage_t &pp = m_Container[m_nSelectedTab][nPage];
	// переключим отображаемый буфер
	m_Screen[nPage]->ChangeBuffer(pp.pBuffer, pp.nBufSize);
	// зададим режимы отображения
	SetColormode(nPage, pp.bColorMode);
	SetBWMode(nPage, pp.bBWAdaptMode);
}

void CBKMEMDlg::OnTcnSelchangeTab1(NMHDR *pNMHDR, LRESULT *pResult)
{
	m_nSelectedTab = m_tab.GetCurSel();

	if (m_nSelectedTab < 0)
	{
		m_nSelectedTab = 0;
	}

	SelectTab();
	*pResult = S_OK;
}

static UINT mmBtn2[2][5] =
{
	{
		ID_BUTTON_MM_COLOR_MODE_P1,
		ID_BUTTON_MM_BW_MODE_P1,
		ID_BUTTON_MM_SPRITE_P1,
		ID_BUTTON_MM_SAVE_P1,
		ID_BUTTON_MM_LOAD_P1,
	},
	{
		ID_BUTTON_MM_COLOR_MODE_P2,
		ID_BUTTON_MM_BW_MODE_P2,
		ID_BUTTON_MM_SPRITE_P2,
		ID_BUTTON_MM_SAVE_P2,
		ID_BUTTON_MM_LOAD_P2,
	}
};


void CBKMEMDlg::CreateScreen(int nPage, int idUI)
{
	CString str;
	str.Format(_T("BKScreen_P%d"), nPage + 1);
	// если рендер D3D - то карту памяти выводим, используя DrawDIB
	m_Screen[nPage] = new CScreen((g_Config.m_nScreenRenderType == CONF_SCREEN_RENDER::D3D ? CONF_SCREEN_RENDER::VFW : g_Config.m_nScreenRenderType));

	if (m_Screen[nPage])
	{
		CRect rect;
		GetDlgItem(idUI)->GetWindowRect(&rect);
		ScreenToClient(&rect);
		GetDlgItem(idUI)->DestroyWindow();
		int nPixelW = ::GetDeviceCaps(GetDC()->GetSafeHdc(), LOGPIXELSX);
		int nPixelH = ::GetDeviceCaps(GetDC()->GetSafeHdc(), LOGPIXELSY);
		rect.right = rect.left + ::MulDiv(BK_SCREEN_WIDTH, nPixelW, DEFAULT_DPIX);
		rect.bottom = rect.top + ::MulDiv(BK_SCREEN_HEIGHT, nPixelH, DEFAULT_DPIY);

		// размеры - константа 512х256
		if (!m_Screen[nPage]->Create(nullptr, str,
		                             WS_VISIBLE | WS_CHILD,
		                             rect,
		                             &m_tab, 0)
		   )
		{
			TRACE(_T("Не удалось создать экран %s\n"), str);
			SAFE_DELETE(m_Screen[nPage]); // не удалось создать
		}

		// подвинем кнопки
		CPoint pt = { rect.right + ::MulDiv(14, nPixelW, DEFAULT_DPIX), rect.top }; // это верхний левый угол, с которого начинается столбец кнопок
		int nMargin = ::MulDiv(4, nPixelH, DEFAULT_DPIY);
		CRect sr, dr;
		int i = 0;

		for (; i < 2; ++i)
		{
			CWnd *p = GetDlgItem(mmBtn2[nPage][i]);
			p->GetWindowRect(sr);
			dr = { pt.x, pt.y, pt.x + sr.Width(), pt.y + sr.Height() };
			p->MoveWindow(&dr, FALSE);
			pt.y += sr.Height() + nMargin;
		}

		pt.y = rect.bottom; // это нижний левый угол, с которого начинается столбец кнопок

		for (; i < 5; ++i)
		{
			CWnd *p = GetDlgItem(mmBtn2[nPage][i]);
			p->GetWindowRect(sr);
			dr = { pt.x, pt.y - sr.Height(), pt.x + sr.Width(), pt.y };
			p->MoveWindow(&dr, FALSE);
			pt.y -= sr.Height() + nMargin;
		}

		this->GetWindowRect(rect);
		ScreenToClient(&rect);
		int x = rect.left;
		rect.left -= x; rect.right -= x;
		int y = rect.top;
		rect.top -= y; rect.bottom -= y;
		rect.right = -x + dr.right + ::MulDiv(14, nPixelW, DEFAULT_DPIX);
		ClientToScreen(&rect);
		MoveWindow(&rect, FALSE);
	}
	else
	{
		// не удалось создать
		TRACE(_T("Недостаточно памяти для создания экрана %s\n"), str);
		g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
	}
}

void CBKMEMDlg::CreateTabs_10(uint8_t *pMemory)
{
	m_nTabsCount = 1;
	CString strtab;

	for (int i = 0; i < m_nTabsCount; ++i)
	{
		size_t nOffs = size_t(i) * 0100000;
		m_Container[i][MM_FIRST_PAGE]  = { true, true, false, pMemory + nOffs,          040000 };
		m_Container[i][MM_SECOND_PAGE] = { true, true, false, pMemory + nOffs + 040000, 040000 };
		strtab.Format(_T("BK10_pg%d%d"), i * 2, i * 2 + 1);
		m_tab.InsertItem(i, strtab);
	}
}

void CBKMEMDlg::CreateTabs_11M(uint8_t *pMemory)
{
	m_nTabsCount = 4;
	CString strtab;

	for (int i = 0; i < m_nTabsCount; ++i)
	{
		int n0 = i * 2;
		int n1 = i * 2 + 1;
		m_Container[i][MM_FIRST_PAGE]  = { true, true, false, pMemory + (size_t(n0) << 14), 040000 };
		m_Container[i][MM_SECOND_PAGE] = { true, true, false, pMemory + (size_t(n1) << 14), 040000 };
		strtab.Format(_T("BK11M_pg%d%d"), n0, n1);
		m_tab.InsertItem(i, strtab);
	}
}

void CBKMEMDlg::AddTabs_EXT32(uint8_t *pMemory)
{
	int i = m_nTabsCount++;
	CString strtab;
	// дополнительные 32 КБ памяти у модифицированного блока МСТД
	m_Container[i][MM_FIRST_PAGE]  = { true, true, false, pMemory,          040000 };
	m_Container[i][MM_SECOND_PAGE] = { true, true, false, pMemory + 040000, 040000 };
	strtab.Format(_T("EXT_pg%d%d"), i * 2, i * 2 + 1);
	m_tab.InsertItem(i, strtab);
}

void CBKMEMDlg::AddTabs_EXT16(uint8_t *pMemory)
{
	unsigned int i = m_nTabsCount++;
	CString strtab;
	// дополнительные 16 КБ памяти у стандартного контроллера НГМД
	m_Container[i][MM_FIRST_PAGE] = { true, true, false, pMemory + 0120000, 040000 };
	m_Container[i][MM_SECOND_PAGE] = { false, };
	m_tab.InsertItem(i, _T("EXT_pg0"));
}

void CBKMEMDlg::AddTabsA16M(uint8_t *pMemory)
{
	unsigned int i = m_nTabsCount++;
	CString strtab;
	// дополнительные 16 КБ памяти
	m_Container[i][MM_FIRST_PAGE] = { true, true, false, pMemory, 040000 };
	m_Container[i][MM_SECOND_PAGE] = { false, };
	m_tab.InsertItem(i, _T("A16M_pg0"));
}

void CBKMEMDlg::AddTabsSMK512(uint8_t *pMemory)
{
	static const CString arPgNums[16] =
	{
		_T("0000"), _T("2000"), _T("0004"), _T("2004"),
		_T("0010"), _T("2010"), _T("0014"), _T("2014"),
		_T("0001"), _T("2001"), _T("0005"), _T("2005"),
		_T("0011"), _T("2011"), _T("0015"), _T("2015")
	};
	int n = m_nTabsCount;
	m_nTabsCount += 16;
	CString strtab;

	for (int i = n, j = 0; i < m_nTabsCount; ++i, ++j)
	{
		size_t nOffs = size_t(j) * 0100000;
		m_Container[i][MM_FIRST_PAGE]  = { true, true, false, pMemory + nOffs,          040000 };
		m_Container[i][MM_SECOND_PAGE] = { true, true, false, pMemory + nOffs + 040000, 040000 };
		strtab = _T("SMK_") + arPgNums[j];
		m_tab.InsertItem(i, strtab);
	}
}

void CBKMEMDlg::DrawTab()
{
	if (!m_lockStop.IsLocked())
	{
		m_lockDraw.Lock();

		if (m_Container[m_nSelectedTab][MM_FIRST_PAGE].bExist)
		{
			m_Screen[MM_FIRST_PAGE]->ReDrawScreen();
		}

		if (m_Container[m_nSelectedTab][MM_SECOND_PAGE].bExist)
		{
			m_Screen[MM_SECOND_PAGE]->ReDrawScreen();
		}

		m_lockDraw.UnLock();
	}
}


void CBKMEMDlg::OnCancel()
{
	CBaseDialog::OnCancel();
	DestroyWindow();
}



void CBKMEMDlg::OnDestroy()
{
	// посылаем в MainFrame сообщение о закрытии всего этого тут
	GetParentFrame()->SendMessage(WM_MEMMAP_CLOSE);
	CBaseDialog::OnDestroy();
	m_lockStop.Lock();

	while (m_lockDraw.IsLocked())
	{
		Sleep(1); // тут может быть дедлок. если UI в m_pdlgBKMem->DrawTab() захочет сообщений;
	}

	m_tab.DeleteAllItems();
	// DestroyWindow надо делать вручную
	m_Screen[MM_FIRST_PAGE]->DestroyWindow();
	m_Screen[MM_SECOND_PAGE]->DestroyWindow();
	SAFE_DELETE(m_Screen[MM_FIRST_PAGE]);
	SAFE_DELETE(m_Screen[MM_SECOND_PAGE]);
	delete this; // самоудаляемся
	// и тут надо быть осторожным. Всё, что навыделяли - удалять вручную.
}

BOOL CBKMEMDlg::PreTranslateMessage(MSG *pMsg)
{
	if (pMsg->message >= WM_MOUSEFIRST && pMsg->message <= WM_MOUSELAST)
	{
		m_ToolTip.RelayEvent(pMsg);
	}

	return CBaseDialog::PreTranslateMessage(pMsg);
}


void CBKMEMDlg::OnBnClickedButtonMmColorModeP1()
{
	ChangeColormode(MM_FIRST_PAGE);
}


void CBKMEMDlg::OnBnClickedButtonMmBwModeP1()
{
	ChangeBWMode(MM_FIRST_PAGE);
}


void CBKMEMDlg::OnBnClickedButtonMmLoadP1()
{
	LoadImg(MM_FIRST_PAGE);
}


void CBKMEMDlg::OnBnClickedButtonMmSaveP1()
{
	SaveImg(MM_FIRST_PAGE);
}


void CBKMEMDlg::OnBnClickedButtonMmSpriteP1()
{
	ViewSprite(MM_FIRST_PAGE);
}


void CBKMEMDlg::OnBnClickedButtonMmColorModeP2()
{
	ChangeColormode(MM_SECOND_PAGE);
}


void CBKMEMDlg::OnBnClickedButtonMmBwModeP2()
{
	ChangeBWMode(MM_SECOND_PAGE);
}


void CBKMEMDlg::OnBnClickedButtonMmLoadP2()
{
	LoadImg(MM_SECOND_PAGE);
}


void CBKMEMDlg::OnBnClickedButtonMmSaveP2()
{
	SaveImg(MM_SECOND_PAGE);
}


void CBKMEMDlg::OnBnClickedButtonMmSpriteP2()
{
	ViewSprite(MM_SECOND_PAGE);
}

void CBKMEMDlg::ChangeColormode(int nPage)
{
	MM16kPage_t &pp = m_Container[m_nSelectedTab][nPage];
	bool b = !pp.bColorMode;
	pp.bColorMode = b;
	SetColormode(nPage, b);
}

void CBKMEMDlg::SetColormode(int nPage, bool bMode)
{
	m_Screen[nPage]->SetColorMode(bMode);

	switch (nPage)
	{
		case MM_FIRST_PAGE:
			reinterpret_cast<CButton *>(GetDlgItem(ID_BUTTON_MM_BW_MODE_P1))->EnableWindow(!bMode);
			break;

		case MM_SECOND_PAGE:
			reinterpret_cast<CButton *>(GetDlgItem(ID_BUTTON_MM_BW_MODE_P2))->EnableWindow(!bMode);
			break;
	}
}

void CBKMEMDlg::ChangeBWMode(int nPage)
{
	MM16kPage_t &pp = m_Container[m_nSelectedTab][nPage];
	bool b = !pp.bBWAdaptMode;
	pp.bBWAdaptMode = b;
	SetBWMode(nPage, b);
}

void CBKMEMDlg::SetBWMode(int nPage, bool bMode)
{
	m_Screen[nPage]->SetAdaptMode(bMode);
}

void CBKMEMDlg::ViewSprite(int nPage)
{
	MM16kPage_t &pp = m_Container[m_nSelectedTab][nPage];
	auto pSprWnd = new CSprWnd(pp.pBuffer, pp.nBufSize); // обязательно создавать динамически.

	if (pSprWnd)
	{
		WNDCLASS wc;
		memset(&wc, 0, sizeof(wc));
		wc.style = CS_HREDRAW | CS_VREDRAW;
		wc.hInstance = AfxGetInstanceHandle();
		wc.lpfnWndProc = ::DefWindowProc;
		wc.lpszClassName = _T("MySprClass");
		wc.hbrBackground = (HBRUSH)::GetStockObject(GRAY_BRUSH);
		AfxRegisterClass(&wc);
		CString str, str2;
		m_tab.GetWindowText(str);
		str2.Format(_T(" %d"), nPage);

		if (pSprWnd->Create(
		            _T("MySprClass"), // Имя класса виндовс
		            _T("Sprite View : ") + str + str2, // имя окна
		            WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME, // window styles
		            CRect(0, 0, 200, 200), // размер окна
		            GetParentOwner(), // родитель окна
		            nullptr,
		            WS_EX_TOOLWINDOW // | WS_EX_OVERLAPPEDWINDOW // extended window styles
		        ))
		{
			pSprWnd->ShowWindow(SW_SHOW);
		}
	}
	else
	{
		g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
	}

	m_Screen[nPage]->SetFocus();
}

#pragma warning(disable:4996)

void CBKMEMDlg::SaveImg(int nPage)
{
	MM16kPage_t &pp = m_Container[m_nSelectedTab][nPage];

	if (!!(::GetAsyncKeyState(VK_SHIFT) & 0x8000))
	{
		// сохраним страницу как бин файл
		uint16_t nAddr = 040000;
		uint16_t nLen = pp.nBufSize;
		CString strDefExt(MAKEINTRESOURCE(IDS_FILEEXT_BINARY));
		CString strFilter(MAKEINTRESOURCE(IDS_FILEFILTER_BIN));
		CFileDialog dlg(FALSE, strDefExt, nullptr,
		                OFN_HIDEREADONLY | OFN_CREATEPROMPT | OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR | OFN_EXPLORER,
		                strFilter, this);
		dlg.GetOFN().lpstrInitialDir = g_Config.m_strBinPath.GetString(); // диалог всегда будем начинать с директории для bin файлов

		if (dlg.DoModal() == IDOK)
		{
			CString str = dlg.GetPathName();
			::SaveBinFile(pp.pBuffer, nAddr, nLen, str);
		}
	}
	else
	{
		HBITMAP hBm = nullptr;
		uint32_t *pNewBits = nullptr;

		if (pp.bColorMode)
		{
			// сохранение страницы как изображения. совершенно особый подход.
			// сохранять будем как есть
			pNewBits = new uint32_t[256 * 256]; // новое битовое поле

			if (pNewBits)
			{
				int nbi = 0;

				// формируем битмап
				for (int y = 0; y < 256; ++y)
				{
					// алгоритм не оптимален, но нам нужна принципиальная работоспособность
					// оптимизируем потом
					for (int x = 0; x < 256; ++x)
					{
						// берём очередной бит.
						int b = y * 0100 + x / 4;
						int m = ((x % 4) * 2);
						uint8_t v = pp.pBuffer[b];
						int c = (v >> m) & 3;
						pNewBits[nbi++] = g_pColorPalettes[0][c];
					}
				}

				hBm = CreateBitmap(256, 256, 1, 32, pNewBits);
			}
			else
			{
				g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
			}
		}
		else
		{
			// создаём чёрно-белую картинку
			pNewBits = new uint32_t[512 * 256]; // новое битовое поле

			if (pNewBits)
			{
				int nbi = 0;

				// формируем битмап
				for (int y = 0; y < 256; ++y)
				{
					// алгоритм не оптимален, но нам нужна принципиальная работоспособность
					// оптимизируем потом
					for (int x = 0; x < 512; ++x)
					{
						// берём очередной бит.
						int b = y * 0100 + x / 8;
						int m = (x % 8);
						uint8_t v = pp.pBuffer[b];
						int c = (v >> m) & 1;
						pNewBits[nbi++] = g_pMonochromePalette[0][c];
					}
				}

				hBm = CreateBitmap(512, 256, 1, 32, pNewBits);
			}
			else
			{
				g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
			}
		}

		if (pNewBits)
		{
			CImage img;
			img.Attach(hBm);
			CString strFilter;
			CSimpleArray<GUID> aguidFileTypes;
			img.GetExporterFilterString(strFilter, aguidFileTypes);
			CFileDialog dlg(FALSE, _T("jpg"), nullptr,
			                OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_EXPLORER,
			                strFilter, this);

			if (dlg.DoModal() == IDOK)
			{
				DWORD nFilterSave = dlg.m_ofn.nFilterIndex;
				GUID guid = nFilterSave > 0 ? aguidFileTypes[nFilterSave - 1] : GUID(GUID_NULL);
				CString strFileName = dlg.GetPathName();

				if (dlg.GetFileExt().IsEmpty())
				{
					if (strFileName[strFileName.GetLength() - 1] == '.')
					{
						strFileName = strFileName.Left(strFileName.GetLength() - 1);
					}

					CString strExt;

					if (nFilterSave == 0)
					{
						strExt = _T(".jpg"); // default to JPEG
					}
					else
					{
						// Look up the first extension in the filters
						int nCount = (nFilterSave * 2) - 1;
						int nLeft = 0;

						while (nCount)
						{
							if (strFilter[nLeft++] == '|')
							{
								nCount--;
							}
						}

						ASSERT(nLeft < strFilter.GetLength());
						strExt = strFilter.Tokenize(_T(";|"), nLeft).MakeLower();
						strExt = ::PathFindExtension(strExt);
					}

					strFileName += strExt;
				}

				img.Save(strFileName, guid);
			}

			img.Detach();
			DeleteObject(hBm);
			delete[] pNewBits;
		}
	}

	m_Screen[nPage]->SetFocus();
}

void CBKMEMDlg::LoadImg(int nPage)
{
	MM16kPage_t &pp = m_Container[m_nSelectedTab][nPage];

	if (!!(::GetAsyncKeyState(VK_SHIFT) & 0x8000))
	{
		// загрузим страницу как бин файл
		CString strDefExt(MAKEINTRESOURCE(IDS_FILEEXT_BINARY));
		CString strFilter(MAKEINTRESOURCE(IDS_FILEFILTER_BIN));
		CFileDialog dlg(TRUE, strDefExt, nullptr,
		                OFN_HIDEREADONLY | OFN_CREATEPROMPT | OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR | OFN_EXPLORER,
		                strFilter, this);
		dlg.GetOFN().lpstrInitialDir = g_Config.m_strBinPath.GetString(); // диалог всегда будем начинать с директории для bin файлов

		if (dlg.DoModal() == IDOK)
		{
			CString str = dlg.GetPathName();
			uint16_t nAddr, nLen;
			uint8_t *buf = nullptr;

			if (::LoadBinFile(&buf, &nAddr, &nLen, str, true))
			{
				if (nLen >= pp.nBufSize)
				{
					nLen = pp.nBufSize;
				}

				memcpy(pp.pBuffer, buf, nLen);
				delete[] buf;
			}
		}
	}
	else
	{
		CImage img;
		CString strImporters;
		CSimpleArray<GUID> aguidFileTypes;
		CString sf(MAKEINTRESOURCE(IDS_FILEFILTER_IMGLOAD));
		img.GetImporterFilterString(strImporters, aguidFileTypes, sf);
		CFileDialog dlg(TRUE, _T("png"), nullptr,
		                OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_EXPLORER,
		                strImporters, this);

		if (dlg.DoModal() == IDOK)
		{
			if (SUCCEEDED(img.Load(dlg.GetPathName())))
			{
				int w = img.GetWidth();
				int h = img.GetHeight();

				if (pp.bColorMode)
				{
					w = min(256, w); // если подсовывают большую картинку -
					h = min(256, h); // будем тупо обрезать её

					for (int y = 0; y < h; ++y)
					{
						// алгоритм не оптимален, но нам нужна принципиальная работоспособность
						// оптимизируем потом
						for (int x = 0; x < w; ++x)
						{
							// берём очередной пиксел.
							COLORREF c = img.GetPixel(x, y);
							int b = y * 0100 + x / 4; // байт
							int m = ((x % 4) * 2); // маска в нём
							// теперь определим, какой цвет у пикселя
							int bc = 0; // чёрный
							int cr = (c & 0x0000ff);
							int cg = (c & 0x00ff00) >> 8;
							int cb = (c & 0xff0000) >> 16;
							// теперь узнаем, какого цвета больше.
							int p = max(max(cg, cb), cr);

							if (p > 0x3f) //если макс значение выше порога, то только тогда будем определять цвет
							{
								if (cr > max(cg, cb))
								{
									bc = 3; // то это красный цвет
								}
								else if (cg > max(cr, cb))
								{
									bc = 2; // то это зелёный цвет
								}
								else if (cb > max(cr, cg))
								{
									bc = 1; // то это синий цвет
								}
							}

							// все остальные комбинации - чёрный цвет. нефиг тут.
							pp.pBuffer[b] &= ~(3 << m);
							pp.pBuffer[b] |= bc << m; // задаём цвет
						}
					}
				}
				else
				{
					w = min(512, w); // если подсовывают большую картинку -
					h = min(256, h); // будем тупо обрезать её

					for (int y = 0; y < h; ++y)
					{
						// алгоритм не оптимален, но нам нужна принципиальная работоспособность
						// оптимизируем потом
						for (int x = 0; x < w; ++x)
						{
							// берём очередной пиксел.
							COLORREF c = img.GetPixel(x, y);
							int b = y * 0100 + x / 8; // байт
							int m = (x % 8); // маска в нём
							// теперь определим, какой цвет у пикселя
							int bc = 0; // чёрный
							int cr = (c & 0x0000ff);
							int cg = (c & 0x00ff00) >> 8;
							int cb = (c & 0xff0000) >> 16;
							int p = max(max(cg, cb), cr);

							if (p > 0x7f)
							{
								bc = 1; // то это белый цвет
							}

							// все остальные комбинации - чёрный цвет. нефиг тут.
							pp.pBuffer[b] &= ~(1 << m);
							pp.pBuffer[b] |= bc << m; // задаём цвет
						}
					}
				}

				img.Destroy();
			}
		}
	}

	m_Screen[nPage]->SetFocus();
}


/*
вот так эта вся херня выглядит.
CBKMEMDlg-----------------------------------------------+
| CTabCtrl----------------------------------------+     |
| | CScreen-------------------------------------+ | +-+ |
| | |                                           | | +-+ |
| | |                                           | | +-+ |
| | |                                           | | +-+ |
| | |                                           | | +-+ |
| | |                                           | | +-+ |
| | +-------------------------------------------+ |     |
| | CScreen-------------------------------------+ | +-+ |
| | |                                           | | +-+ |
| | |                                           | | +-+ |
| | |                                           | | +-+ |
| | |                                           | | +-+ |
| | |                                           | | +-+ |
| | +-------------------------------------------+ |     |
| +-----------------------------------------------+     |
+-------------------------------------------------------+
Справа от CTabCtrl - кнопки
*/



