fceux/src/drivers/win/tasedit.cpp

576 lines
15 KiB
C++

#include <set>
#include <fstream>
#include "common.h"
#include "tasedit.h"
#include "fceu.h"
#include "debugger.h"
#include "replay.h"
#include "movie.h"
#include "utils/xstring.h"
#include "Win32InputBox.h"
#include "keyboard.h"
#include "joystick.h"
using namespace std;
//to change header font
//http://forums.devx.com/archive/index.php/t-37234.html
HWND hwndTasEdit = 0;
static HMENU hmenu, hrmenu;
static int lastCursor;
static HWND hwndList, hwndHeader;
static WNDPROC hwndHeader_oldWndproc, hwndList_oldWndProc;
typedef std::set<int> TSelectionFrames;
static TSelectionFrames selectionFrames;
//hacky.. we need to think about how to convey information from the driver to the movie code.
//add a new fceud_ function?? blehhh maybe
extern EMOVIEMODE movieMode;
static void GetDispInfo(NMLVDISPINFO* nmlvDispInfo)
{
LVITEM& item = nmlvDispInfo->item;
if(item.mask & LVIF_TEXT)
{
switch(item.iSubItem)
{
case 0:
if(item.iImage == I_IMAGECALLBACK && item.iItem == currFrameCounter)
item.iImage = 0;
else
item.iImage = -1;
break;
case 1:
U32ToDecStr(item.pszText,item.iItem);
break;
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9: {
int bit = (item.iSubItem - 2);
uint8 data = currMovieData.records[item.iItem].joysticks[0];
if(data & (1<<bit))
{
item.pszText[0] = MovieRecord::mnemonics[bit];
item.pszText[1] = 0;
} else
item.pszText[1] = 0;
}
break;
}
}
}
#define CDDS_ITEMPREPAINT (CDDS_ITEM | CDDS_PREPAINT)
#define CDDS_ITEMPOSTPAINT (CDDS_ITEM | CDDS_POSTPAINT)
#define CDDS_ITEMPREERASE (CDDS_ITEM | CDDS_PREERASE)
#define CDDS_ITEMPOSTERASE (CDDS_ITEM | CDDS_POSTERASE)
#define CDDS_SUBITEMPREPAINT (CDDS_SUBITEM | CDDS_ITEMPREPAINT)
#define CDDS_SUBITEMPOSTPAINT (CDDS_SUBITEM | CDDS_ITEMPOSTPAINT)
#define CDDS_SUBITEMPREERASE (CDDS_SUBITEM | CDDS_ITEMPREERASE)
#define CDDS_SUBITEMPOSTERASE (CDDS_SUBITEM | CDDS_ITEMPOSTERASE)
static LONG CustomDraw(NMLVCUSTOMDRAW* msg)
{
switch(msg->nmcd.dwDrawStage)
{
case CDDS_PREPAINT:
return CDRF_NOTIFYITEMDRAW;
case CDDS_ITEMPREPAINT:
return CDRF_NOTIFYSUBITEMDRAW;
case CDDS_SUBITEMPREPAINT:
SelectObject(msg->nmcd.hdc,debugSystem->hFixedFont);
if((int)msg->nmcd.dwItemSpec < currMovieData.greenZoneCount)
msg->clrTextBk = RGB(192,255,192);
return CDRF_DODEFAULT;
default:
return CDRF_DODEFAULT;
}
}
// called from the rest of the emulator when things happen and the tasedit should change to reflect it
void UpdateTasEdit()
{
if(!hwndTasEdit) return;
//update the number of items
int currLVItemCount = ListView_GetItemCount(hwndList);
if(currMovieData.getNumRecords() != currLVItemCount)
{
ListView_SetItemCountEx(hwndList,currMovieData.getNumRecords(),LVSICF_NOSCROLL | LVSICF_NOINVALIDATEALL);
}
//update the cursor
int newCursor = currFrameCounter;
if(newCursor != lastCursor)
{
//unselect all prior rows
TSelectionFrames oldSelected = selectionFrames;
for(TSelectionFrames::iterator it(oldSelected.begin()); it != oldSelected.end(); it++)
ListView_SetItemState(hwndList,*it,0, LVIS_FOCUSED|LVIS_SELECTED);
//scroll to the row
ListView_EnsureVisible(hwndList,newCursor,FALSE);
//select the row
ListView_SetItemState(hwndList,newCursor,LVIS_FOCUSED|LVIS_SELECTED,LVIS_FOCUSED|LVIS_SELECTED);
//update the old and new rows
ListView_Update(hwndList,newCursor);
ListView_Update(hwndList,lastCursor);
for(TSelectionFrames::iterator it(oldSelected.begin()); it != oldSelected.end(); it++)
ListView_Update(hwndList,*it);
lastCursor = newCursor;
}
}
void RedrawList()
{
InvalidateRect(hwndList,0,FALSE);
}
enum ECONTEXTMENU
{
CONTEXTMENU_STRAY = 0,
CONTEXTMENU_SELECTED = 1,
};
void ShowMenu(ECONTEXTMENU which, POINT& pt)
{
HMENU sub = GetSubMenu(hrmenu,(int)which);
TrackPopupMenu(sub,0,pt.x,pt.y,TPM_RIGHTBUTTON,hwndTasEdit,0);
}
void StrayClickMenu(LPNMITEMACTIVATE info)
{
POINT pt = info->ptAction;
ClientToScreen(hwndList,&pt);
ShowMenu(CONTEXTMENU_STRAY,pt);
}
void RightClickMenu(LPNMITEMACTIVATE info)
{
POINT pt = info->ptAction;
ClientToScreen(hwndList,&pt);
ShowMenu(CONTEXTMENU_SELECTED,pt);
}
void RightClick(LPNMITEMACTIVATE info)
{
int index = info->iItem;
int column = info->iSubItem;
//stray clicks give a context menu:
if(index == -1)
{
StrayClickMenu(info);
return;
}
//make sure that the click is in our currently selected set.
//if it is not, then we don't know what to do yet
if(selectionFrames.find(index) == selectionFrames.end())
{
return;
}
RightClickMenu(info);
}
void InvalidateGreenZone(int after)
{
currMovieData.greenZoneCount = std::min(after+1,currMovieData.greenZoneCount);
}
void DoubleClick(LPNMITEMACTIVATE info)
{
int index = info->iItem;
//stray click
if(index == -1)
return;
//if the icon or frame columns were double clicked:
if(info->iSubItem == 0 || info->iSubItem == 1)
{
//if the row is in the green zone, then move to it
if(index < currMovieData.greenZoneCount)
{
MovieData::loadSavestateFrom(&currMovieData.records[index].savestate);
currFrameCounter = index;
}
}
else //if an input column was clicked:
{
//toggle the bit
int bit = (info->iSubItem-2);
currMovieData.records[index].toggleBit(0,bit);
//update the row
ListView_Update(hwndList,index);
InvalidateGreenZone(index);
//redraw everything to show the reduced green zone
RedrawList();
}
}
//insert frames at the currently selected positions.
static void InsertFrames()
{
int frames = selectionFrames.size();
//this is going to be slow.
//to keep this from being even slower than it would otherwise be, go ahead and reserve records
currMovieData.records.reserve(currMovieData.records.size()+frames);
//insert frames before each selection
int ctr=0;
for(TSelectionFrames::iterator it(selectionFrames.begin()); it != selectionFrames.end(); it++)
{
currMovieData.insertEmpty(*it+ctr,1);
ctr++;
}
InvalidateGreenZone(*selectionFrames.begin());
RedrawList();
}
//the column set operation, for setting a button for a span of selected values
static void ColumnSet(int column)
{
int button = column-2;
//inspect the selected frames. count the set and unset rows
int set=0, unset=0;
for(TSelectionFrames::iterator it(selectionFrames.begin()); it != selectionFrames.end(); it++)
{
if(currMovieData.records[*it].checkBit(0,button))
set++;
else unset++;
}
//if it is half and half, then set them all
//if they are all set, unset them all
//if they are all unset, set them all
bool setz = (set==0);
bool unsetz = (unset==0);
bool newValue;
//do nothing if we didnt even have any work to do
if(setz && unsetz)
return;
//all unset.. set them
else if(setz && !unsetz)
newValue = true;
//all set.. unset them
else if(!setz && unsetz)
newValue = false;
//a mix. set them.
else newValue = true;
//operate on the data and update the listview
for(TSelectionFrames::iterator it(selectionFrames.begin()); it != selectionFrames.end(); it++)
{
currMovieData.records[*it].setBitValue(0,button,newValue);
//we would do this if we wanted to update the affected record. but that results in big operations
//redrawing once per item set, which causes it to flicker and take forever.
//so now we rely on the update at the end.
//ListView_Update(hwndList,*it);
}
//reduce the green zone
InvalidateGreenZone(*selectionFrames.begin());
//redraw everything to show the reduced green zone
RedrawList();
}
//copies the current selection to the clipboard
static void Copy()
{
}
//The subclass wndproc for the listview header
static LRESULT APIENTRY HeaderWndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
switch(msg)
{
case WM_LBUTTONDOWN:
{
//perform hit test
HDHITTESTINFO info;
info.pt.x = GET_X_LPARAM(lParam);
info.pt.y = GET_Y_LPARAM(lParam);
SendMessage(hWnd,HDM_HITTEST,0,(LPARAM)&info);
if(info.iItem != -1)
ColumnSet(info.iItem);
}
}
return CallWindowProc(hwndHeader_oldWndproc,hWnd,msg,wParam,lParam);
}
//The subclass wndproc for the listview
static LRESULT APIENTRY ListWndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
switch(msg)
{
case WM_CHAR:
return 0;
}
return CallWindowProc(hwndList_oldWndProc,hWnd,msg,wParam,lParam);
}
//All dialog initialization
static void InitDialog()
{
//prepare the listview
ListView_SetExtendedListViewStyleEx(hwndList,
LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES ,
LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES );
//subclass the header
hwndHeader = ListView_GetHeader(hwndList);
hwndHeader_oldWndproc = (WNDPROC)SetWindowLong(hwndHeader,GWL_WNDPROC,(LONG)HeaderWndProc);
//subclass the whole listview, so we can block some keystrokes
hwndList_oldWndProc = (WNDPROC)SetWindowLong(hwndList,GWL_WNDPROC,(LONG)ListWndProc);
//setup all images for the listview
HIMAGELIST himglist = ImageList_Create(12,12,ILC_COLOR32 | ILC_MASK,1,1);
HBITMAP bmp = LoadBitmap(fceu_hInstance,MAKEINTRESOURCE(IDB_TE_ARROW));
ImageList_AddMasked(himglist, bmp, RGB(255,0,255));
DeleteObject(bmp);
ListView_SetImageList(hwndList,himglist,LVSIL_SMALL);
//doesnt work well??
//HIMAGELIST himglist = ImageList_LoadImage(fceu_hInstance,MAKEINTRESOURCE(IDB_TE_ARROW),12,1,RGB(255,0,255),IMAGE_BITMAP,LR_DEFAULTCOLOR);
//setup columns
LVCOLUMN lvc;
int colidx=0;
lvc.mask = LVCF_WIDTH;
lvc.cx = 12;
ListView_InsertColumn(hwndList, colidx++, &lvc);
lvc.mask = LVCF_WIDTH | LVCF_TEXT;
lvc.cx = 95;
lvc.pszText = "Frame#";
ListView_InsertColumn(hwndList, colidx++, &lvc);
lvc.cx = 20;
lvc.pszText = "A";
ListView_InsertColumn(hwndList, colidx++, &lvc);
lvc.pszText = "B";
ListView_InsertColumn(hwndList, colidx++, &lvc);
lvc.pszText = "S";
ListView_InsertColumn(hwndList, colidx++, &lvc);
lvc.pszText = "T";
ListView_InsertColumn(hwndList, colidx++, &lvc);
lvc.pszText = "U";
ListView_InsertColumn(hwndList, colidx++, &lvc);
lvc.pszText = "D";
ListView_InsertColumn(hwndList, colidx++, &lvc);
lvc.pszText = "L";
ListView_InsertColumn(hwndList, colidx++, &lvc);
lvc.pszText = "R";
ListView_InsertColumn(hwndList, colidx++, &lvc);
//-----------------------------
//the initial update
UpdateTasEdit();
}
void KillTasEdit()
{
DestroyWindow(hwndTasEdit);
hwndTasEdit = 0;
FCEUMOV_ExitTasEdit();
}
static void Export()
{
const char filter[]="FCEUX Movie File(*.fm2)\0*.fm2\0";
char fname[2048] = {0};
OPENFILENAME ofn;
memset(&ofn,0,sizeof(ofn));
ofn.lStructSize=sizeof(ofn);
ofn.hInstance=fceu_hInstance;
ofn.lpstrTitle="Export TAS as...";
ofn.lpstrFilter=filter;
ofn.lpstrFile=fname;
ofn.nMaxFile=256;
std::string initdir = FCEU_GetPath(FCEUMKF_MOVIE);
ofn.lpstrInitialDir=initdir.c_str();
if(GetSaveFileName(&ofn))
{
fstream* osRecordingMovie = FCEUD_UTF8_fstream(ofn.lpstrFile, "wb");
currMovieData.dump(osRecordingMovie,false);
delete osRecordingMovie;
osRecordingMovie = 0;
}
}
//likewise, handles a changed item range from the listview
static void ItemRangeChanged(NMLVODSTATECHANGE* info)
{
bool ON = !(info->uOldState & LVIS_SELECTED) && (info->uNewState & LVIS_SELECTED);
bool OFF = (info->uOldState & LVIS_SELECTED) && !(info->uNewState & LVIS_SELECTED);
if(ON)
for(int i=info->iFrom;i<=info->iTo;i++)
selectionFrames.insert(i);
else
for(int i=info->iFrom;i<=info->iTo;i++)
selectionFrames.erase(i);
}
//handles a changed item from the listview
//used to track selection
static void ItemChanged(NMLISTVIEW* info)
{
int item = info->iItem;
bool ON = !(info->uOldState & LVIS_SELECTED) && (info->uNewState & LVIS_SELECTED);
bool OFF = (info->uOldState & LVIS_SELECTED) && !(info->uNewState & LVIS_SELECTED);
//if the item is -1, apply the change to all items
if(item == -1)
{
if(OFF)
{
selectionFrames.clear();
}
else
FCEUD_PrintError("Unexpected condition in TasEdit ItemChanged. Please report.");
}
else
{
if(ON)
selectionFrames.insert(item);
else if(OFF)
selectionFrames.erase(item);
}
}
BOOL CALLBACK WndprocTasEdit(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_INITDIALOG:
hwndList = GetDlgItem(hwndDlg,IDC_LIST1);
InitDialog();
break;
case WM_NOTIFY:
switch(wParam)
{
case IDC_LIST1:
switch(((LPNMHDR)lParam)->code)
{
case NM_CUSTOMDRAW:
SetWindowLong(hwndDlg, DWL_MSGRESULT, CustomDraw((NMLVCUSTOMDRAW*)lParam));
return TRUE;
case LVN_GETDISPINFO:
GetDispInfo((NMLVDISPINFO*)lParam);
break;
case NM_DBLCLK:
DoubleClick((LPNMITEMACTIVATE)lParam);
break;
case NM_RCLICK:
RightClick((LPNMITEMACTIVATE)lParam);
break;
case LVN_ITEMCHANGED:
ItemChanged((LPNMLISTVIEW) lParam);
break;
case LVN_ODSTATECHANGED:
ItemRangeChanged((LPNMLVODSTATECHANGE) lParam);
break;
}
break;
}
break;
case WM_CLOSE:
case WM_QUIT:
KillTasEdit();
break;
case WM_COMMAND:
switch(LOWORD(wParam))
{
case MENU_INSERTFRAMES:
InsertFrames();
break;
case MENU_STRAY_INSERTFRAMES:
{
int frames;
if(CWin32InputBox::GetInteger("Insert Frames", "How many frames?", frames, hwndDlg) == IDOK)
{
currMovieData.insertEmpty(-1,frames);
RedrawList();
}
}
break;
case IDC_HACKY1:
//hacky1: delete all items after the current selection
currMovieData.records.resize(currFrameCounter+1);
InvalidateGreenZone(currFrameCounter);
UpdateTasEdit();
break;
case ID_FILE_OPENFM2:
Replay_LoadMovie(true);
break;
case ID_FILE_SAVEFM2:
Export();
break;
case ACCEL_CTRL_C:
Copy();
break;
case ACCEL_CTRL_W:
KillTasEdit();
break;
}
break;
}
return FALSE;
}
void DoTasEdit()
{
if(!hmenu)
{
hmenu = LoadMenu(fceu_hInstance,"TASEDITMENU");
hrmenu = LoadMenu(fceu_hInstance,"TASEDITCONTEXTMENUS");
}
lastCursor = -1;
if(!hwndTasEdit)
hwndTasEdit = CreateDialog(fceu_hInstance,"TASEDIT",NULL,WndprocTasEdit);
if(hwndTasEdit)
{
KeyboardSetBackgroundAccessBit(KEYBACKACCESS_TASEDIT);
JoystickSetBackgroundAccessBit(JOYBACKACCESS_TASEDIT);
FCEUMOV_EnterTasEdit();
SetWindowPos(hwndTasEdit,HWND_TOP,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE|SWP_NOOWNERZORDER);
}
}