BizHawk/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs

535 lines
15 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using System.ComponentModel;
using BizHawk.Client.Common;
using BizHawk.Client.Common.MovieConversionExtensions;
using BizHawk.Client.EmuHawk.WinFormExtensions;
using BizHawk.Client.EmuHawk.ToolExtensions;
namespace BizHawk.Client.EmuHawk
{
public partial class TAStudio : Form, IToolForm, IControlMainform
{
// TODO: UI flow that conveniently allows to start from savestate
private const string MarkerColumnName = "MarkerColumn";
private const string FrameColumnName = "FrameColumn";
private readonly List<TasClipboardEntry> _tasClipboard = new List<TasClipboardEntry>();
private int _defaultWidth;
private int _defaultHeight;
private TasMovie _currentTasMovie;
private MovieEndAction _originalEndAction; // The movie end behavior selected by the user (that is overridden by TAStudio)
2014-07-09 21:56:27 +00:00
private Dictionary<string, string> GenerateColumnNames()
{
2014-07-09 21:56:27 +00:00
var lg = Global.MovieSession.LogGeneratorInstance();
lg.SetSource(Global.MovieSession.MovieControllerAdapter);
return (lg as Bk2LogEntryGenerator).Map();
}
private int? _autoRestoreFrame; // The frame auto-restore will restore to, if set
public TAStudio()
{
InitializeComponent();
WantsToControlStopMovie = true;
2014-07-17 19:00:28 +00:00
TasPlaybackBox.Tastudio = this;
2014-07-16 02:20:14 +00:00
MarkerControl.Tastudio = this;
TasView.QueryItemText += TasView_QueryItemText;
TasView.QueryItemBkColor += TasView_QueryItemBkColor;
TasView.QueryItemIcon += TasView_QueryItemIcon;
TopMost = Global.Config.TAStudioSettings.TopMost;
TasView.InputPaintingMode = Global.Config.TAStudioDrawInput;
TasView.PointedCellChanged += TasView_PointedCellChanged;
TasView.MultiSelect = true;
TasView.MaxCharactersInHorizontal = 1;
}
2014-10-17 17:40:11 +00:00
public TasMovie CurrentMovie
{
get { return _currentTasMovie; }
}
private void TastudioToStopMovie()
{
Global.MovieSession.StopMovie(false);
GlobalWin.MainForm.SetMainformMovieInfo();
}
2014-10-17 17:58:48 +00:00
private static void ConvertCurrentMovieToTasproj()
2014-07-08 15:15:35 +00:00
{
Global.MovieSession.Movie.Save();
Global.MovieSession.Movie = Global.MovieSession.Movie.ToTasMovie();
Global.MovieSession.Movie.Save();
Global.MovieSession.Movie.SwitchToRecord();
2014-07-08 15:15:35 +00:00
}
private void EngageTastudio()
{
GlobalWin.MainForm.PauseOnFrame = null;
GlobalWin.OSD.AddMessage("TAStudio engaged");
_currentTasMovie = Global.MovieSession.Movie as TasMovie;
SetTasMovieCallbacks();
2014-09-16 23:26:17 +00:00
SetTextProperty();
2014-07-12 01:32:21 +00:00
GlobalWin.MainForm.PauseEmulator();
GlobalWin.MainForm.RelinquishControl(this);
_originalEndAction = Global.Config.MovieEndAction;
GlobalWin.MainForm.ClearRewindData();
Global.Config.MovieEndAction = MovieEndAction.Record;
GlobalWin.MainForm.SetMainformMovieInfo();
2014-07-08 15:15:35 +00:00
}
2014-07-08 15:15:35 +00:00
private void DisengageTastudio()
{
GlobalWin.MainForm.PauseOnFrame = null;
2014-07-08 15:15:35 +00:00
GlobalWin.OSD.AddMessage("TAStudio disengaged");
Global.MovieSession.Movie = MovieService.DefaultInstance;
GlobalWin.MainForm.TakeBackControl();
Global.Config.MovieEndAction = _originalEndAction;
GlobalWin.MainForm.SetMainformMovieInfo();
2014-07-08 15:15:35 +00:00
}
2014-07-08 15:15:35 +00:00
private void NewTasMovie()
{
Global.MovieSession.Movie = new TasMovie();
_currentTasMovie = Global.MovieSession.Movie as TasMovie;
SetTasMovieCallbacks();
_currentTasMovie.PropertyChanged += new PropertyChangedEventHandler(this.TasMovie_OnPropertyChanged);
_currentTasMovie.Filename = DefaultTasProjName(); // TODO don't do this, take over any mainform actions that can crash without a filename
_currentTasMovie.PopulateWithDefaultHeaderValues();
_currentTasMovie.ClearChanges();
TasView.RowCount = 1;
2014-07-08 15:15:35 +00:00
}
private static string DefaultTasProjName()
{
return Path.Combine(
PathManager.MakeAbsolutePath(Global.Config.PathEntries.MoviesPathFragment, null),
PathManager.FilesystemSafeName(Global.Game) + "." + TasMovie.Extension);
}
private void SetTasMovieCallbacks()
{
_currentTasMovie.ClientSettingsForSave = ClientSettingsForSave;
_currentTasMovie.GetClientSettingsOnLoad = GetClientSettingsOnLoad;
}
private void StartNewTasMovie()
{
if (AskSaveChanges())
{
NewTasMovie();
WantsToControlStopMovie = false;
StartNewMovieWrapper(record: true);
WantsToControlStopMovie = true;
2014-09-16 23:26:17 +00:00
SetTextProperty();
2014-07-09 22:44:20 +00:00
RefreshDialog();
}
}
private string ClientSettingsForSave()
{
return TasView.UserSettingsSerialized();
}
private void GetClientSettingsOnLoad(string settingsJson)
{
TasView.LoadSettingsSerialized(settingsJson);
TasView.Refresh();
}
2014-09-16 23:26:17 +00:00
private void SetTextProperty()
{
var text = "TAStudio";
if (_currentTasMovie != null)
{
text += " - " + _currentTasMovie.Name + (_currentTasMovie.Changes ? "*" : "");
}
Text = text;
}
public bool LoadProject(string path)
2014-07-08 15:15:35 +00:00
{
if (AskSaveChanges())
2014-07-08 15:15:35 +00:00
{
var movie = new TasMovie
2014-07-09 22:44:20 +00:00
{
Filename = path,
ClientSettingsForSave = ClientSettingsForSave,
GetClientSettingsOnLoad = GetClientSettingsOnLoad
2014-07-09 22:44:20 +00:00
};
2014-09-25 12:28:58 +00:00
movie.PropertyChanged += TasMovie_OnPropertyChanged;
2014-09-25 12:28:58 +00:00
movie.Load();
2014-07-08 15:15:35 +00:00
2014-07-09 22:44:20 +00:00
var file = new FileInfo(path);
if (!file.Exists)
2014-07-08 15:15:35 +00:00
{
Global.Config.RecentTas.HandleLoadError(path);
2014-07-08 15:15:35 +00:00
}
2014-07-09 22:44:20 +00:00
WantsToControlStopMovie = false;
2014-10-17 17:58:48 +00:00
var shouldRecord = movie.InputLogLength == 0;
var result = StartNewMovieWrapper(record: true);
if (!result)
{
return false;
}
_currentTasMovie = Global.MovieSession.Movie as TasMovie;
SetTasMovieCallbacks();
WantsToControlStopMovie = true;
2014-07-09 22:44:20 +00:00
Global.Config.RecentTas.Add(path);
Text = "TAStudio - " + _currentTasMovie.Name;
2014-07-09 22:44:20 +00:00
RefreshDialog();
return true;
2014-07-08 15:15:35 +00:00
}
return false;
2014-07-08 15:15:35 +00:00
}
2014-07-16 02:17:19 +00:00
public void RefreshDialog()
2014-07-08 15:15:35 +00:00
{
_currentTasMovie.FlushInputCache();
_currentTasMovie.UseInputCache = true;
TasView.RowCount = _currentTasMovie.InputLogLength + 1;
TasView.Refresh();
2014-08-23 16:00:56 +00:00
_currentTasMovie.FlushInputCache();
_currentTasMovie.UseInputCache = false;
if (MarkerControl != null)
{
MarkerControl.UpdateValues();
}
2014-07-08 15:15:35 +00:00
}
private void DoAutoRestore()
{
if (Global.Config.TAStudioAutoRestoreLastPosition && _autoRestoreFrame.HasValue)
{
if (_autoRestoreFrame > Global.Emulator.Frame) // Don't unpause if we are already on the desired frame, else runaway seek
{
GlobalWin.MainForm.UnpauseEmulator();
GlobalWin.MainForm.PauseOnFrame = _autoRestoreFrame;
}
2014-07-13 15:26:50 +00:00
}
_autoRestoreFrame = null;
2014-07-13 15:26:50 +00:00
}
private void SetUpColumns()
{
TasView.AllColumns.Clear();
AddColumn(MarkerColumnName, string.Empty, 18);
AddColumn(FrameColumnName, "Frame#", 68);
2014-07-09 21:56:27 +00:00
foreach (var kvp in GenerateColumnNames())
{
AddColumn(kvp.Key, kvp.Value, 20 * kvp.Value.Length);
}
}
public void AddColumn(string columnName, string columnText, int columnWidth)
{
if (TasView.AllColumns[columnName] == null)
{
var column = new InputRoll.RollColumn
{
Name = columnName,
Text = columnText,
Width = columnWidth,
};
TasView.AllColumns.Add(column);
}
}
private void LoadConfigSettings()
{
_defaultWidth = Size.Width;
_defaultHeight = Size.Height;
if (Global.Config.TAStudioSettings.UseWindowPosition)
{
Location = Global.Config.TAStudioSettings.WindowPosition;
}
if (Global.Config.TAStudioSettings.UseWindowSize)
{
Size = Global.Config.TAStudioSettings.WindowSize;
}
}
private void SaveConfigSettings()
{
Global.Config.TAStudioSettings.Wndx = Location.X;
Global.Config.TAStudioSettings.Wndy = Location.Y;
Global.Config.TAStudioSettings.Width = Right - Left;
Global.Config.TAStudioSettings.Height = Bottom - Top;
}
2014-08-31 16:51:19 +00:00
private void StartAtNearestFrameAndEmulate(int frame)
{
_currentTasMovie.SwitchToPlay();
var closestState = _currentTasMovie.TasStateManager.GetStateClosestToFrame(frame);
2014-08-31 16:51:19 +00:00
if (closestState != null)
{
LoadState(closestState.ToArray());
2014-08-31 16:51:19 +00:00
}
GlobalWin.MainForm.PauseOnFrame = frame;
GlobalWin.MainForm.UnpauseEmulator();
}
private void LoadState(byte[] state)
{
Global.Emulator.LoadStateBinary(new BinaryReader(new MemoryStream(state)));
_hackyDontUpdate = true;
GlobalWin.Tools.UpdateBefore();
GlobalWin.Tools.UpdateAfter();
_hackyDontUpdate = false;
}
private void UpdateOtherTools() // a hack probably, surely there is a better way to do this
{
_hackyDontUpdate = true;
GlobalWin.Tools.UpdateBefore();
GlobalWin.Tools.UpdateAfter();
_hackyDontUpdate = false;
}
2014-07-17 19:00:28 +00:00
public void TogglePause()
{
GlobalWin.MainForm.TogglePause();
2014-07-17 19:00:28 +00:00
}
private void SetSplicer()
{
// TODO: columns selected
// TODO: clipboard
var list = TasView.SelectedRows;
string message = "Selected: ";
if (list.Any())
{
message += list.Count() + " rows 0 col, Clipboard: ";
}
else
{
message += list.Count() + " none, Clipboard: ";
}
2014-10-17 17:58:48 +00:00
message += _tasClipboard.Any() ? _tasClipboard.Count + " rows 0 col": "empty";
SplicerStatusLabel.Text = message;
}
private void RefreshFloatingWindowControl()
{
Owner = Global.Config.TAStudioSettings.FloatingWindow ? null : GlobalWin.MainForm;
}
2014-07-16 02:17:19 +00:00
public void CallAddMarkerPopUp(int? frame = null)
{
var markerFrame = frame ?? TasView.LastSelectedIndex ?? Global.Emulator.Frame;
InputPrompt i = new InputPrompt
{
Text = "Marker for frame " + markerFrame,
TextInputType = InputPrompt.InputType.Text,
Message = "Enter a message",
InitialValue = _currentTasMovie.Markers.IsMarker(markerFrame) ? _currentTasMovie.Markers.PreviousOrCurrent(markerFrame).Message : ""
};
var result = i.ShowHawkDialog();
if (result == DialogResult.OK)
{
_currentTasMovie.Markers.Add(markerFrame, i.PromptText);
MarkerControl.UpdateValues();
}
}
2014-07-11 02:31:43 +00:00
private void UpdateChangesIndicator()
{
// TODO
}
// TODO: move me
// Sets either the pending frame or the tas input log
private void ToggleBoolState(int frame, string buttonName)
{
if (frame < _currentTasMovie.InputLogLength)
{
_currentTasMovie.ToggleBoolState(frame, buttonName);
}
else if (frame == Global.Emulator.Frame && frame == _currentTasMovie.InputLogLength)
{
Global.ClickyVirtualPadController.Toggle(buttonName);
}
}
// TODO: move me
// Sets either the pending frame or the tas input log
private void SetBoolState(int frame, string buttonName, bool value)
{
if (frame < _currentTasMovie.InputLogLength)
{
_currentTasMovie.SetBoolState(frame, buttonName, value);
}
else if (frame == Global.Emulator.Frame && frame == _currentTasMovie.InputLogLength)
{
Global.ClickyVirtualPadController.SetBool(buttonName, value);
}
}
private void SetColumnsFromCurrentStickies()
{
foreach (var column in TasView.VisibleColumns)
{
if (Global.StickyXORAdapter.IsSticky(column.Name))
{
column.Emphasis = true;
}
}
}
2014-10-17 17:40:11 +00:00
private void NewDefaultProject()
{
NewTasMovie();
StartNewMovieWrapper(record: true);
2014-10-17 17:40:11 +00:00
_currentTasMovie.TasStateManager.Capture();
_currentTasMovie.SwitchToRecord();
_currentTasMovie.ClearChanges();
}
private bool StartNewMovieWrapper(bool record)
{
_initializing = true;
var result = GlobalWin.MainForm.StartNewMovie(_currentTasMovie, record);
_initializing = false;
return result;
}
2014-10-17 17:40:11 +00:00
#region Dialog Events
private void Tastudio_Load(object sender, EventArgs e)
{
InitializeOnLoad();
LoadConfigSettings();
SetColumnsFromCurrentStickies();
RightClickMenu.Items.AddRange(TasView.GenerateContextMenuItems().ToArray());
RefreshDialog();
}
private void InitializeOnLoad()
{
// Start Scenario 1: A regular movie is active
if (Global.MovieSession.Movie.IsActive && !(Global.MovieSession.Movie is TasMovie))
{
var result = MessageBox.Show("In order to use Tastudio, a new project must be created from the current movie\nThe current movie will be saved and closed, and a new project file will be created\nProceed?", "Convert movie", MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
if (result == DialogResult.OK)
{
ConvertCurrentMovieToTasproj();
}
else
{
Close();
return;
}
}
// Start Scenario 2: A tasproj is already active
else if (Global.MovieSession.Movie.IsActive && Global.MovieSession.Movie is TasMovie)
{
// Nothing to do
}
// Start Scenario 3: No movie, but user wants to autload their last project
else if (Global.Config.AutoloadTAStudioProject && !string.IsNullOrEmpty(Global.Config.RecentTas.MostRecent))
{
var result = LoadProject(Global.Config.RecentTas.MostRecent);
if (!result)
{
TasView.AllColumns.Clear();
2014-10-17 17:40:11 +00:00
NewDefaultProject();
}
}
// Start Scenario 4: No movie, default behavior of engaging tastudio with a new default project
else
{
2014-10-17 17:40:11 +00:00
NewDefaultProject();
}
EngageTastudio();
if (!TasView.AllColumns.Any()) // If a project with column settings has already been loaded we don't need to do this
{
SetUpColumns();
}
}
private void Tastudio_Closing(object sender, FormClosingEventArgs e)
{
if (AskSaveChanges())
{
SaveConfigSettings();
GlobalWin.MainForm.StopMovie(saveChanges: false);
DisengageTastudio();
}
else
{
e.Cancel = true;
}
}
2014-10-17 17:40:11 +00:00
protected override void OnShown(EventArgs e)
{
RefreshFloatingWindowControl();
base.OnShown(e);
}
/// <summary>
/// This method is called everytime the Changes property is toggled on a TasMovie instance.
/// </summary>
private void TasMovie_OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
2014-09-16 23:26:17 +00:00
SetTextProperty();
}
private void RightClickMenu_Opened(object sender, EventArgs e)
{
SetMarkersContextMenuItem.Enabled =
SelectBetweenMarkersContextMenuItem.Enabled =
RemoveMarkersContextMenuItem.Enabled =
DeselectContextMenuItem.Enabled =
ClearContextMenuItem.Enabled =
DeleteFramesContextMenuItem.Enabled =
CloneContextMenuItem.Enabled =
InsertFrameContextMenuItem.Enabled =
InsertNumFramesContextMenuItem.Enabled =
TruncateContextMenuItem.Enabled =
TasView.SelectedRows.Any();
RemoveMarkersContextMenuItem.Enabled = _currentTasMovie.Markers.Any(m => TasView.SelectedRows.Contains(m.Frame)); // Disable the option to remove markers if no markers are selected (FCEUX does this).
}
#endregion
}
}