1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-06-25 05:29:31 +00:00

Port project loader code

Created a file to hold the non-WPF parts of ProjectView.cs.
Pulled some code related to project loading into it.  Created a few
related dialogs.
This commit is contained in:
Andy McFadden 2019-05-05 16:50:28 -07:00
parent c976b92f34
commit ce27ae720e
13 changed files with 1115 additions and 6 deletions

View File

@ -26,5 +26,7 @@ namespace SourceGenWPF {
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application {
public static readonly CommonUtil.Version ProgramVersion =
new CommonUtil.Version(1, 2, 0, CommonUtil.Version.PreRelType.Alpha, 1);
}
}

View File

@ -0,0 +1,779 @@
/*
* Copyright 2019 faddenSoft
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Windows;
using Asm65;
using CommonUtil;
using SourceGenWPF.ProjWin;
using System.Web.Script.Serialization;
namespace SourceGenWPF {
/// <summary>
/// This class manages user interaction. The goal is for this to be relatively
/// GUI-toolkit-agnostic, with all the WPF stuff tucked into the code-behind files. An
/// instance of this class is created by MainWindow when the app starts.
///
/// There is some Windows-specific stuff, like MessageBox and OpenFileDialog.
/// </summary>
public class MainController {
#region Project state
// Currently open project, or null if none.
private DisasmProject mProject;
// Pathname to 65xx data file.
private string mDataPathName;
// Pathname of .dis65 file. This will be empty for a new project.
private string mProjectPathName;
#if false
/// <summary>
/// Symbol subset, used to supply data to the symbol ListView. Initialized with
/// an empty symbol table.
/// </summary>
private SymbolTableSubset mSymbolSubset;
#endif
/// <summary>
/// Current code list view selection. The length will match the DisplayList Count.
///
/// A simple foreach through codeListView.SelectedIndices on a 500K-line data set
/// takes about 2.5 seconds on a fast Win10 x64 machine. Fortunately the control
/// notifies us of changes to the selection, so we can track it ourselves.
/// </summary>
private VirtualListViewSelection mCodeViewSelection = new VirtualListViewSelection();
/// <summary>
/// Data backing the codeListView.
/// </summary>
private DisplayList mDisplayList;
#endregion Project state
/// <summary>
/// List of recently-opened projects.
/// </summary>
private List<string> mRecentProjectPaths = new List<string>(MAX_RECENT_PROJECTS);
public const int MAX_RECENT_PROJECTS = 6;
/// <summary>
/// Activity log generated by the code and data analyzers. Displayed in window.
/// </summary>
private DebugLog mGenerationLog;
/// <summary>
/// Timing data generated during analysis.
/// </summary>
TaskTimer mReanalysisTimer = new TaskTimer();
/// <summary>
/// Stack for navigate forward/backward.
/// </summary>
private NavStack mNavStack = new NavStack();
/// <summary>
/// Output format configuration.
/// </summary>
private Formatter.FormatConfig mFormatterConfig;
/// <summary>
/// Output format controller.
///
/// This is shared with the DisplayList.
/// </summary>
private Formatter mOutputFormatter;
/// <summary>
/// Pseudo-op names.
///
/// This is shared with the DisplayList.
/// </summary>
private PseudoOp.PseudoOpNames mPseudoOpNames;
/// <summary>
/// String we most recently searched for.
/// </summary>
private string mFindString = string.Empty;
/// <summary>
/// Initial start point of most recent search.
/// </summary>
private int mFindStartIndex = -1;
/// <summary>
/// Used to highlight the line that is the target of the selected line.
/// </summary>
private int mTargetHighlightIndex = -1;
/// <summary>
/// CPU definition used when the Formatter was created. If the CPU choice or
/// inclusion of undocumented opcodes changes, we need to wipe the formatter.
/// </summary>
private CpuDef mOutputFormatterCpuDef;
/// <summary>
/// Instruction description object. Used for Info window.
/// </summary>
private OpDescription mOpDesc = OpDescription.GetOpDescription(null);
/// <summary>
/// If true, plugins will execute in the main application's AppDomain instead of
/// the sandbox.
/// </summary>
private bool mUseMainAppDomainForPlugins = false;
/// <summary>
/// Ensures that the named project is at the top of the list. If it's elsewhere
/// in the list, move it to the top. Excess items are removed.
/// </summary>
/// <param name="projectPath"></param>
private void UpdateRecentProjectList(string projectPath) {
if (string.IsNullOrEmpty(projectPath)) {
// This can happen if you create a new project, then close the window
// without having saved it.
return;
}
int index = mRecentProjectPaths.IndexOf(projectPath);
if (index == 0) {
// Already in the list, nothing changes. No need to update anything else.
return;
}
if (index > 0) {
mRecentProjectPaths.RemoveAt(index);
}
mRecentProjectPaths.Insert(0, projectPath);
// Trim the list to the max allowed.
while (mRecentProjectPaths.Count > MAX_RECENT_PROJECTS) {
Debug.WriteLine("Recent projects: dropping " +
mRecentProjectPaths[MAX_RECENT_PROJECTS]);
mRecentProjectPaths.RemoveAt(MAX_RECENT_PROJECTS);
}
// Store updated list in app settings. JSON-in-JSON is ugly and inefficient,
// but it'll do for now.
JavaScriptSerializer ser = new JavaScriptSerializer();
string cereal = ser.Serialize(mRecentProjectPaths);
AppSettings.Global.SetString(AppSettings.PRVW_RECENT_PROJECT_LIST, cereal);
#if false
UpdateRecentLinks();
#endif
}
public void OpenRecentProject(int projIndex) {
//if (DoClose()) {
// DoOpenFile(mRecentProjectPaths[projIndex]);
//}
}
/// <summary>
/// Handles opening an existing project by letting the user select the project file.
/// </summary>
private void DoOpen() {
if (!DoClose()) {
return;
}
OpenFileDialog fileDlg = new OpenFileDialog() {
Filter = ProjectFile.FILENAME_FILTER + "|" + Res.Strings.FILE_FILTER_ALL,
FilterIndex = 1
};
if (fileDlg.ShowDialog() != true) {
return;
}
string projPathName = Path.GetFullPath(fileDlg.FileName);
DoOpenFile(projPathName);
}
/// <summary>
/// Handles opening an existing project, given a pathname to the project file.
/// </summary>
private void DoOpenFile(string projPathName) {
Debug.WriteLine("DoOpenFile: " + projPathName);
Debug.Assert(mProject == null);
if (!File.Exists(projPathName)) {
string msg = string.Format(Res.Strings.ERR_FILE_NOT_FOUND_FMT, projPathName);
MessageBox.Show(msg, Res.Strings.ERR_FILE_GENERIC_CAPTION,
MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
DisasmProject newProject = new DisasmProject();
newProject.UseMainAppDomainForPlugins = mUseMainAppDomainForPlugins;
// Deserialize the project file. I want to do this before loading the data file
// in case we decide to store the data file name in the project (e.g. the data
// file is a disk image or zip archive, and we need to know which part(s) to
// extract).
if (!ProjectFile.DeserializeFromFile(projPathName, newProject,
out FileLoadReport report)) {
// Should probably use a less-busy dialog for something simple like
// "permission denied", but the open file dialog handles most simple
// stuff directly.
ProjectLoadIssues dlg = new ProjectLoadIssues(report.Format(),
ProjectLoadIssues.Buttons.Cancel);
dlg.ShowDialog();
// ignore dlg.DialogResult
return;
}
// Now open the data file, generating the pathname by stripping off the ".dis65"
// extension. If we can't find the file, show a message box and offer the option to
// locate it manually, repeating the process until successful or canceled.
const string UNKNOWN_FILE = "UNKNOWN";
string dataPathName;
if (projPathName.Length <= ProjectFile.FILENAME_EXT.Length) {
dataPathName = UNKNOWN_FILE;
} else {
dataPathName = projPathName.Substring(0,
projPathName.Length - ProjectFile.FILENAME_EXT.Length);
}
byte[] fileData;
while ((fileData = FindValidDataFile(ref dataPathName, newProject,
out bool cancel)) == null) {
if (cancel) {
// give up
Debug.WriteLine("Abandoning attempt to open project");
return;
}
}
// If there were warnings, notify the user and give the a chance to cancel.
if (report.Count != 0) {
ProjectLoadIssues dlg = new ProjectLoadIssues(report.Format(),
ProjectLoadIssues.Buttons.ContinueOrCancel);
bool? ok = dlg.ShowDialog();
if (ok != true) {
return;
}
}
mProject = newProject;
mProjectPathName = mProject.ProjectPathName = projPathName;
mProject.SetFileData(fileData, Path.GetFileName(dataPathName));
FinishPrep();
}
/// <summary>
/// Finds and loads the specified data file. The file's length and CRC must match
/// the project's expectations.
/// </summary>
/// <param name="dataPathName">Full path to file.</param>
/// <param name="proj">Project object.</param>
/// <param name="cancel">Returns true if we want to cancel the attempt.</param>
/// <returns></returns>
private byte[] FindValidDataFile(ref string dataPathName, DisasmProject proj,
out bool cancel) {
FileInfo fi = new FileInfo(dataPathName);
if (!fi.Exists) {
Debug.WriteLine("File '" + dataPathName + "' doesn't exist");
dataPathName = ChooseDataFile(dataPathName,
Res.Strings.OPEN_DATA_DOESNT_EXIST);
cancel = (dataPathName == null);
return null;
}
if (fi.Length != proj.FileDataLength) {
Debug.WriteLine("File '" + dataPathName + "' has length=" + fi.Length +
", expected " + proj.FileDataLength);
dataPathName = ChooseDataFile(dataPathName,
string.Format(Res.Strings.OPEN_DATA_WRONG_LENGTH_FMT,
fi.Length, proj.FileDataLength));
cancel = (dataPathName == null);
return null;
}
byte[] fileData = null;
try {
fileData = LoadDataFile(dataPathName);
} catch (Exception ex) {
Debug.WriteLine("File '" + dataPathName + "' failed to load: " + ex.Message);
dataPathName = ChooseDataFile(dataPathName,
string.Format(Res.Strings.OPEN_DATA_LOAD_FAILED_FMT, ex.Message));
cancel = (dataPathName == null);
return null;
}
uint crc = CRC32.OnWholeBuffer(0, fileData);
if (crc != proj.FileDataCrc32) {
Debug.WriteLine("File '" + dataPathName + "' has CRC32=" + crc +
", expected " + proj.FileDataCrc32);
// Format the CRC as signed decimal, so that interested parties can
// easily replace the value in the .dis65 file.
dataPathName = ChooseDataFile(dataPathName,
string.Format(Res.Strings.OPEN_DATA_WRONG_CRC_FMT,
(int)crc, (int)proj.FileDataCrc32));
cancel = (dataPathName == null);
return null;
}
cancel = false;
return fileData;
}
/// <summary>
/// Displays a "do you want to pick a different file" message, then (on OK) allows the
/// user to select a file.
/// </summary>
/// <param name="origPath">Pathname of original file.</param>
/// <param name="errorMsg">Message to display in the message box.</param>
/// <returns>Full path of file to open.</returns>
private string ChooseDataFile(string origPath, string errorMsg) {
DataFileLoadIssue dlg = new DataFileLoadIssue(origPath, errorMsg);
bool? ok = dlg.ShowDialog();
if (ok != true) {
return null;
}
OpenFileDialog fileDlg = new OpenFileDialog() {
FileName = Path.GetFileName(origPath),
Filter = Res.Strings.FILE_FILTER_ALL
};
if (fileDlg.ShowDialog() != true) {
return null;
}
string newPath = Path.GetFullPath(fileDlg.FileName);
Debug.WriteLine("User selected data file " + newPath);
return newPath;
}
private bool DoSaveAs() {
SaveFileDialog fileDlg = new SaveFileDialog() {
Filter = ProjectFile.FILENAME_FILTER + "|" + Res.Strings.FILE_FILTER_ALL,
FilterIndex = 1,
ValidateNames = true,
AddExtension = true,
FileName = Path.GetFileName(mDataPathName) + ProjectFile.FILENAME_EXT
};
if (fileDlg.ShowDialog() == true) {
string pathName = Path.GetFullPath(fileDlg.FileName);
Debug.WriteLine("Project save path: " + pathName);
if (DoSave(pathName)) {
// Success, record the path name.
mProjectPathName = mProject.ProjectPathName = pathName;
// add it to the title bar
#if false
UpdateMenuItemsAndTitle();
#endif
return true;
}
}
return false;
}
// Save the project. If it hasn't been saved before, use save-as behavior instead.
private bool DoSave() {
if (string.IsNullOrEmpty(mProjectPathName)) {
return DoSaveAs();
}
return DoSave(mProjectPathName);
}
private bool DoSave(string pathName) {
Debug.WriteLine("SAVING " + pathName);
if (!ProjectFile.SerializeToFile(mProject, pathName, out string errorMessage)) {
MessageBox.Show(Res.Strings.ERR_PROJECT_SAVE_FAIL + ": " + errorMessage,
Res.Strings.OPERATION_FAILED,
MessageBoxButton.OK, MessageBoxImage.Error);
return false;
}
mProject.ResetDirtyFlag();
#if false
// If the debug dialog is visible, update it.
if (mShowUndoRedoHistoryDialog != null) {
mShowUndoRedoHistoryDialog.BodyText = mProject.DebugGetUndoRedoHistory();
}
UpdateMenuItemsAndTitle();
#endif
// Update this, in case this was a new project.
UpdateRecentProjectList(pathName);
#if false
// Seems like a good time to save this off too.
SaveAppSettings();
#endif
return true;
}
/// <summary>
/// Closes the project and associated modeless dialogs. Unsaved changes will be
/// lost, so if the project has outstanding changes the user will be given the
/// opportunity to cancel.
/// </summary>
/// <returns>True if the project was closed, false if the user chose to cancel.</returns>
private bool DoClose() {
Debug.WriteLine("ProjectView.DoClose() - dirty=" +
(mProject == null ? "N/A" : mProject.IsDirty.ToString()));
if (mProject != null && mProject.IsDirty) {
DiscardChanges dlg = new DiscardChanges();
bool? ok = dlg.ShowDialog();
if (ok != true) {
return false;
} else if (dlg.UserChoice == DiscardChanges.Choice.SaveAndContinue) {
if (!DoSave()) {
return false;
}
}
}
#if false
// Close modeless dialogs that depend on project.
if (mShowUndoRedoHistoryDialog != null) {
mShowUndoRedoHistoryDialog.Close();
}
if (mShowAnalysisTimersDialog != null) {
mShowAnalysisTimersDialog.Close();
}
if (mShowAnalyzerOutputDialog != null) {
mShowAnalyzerOutputDialog.Close();
}
if (mHexDumpDialog != null) {
mHexDumpDialog.Close();
}
#endif
// Discard all project state.
if (mProject != null) {
mProject.Cleanup();
mProject = null;
}
mDataPathName = null;
mProjectPathName = null;
#if false
mSymbolSubset = new SymbolTableSubset(new SymbolTable());
mCodeViewSelection = new VirtualListViewSelection();
mDisplayList = null;
codeListView.VirtualListSize = 0;
//codeListView.Items.Clear();
ShowNoProject();
InvalidateControls(null);
#endif
mGenerationLog = null;
// Not necessary, but it lets us check the memory monitor to see if we got
// rid of everything.
GC.Collect();
return true;
}
#region Project management
private bool PrepareNewProject(string dataPathName, SystemDef sysDef) {
DisasmProject proj = new DisasmProject();
mDataPathName = dataPathName;
mProjectPathName = string.Empty;
byte[] fileData = null;
try {
fileData = LoadDataFile(dataPathName);
} catch (Exception ex) {
Debug.WriteLine("PrepareNewProject exception: " + ex);
string message = Res.Strings.OPEN_DATA_FAIL_CAPTION;
string caption = Res.Strings.OPEN_DATA_FAIL_MESSAGE + ": " + ex.Message;
MessageBox.Show(caption, message, MessageBoxButton.OK,
MessageBoxImage.Error);
return false;
}
proj.UseMainAppDomainForPlugins = mUseMainAppDomainForPlugins;
proj.Initialize(fileData.Length);
proj.PrepForNew(fileData, Path.GetFileName(dataPathName));
proj.LongComments.Add(DisplayList.Line.HEADER_COMMENT_OFFSET,
new MultiLineComment("6502bench SourceGen v" + App.ProgramVersion));
// The system definition provides a set of defaults that can be overridden.
// We pull everything of interest out and then discard the object.
proj.ApplySystemDef(sysDef);
mProject = proj;
return true;
}
private void FinishPrep() {
string messages = mProject.LoadExternalFiles();
if (messages.Length != 0) {
// ProjectLoadIssues isn't quite the right dialog, but it'll do.
ProjectLoadIssues dlg = new ProjectLoadIssues(messages,
ProjectLoadIssues.Buttons.Continue);
dlg.ShowDialog();
}
mDisplayList = new DisplayList(mProject, mOutputFormatter, mPseudoOpNames);
// Prep the symbol table subset object. Replace the old one with a new one.
//mSymbolSubset = new SymbolTableSubset(mProject.SymbolTable);
RefreshProject(UndoableChange.ReanalysisScope.CodeAndData);
//ShowProject();
//InvalidateControls(null);
// Want to do this after ShowProject() or we see a weird glitch.
UpdateRecentProjectList(mProjectPathName);
}
/// <summary>
/// Loads the data file, reading it entirely into memory.
///
/// All errors are reported as exceptions.
/// </summary>
/// <param name="dataFileName">Full pathname.</param>
/// <returns>Data file contents.</returns>
private byte[] LoadDataFile(string dataFileName) {
byte[] fileData;
using (FileStream fs = File.Open(dataFileName, FileMode.Open, FileAccess.Read)) {
// Check length; should have been caught earlier.
if (fs.Length > DisasmProject.MAX_DATA_FILE_SIZE) {
throw new InvalidDataException(
string.Format(Res.Strings.OPEN_DATA_TOO_LARGE_FMT,
fs.Length / 1024, DisasmProject.MAX_DATA_FILE_SIZE / 1024));
} else if (fs.Length == 0) {
throw new InvalidDataException(Res.Strings.OPEN_DATA_EMPTY);
}
fileData = new byte[fs.Length];
int actual = fs.Read(fileData, 0, (int)fs.Length);
if (actual != fs.Length) {
// Not expected -- should be able to read the entire file in one shot.
throw new Exception(Res.Strings.OPEN_DATA_PARTIAL_READ);
}
}
return fileData;
}
/// <summary>
/// Applies the changes to the project, adds them to the undo stack, and updates
/// the display.
/// </summary>
/// <param name="cs">Set of changes to apply.</param>
private void ApplyUndoableChanges(ChangeSet cs) {
if (cs.Count == 0) {
Debug.WriteLine("ApplyUndoableChanges: change set is empty");
}
ApplyChanges(cs, false);
mProject.PushChangeSet(cs);
#if false
UpdateMenuItemsAndTitle();
// If the debug dialog is visible, update it.
if (mShowUndoRedoHistoryDialog != null) {
mShowUndoRedoHistoryDialog.BodyText = mProject.DebugGetUndoRedoHistory();
}
#endif
}
/// <summary>
/// Applies the changes to the project, and updates the display.
///
/// This is called by the undo/redo commands. Don't call this directly from the
/// various UI-driven functions, as this does not add the change to the undo stack.
/// </summary>
/// <param name="cs">Set of changes to apply.</param>
/// <param name="backward">If set, undo the changes instead.</param>
private void ApplyChanges(ChangeSet cs, bool backward) {
mReanalysisTimer.Clear();
mReanalysisTimer.StartTask("ProjectView.ApplyChanges()");
mReanalysisTimer.StartTask("Save selection");
#if false
int topItem = codeListView.TopItem.Index;
#else
int topItem = 0;
#endif
int topOffset = mDisplayList[topItem].FileOffset;
DisplayList.SavedSelection savedSel = DisplayList.SavedSelection.Generate(
mDisplayList, mCodeViewSelection, topOffset);
//savedSel.DebugDump();
mReanalysisTimer.EndTask("Save selection");
mReanalysisTimer.StartTask("Apply changes");
UndoableChange.ReanalysisScope needReanalysis = mProject.ApplyChanges(cs, backward,
out RangeSet affectedOffsets);
mReanalysisTimer.EndTask("Apply changes");
string refreshTaskStr = "Refresh w/reanalysis=" + needReanalysis;
mReanalysisTimer.StartTask(refreshTaskStr);
if (needReanalysis != UndoableChange.ReanalysisScope.None) {
Debug.WriteLine("Refreshing project (" + needReanalysis + ")");
RefreshProject(needReanalysis);
} else {
Debug.WriteLine("Refreshing " + affectedOffsets.Count + " offsets");
RefreshCodeListViewEntries(affectedOffsets);
mProject.Validate(); // shouldn't matter w/o reanalysis, but do it anyway
}
mReanalysisTimer.EndTask(refreshTaskStr);
VirtualListViewSelection newSel = savedSel.Restore(mDisplayList, out int topIndex);
//newSel.DebugDump();
// Refresh the various windows, and restore the selection.
mReanalysisTimer.StartTask("Invalidate controls");
#if false
InvalidateControls(newSel);
#endif
mReanalysisTimer.EndTask("Invalidate controls");
// This apparently has to be done after the EndUpdate, and inside try/catch.
// See https://stackoverflow.com/questions/626315/ for notes.
try {
Debug.WriteLine("Setting TopItem to index=" + topIndex);
#if false
codeListView.TopItem = codeListView.Items[topIndex];
#endif
} catch (NullReferenceException) {
Debug.WriteLine("Caught an NRE from TopItem");
}
mReanalysisTimer.EndTask("ProjectView.ApplyChanges()");
//mReanalysisTimer.DumpTimes("ProjectView timers:", mGenerationLog);
#if false
if (mShowAnalysisTimersDialog != null) {
string timerStr = mReanalysisTimer.DumpToString("ProjectView timers:");
mShowAnalysisTimersDialog.BodyText = timerStr;
}
#endif
// Lines may have moved around. Update the selection highlight. It's important
// we do it here, and not down in DoRefreshProject(), because at that point the
// ListView's selection index could be referencing a line off the end.
#if false
UpdateSelectionHighlight();
#endif
}
/// <summary>
/// Refreshes the project after something of substance has changed. Some
/// re-analysis will be done, followed by a complete rebuild of the DisplayList.
/// </summary>
/// <param name="reanalysisRequired">Indicates whether reanalysis is required, and
/// what level.</param>
private void RefreshProject(UndoableChange.ReanalysisScope reanalysisRequired) {
Debug.Assert(reanalysisRequired != UndoableChange.ReanalysisScope.None);
// NOTE: my goal is to arrange things so that reanalysis (data-only, and ideally
// code+data) takes less than 100ms. With that response time there's no need for
// background processing and progress bars. Since we need to do data-only
// reanalysis after many common operations, the program becomes unpleasant to
// use if we miss this goal, and progress bars won't make it less so.
// Changing the CPU type or whether undocumented instructions are supported
// invalidates the Formatter's mnemonic cache. We can change these values
// through undo/redo, so we need to check it here.
if (mOutputFormatterCpuDef != mProject.CpuDef) { // reference equality is fine
Debug.WriteLine("CpuDef has changed, resetting formatter (now " +
mProject.CpuDef + ")");
mOutputFormatter = new Formatter(mFormatterConfig);
mDisplayList.SetFormatter(mOutputFormatter);
mDisplayList.SetPseudoOpNames(mPseudoOpNames);
mOutputFormatterCpuDef = mProject.CpuDef;
}
#if false
if (mDisplayList.Count > 200000) {
string prevStatus = toolStripStatusLabel.Text;
// The Windows stuff can take 50-100ms, potentially longer than the actual
// work, so don't bother unless the file is very large.
try {
mReanalysisTimer.StartTask("Do Windows stuff");
Application.UseWaitCursor = true;
Cursor.Current = Cursors.WaitCursor;
toolStripStatusLabel.Text = Res.Strings.STATUS_RECALCULATING;
Refresh(); // redraw status label
mReanalysisTimer.EndTask("Do Windows stuff");
DoRefreshProject(reanalysisRequired);
} finally {
Application.UseWaitCursor = false;
toolStripStatusLabel.Text = prevStatus;
}
} else {
DoRefreshProject(reanalysisRequired);
}
#endif
if (FormatDescriptor.DebugCreateCount != 0) {
Debug.WriteLine("FormatDescriptor total=" + FormatDescriptor.DebugCreateCount +
" prefab=" + FormatDescriptor.DebugPrefabCount + " (" +
(FormatDescriptor.DebugPrefabCount * 100) / FormatDescriptor.DebugCreateCount +
"%)");
}
}
/// <summary>
/// Updates all of the specified ListView entries. This is called after minor changes,
/// such as editing a comment or renaming a label, that can be handled by regenerating
/// selected parts of the DisplayList.
/// </summary>
/// <param name="offsetSet"></param>
private void RefreshCodeListViewEntries(RangeSet offsetSet) {
IEnumerator<RangeSet.Range> iter = offsetSet.RangeListIterator;
while (iter.MoveNext()) {
RangeSet.Range range = iter.Current;
mDisplayList.GenerateRange(range.Low, range.High);
}
}
private void DoRefreshProject(UndoableChange.ReanalysisScope reanalysisRequired) {
if (reanalysisRequired != UndoableChange.ReanalysisScope.DisplayOnly) {
mGenerationLog = new CommonUtil.DebugLog();
mGenerationLog.SetMinPriority(CommonUtil.DebugLog.Priority.Debug);
mGenerationLog.SetShowRelTime(true);
mReanalysisTimer.StartTask("Call DisasmProject.Analyze()");
mProject.Analyze(reanalysisRequired, mGenerationLog, mReanalysisTimer);
mReanalysisTimer.EndTask("Call DisasmProject.Analyze()");
}
if (mGenerationLog != null) {
//mReanalysisTimer.StartTask("Save _log");
//mGenerationLog.WriteToFile(@"C:\Src\WorkBench\SourceGen\TestData\_log.txt");
//mReanalysisTimer.EndTask("Save _log");
#if false
if (mShowAnalyzerOutputDialog != null) {
mShowAnalyzerOutputDialog.BodyText = mGenerationLog.WriteToString();
}
#endif
}
mReanalysisTimer.StartTask("Generate DisplayList");
mDisplayList.GenerateAll();
mReanalysisTimer.EndTask("Generate DisplayList");
}
#endregion Project management
}
}

View File

@ -0,0 +1,26 @@
<Window x:Class="SourceGenWPF.ProjWin.DataFileLoadIssue"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SourceGenWPF.ProjWin"
mc:Ignorable="d"
Title="Data File Load Issue" ShowInTaskbar="False"
Width="570" Height="143" ResizeMode="NoResize" WindowStartupLocation="CenterOwner">
<DockPanel Margin="8" LastChildFill="False">
<TextBlock DockPanel.Dock="Top">There was an error while loading the data file:</TextBlock>
<TextBox Name="pathNameTextBox" DockPanel.Dock="Top" Margin="0,8,0,0" IsReadOnly="True" MaxLines="1" Text="(filename)"/>
<TextBlock Name="problemLabel" DockPanel.Dock="Top" Margin="0,8,0,0" Text="(reason)"/>
<Grid DockPanel.Dock="Bottom" Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Do you want to locate the data file?"/>
<Button Grid.Column="1" Content="OK" Width="75" IsDefault="True"/>
<Button Grid.Column="2" Content="Cancel" Width="75" Margin="8,0,0,0" IsCancel="True"/>
</Grid>
</DockPanel>
</Window>

View File

@ -0,0 +1,47 @@
/*
* Copyright 2018 faddenSoft
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Windows;
namespace SourceGenWPF.ProjWin {
/// <summary>
/// Report a problem encountered while loading a data file.
/// </summary>
public partial class DataFileLoadIssue : Window {
/// <summary>
/// Path name of problematic file.
/// </summary>
private string mPathName;
/// <summary>
/// Message to show in the dialog.
/// </summary>
private string mMessage;
public DataFileLoadIssue(string pathName, string message) {
InitializeComponent();
mPathName = pathName;
mMessage = message;
}
private void DataFileLoadIssue_Load(object sender, EventArgs e) {
pathNameTextBox.Text = mPathName;
problemLabel.Text = mMessage;
}
}
}

View File

@ -0,0 +1,19 @@
<Window x:Class="SourceGenWPF.ProjWin.DiscardChanges"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SourceGenWPF.ProjWin"
mc:Ignorable="d"
Title="Discard Changes?" ShowInTaskbar="False"
Width="398" Height="125" ResizeMode="NoResize" WindowStartupLocation="CenterOwner">
<StackPanel Margin="8">
<TextBlock Text="You have unsaved changes that will be lost if you continue."/>
<TextBlock Margin="0,8,0,0" Text="How do you wish to proceed?"/>
<StackPanel Margin="0,16,0,0" Orientation="Horizontal">
<Button Name="dontSaveButton" Width="120" Content="_Save &amp; Continue" Click="DontSaveButton_Click"/>
<Button Name="saveButton" Width="120" Margin="8,0,0,0" Content="_Discard &amp; Continue" Click="SaveButton_Click"/>
<Button Name="cancelButton" Width="120" Margin="8,0,0,0" Content="Cancel" IsCancel="True"/>
</StackPanel>
</StackPanel>
</Window>

View File

@ -0,0 +1,52 @@
/*
* Copyright 2019 faddenSoft
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Windows;
namespace SourceGenWPF.ProjWin {
/// <summary>
/// Prompt the user before discarding changes.
///
/// Dialog result will be false if the user cancels out. Otherwise, the result will be
/// true, with the selected option in UserChoice.
/// </summary>
public partial class DiscardChanges : Window {
public enum Choice {
Unknown = 0,
SaveAndContinue,
DiscardAndContinue
}
public Choice UserChoice { get; private set; }
public DiscardChanges() {
InitializeComponent();
}
// TODO:
// https://stackoverflow.com/questions/817610/wpf-and-initial-focus
// FocusManager.FocusedElement={Binding ElementName=cancelButton}"
private void SaveButton_Click(object sender, RoutedEventArgs e) {
UserChoice = Choice.SaveAndContinue;
DialogResult = true;
}
private void DontSaveButton_Click(object sender, RoutedEventArgs e) {
UserChoice = Choice.DiscardAndContinue;
DialogResult = true;
}
}
}

View File

@ -30,10 +30,12 @@ limitations under the License.
<KeyGesture>Ctrl+Shift+A</KeyGesture>
</RoutedUICommand.InputGestures>
</RoutedUICommand>
<RoutedUICommand x:Key="RecentProject"/>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="{StaticResource AssembleCmd}" Executed="AssembleCmd_Executed"/>
<CommandBinding Command="{StaticResource RecentProject}" Executed="RecentProject_Executed"/>
</Window.CommandBindings>
<DockPanel>
@ -196,8 +198,12 @@ limitations under the License.
<StackPanel Grid.Row="1" HorizontalAlignment="Left">
<Button Content="Start new project" Width="200" Height="50" Margin="10,30,10,10"/>
<Button Content="Open existing project" Width="200" Height="50" Margin="10"/>
<Button Content="Recent project #1" Width="200" Height="50" Margin="10"/>
<Button Content="Recent project #2" Width="200" Height="50" Margin="10"/>
<Button Content="Recent project #1" Width="200" Height="50" Margin="10"
CommandParameter="1"
Command="{DynamicResource RecentProject}"/>
<Button Content="Recent project #2" Width="200" Height="50" Margin="10"
CommandParameter="2"
Command="{DynamicResource RecentProject}"/>
</StackPanel>
</Grid>

View File

@ -33,19 +33,32 @@ namespace SourceGenWPF.ProjWin {
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public static readonly CommonUtil.Version ProgramVersion =
new CommonUtil.Version(1, 2, 0, CommonUtil.Version.PreRelType.Alpha, 1);
public string ProgramVersionString {
get { return ProgramVersion.ToString(); }
get { return App.ProgramVersion.ToString(); }
}
private MainController mUI;
public MainWindow() {
InitializeComponent();
mUI = new MainController();
}
private void AssembleCmd_Executed(object sender, ExecutedRoutedEventArgs e) {
// test
Debug.WriteLine("assembling");
}
private void RecentProject_Executed(object sender, ExecutedRoutedEventArgs e) {
if (!int.TryParse((string)e.Parameter, out int recentIndex) ||
recentIndex < 1 || recentIndex > MainController.MAX_RECENT_PROJECTS) {
throw new Exception("Bad parameter: " + e.Parameter);
}
recentIndex--;
Debug.WriteLine("Recent project #" + recentIndex);
mUI.OpenRecentProject(recentIndex);
}
}
}

View File

@ -0,0 +1,24 @@
<Window x:Class="SourceGenWPF.ProjWin.ProjectLoadIssues"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SourceGenWPF.ProjWin"
mc:Ignorable="d"
Title="Project Load Issues" ShowInTaskbar="False"
Width="570" Height="231" ResizeMode="NoResize" WindowStartupLocation="CenterOwner"
Loaded="ProjectLoadIssues_Loaded">
<DockPanel LastChildFill="False" Margin="8">
<TextBlock DockPanel.Dock="Top">Problems were detected while loading the project file:</TextBlock>
<TextBox Name="messageTextBox" DockPanel.Dock="Top" Margin="0,8,0,0" Height="107"
IsReadOnly="True" TextWrapping="Wrap" Text=""/>
<TextBlock Name="invalidDiscardLabel" DockPanel.Dock="Top" Margin="0,8,0,0" Foreground="Red">
Invalid data items will be discarded when you save the project.
</TextBlock>
<StackPanel DockPanel.Dock="Bottom" Margin="0,8,0,0"
Orientation="Horizontal" HorizontalAlignment="Right">
<Button Name="okButton" Width="75" Content="Continue" IsDefault="True"/>
<Button Name="cancelButton" Margin="8,0,0,0" Width="75" Content="Cancel" IsCancel="True"/>
</StackPanel>
</DockPanel>
</Window>

View File

@ -0,0 +1,65 @@
/*
* Copyright 2019 faddenSoft
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Windows;
namespace SourceGenWPF.ProjWin {
/// <summary>
/// Display errors and warnings generated while attempting to open a project.
/// </summary>
public partial class ProjectLoadIssues : Window {
/// <summary>
/// Multi-line message for text box.
/// </summary>
private string mMessages;
/// <summary>
/// Which buttons are enabled.
/// </summary>
private Buttons mAllowedButtons;
public enum Buttons {
Unknown = 0, Continue, Cancel, ContinueOrCancel
}
public ProjectLoadIssues(string msgs, Buttons allowedButtons) {
InitializeComponent();
mMessages = msgs;
mAllowedButtons = allowedButtons;
}
private void ProjectLoadIssues_Loaded(object sender, RoutedEventArgs e) {
messageTextBox.Text = mMessages;
if (mAllowedButtons == Buttons.Cancel) {
// Continue not allowed
okButton.IsEnabled = false;
// No point warning them about invalid data if they can't continue.
invalidDiscardLabel.Visibility = Visibility.Hidden;
}
if (mAllowedButtons == Buttons.Continue) {
// Cancel not allowed.
cancelButton.IsEnabled = false;
// They're stuck with the problem.
invalidDiscardLabel.Visibility = Visibility.Hidden;
}
}
}
}

View File

@ -13,6 +13,7 @@
<system:String x:Key="str_ErrBadTypeHint">Type hint not recognized</system:String>
<system:String x:Key="str_ErrDuplicateLabelFmt">Removing duplicate label '{0}' (offset +{1:x6})</system:String>
<system:String x:Key="str_ErrFileExistsNotDirFmt">The file {0} exists, but is not a directory.</system:String>
<system:String x:Key="str_ErrFileGenericCaption">File Error</system:String>
<system:String x:Key="str_ErrFileNotFoundFmt">File not found: {0}</system:String>
<system:String x:Key="str_ErrFileReadOnlyFmt">Cannot write to read-only file {0}.</system:String>
<system:String x:Key="str_ErrInvalidIntValue">Could not convert value to integer</system:String>
@ -20,12 +21,24 @@
<system:String x:Key="str_ErrNotProjectFile">This does not appear to be a valid .dis65 project file</system:String>
<system:String x:Key="str_ErrProjectFileCorrupt">Project file may be corrupt</system:String>
<system:String x:Key="str_ErrProjectLoadFail">Unable to load project file</system:String>
<system:String x:Key="str_ErrProjectSaveFail">Unable to save project file</system:String>
<system:String x:Key="str_FileFilterAll">All files (*.*)|*.*</system:String>
<system:String x:Key="str_FileFilterCs">C# Source Files(*.cs)|*.cs</system:String>
<system:String x:Key="str_FileFilterDis65">SourceGen projects(*.dis65)|*.dis65</system:String>
<system:String x:Key="str_FileFilterSym65">SourceGen symbols (*.sym65)|*.sym65</system:String>
<system:String x:Key="str_InitialExtensionScripts">Extension scripts:</system:String>
<system:String x:Key="str_InitialParameters">Default settings:</system:String>
<system:String x:Key="str_InitialSymbolFiles">Symbol files:</system:String>
<system:String x:Key="str_OpenDataDoesntExist">The file doesn't exist.</system:String>
<system:String x:Key="str_OpenDataEmpty">File is empty</system:String>
<system:String x:Key="str_OpenDataFailCaption">Unable to load data file</system:String>
<system:String x:Key="str_OpenDataFailMessage">Unable to load contents of data file</system:String>
<system:String x:Key="str_OpenDataLoadFailedFmt">The file could not be opened: {0}.</system:String>
<system:String x:Key="str_OpenDataPartialRead">Unable to read the entire file</system:String>
<system:String x:Key="str_OpenDataTooLargeFmt">File is too large ({0:N0} KiB, {1:N0} KiB max).</system:String>
<system:String x:Key="str_OpenDataWrongLengthFmt">The file is {0:N0} bytes long, but the project expected {1:N0}.</system:String>
<system:String x:Key="str_OpenDataWrongCrcFmt">The file has CRC {0}, but the project expected {1}.</system:String>
<system:String x:Key="str_OperationFailed">Failed</system:String>
<system:String x:Key="str_ProjectFieldComment">comment</system:String>
<system:String x:Key="str_ProjectFieldLongComment">long comment</system:String>
<system:String x:Key="str_ProjectFieldNote">note</system:String>

View File

@ -1,4 +1,19 @@
using System;
/*
* Copyright 2019 faddenSoft
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Windows;
namespace SourceGenWPF.Res {
@ -23,6 +38,8 @@ namespace SourceGenWPF.Res {
(string)Application.Current.FindResource("str_ErrDuplicateLabelFmt");
public static string ERR_FILE_EXISTS_NOT_DIR_FMT =
(string)Application.Current.FindResource("str_ErrFileExistsNotDirFmt");
public static string ERR_FILE_GENERIC_CAPTION =
(string)Application.Current.FindResource("str_ErrFileGenericCaption");
public static string ERR_FILE_NOT_FOUND_FMT =
(string)Application.Current.FindResource("str_ErrFileNotFoundFmt");
public static string ERR_FILE_READ_ONLY_FMT =
@ -37,6 +54,10 @@ namespace SourceGenWPF.Res {
(string)Application.Current.FindResource("str_ErrProjectFileCorrupt");
public static string ERR_PROJECT_LOAD_FAIL =
(string)Application.Current.FindResource("str_ErrProjectLoadFail");
public static string ERR_PROJECT_SAVE_FAIL =
(string)Application.Current.FindResource("str_ErrProjectSaveFail");
public static string FILE_FILTER_ALL =
(string)Application.Current.FindResource("str_FileFilterAll");
public static string FILE_FILTER_CS =
(string)Application.Current.FindResource("str_FileFilterCs");
public static string FILE_FILTER_DIS65 =
@ -49,6 +70,26 @@ namespace SourceGenWPF.Res {
(string)Application.Current.FindResource("str_InitialParameters");
public static string INITIAL_SYMBOL_FILES =
(string)Application.Current.FindResource("str_InitialSymbolFiles");
public static string OPEN_DATA_DOESNT_EXIST =
(string)Application.Current.FindResource("str_OpenDataDoesntExist");
public static string OPEN_DATA_EMPTY =
(string)Application.Current.FindResource("str_OpenDataEmpty");
public static string OPEN_DATA_FAIL_CAPTION =
(string)Application.Current.FindResource("str_OpenDataFailCaption");
public static string OPEN_DATA_FAIL_MESSAGE =
(string)Application.Current.FindResource("str_OpenDataFailMessage");
public static string OPEN_DATA_PARTIAL_READ =
(string)Application.Current.FindResource("str_OpenDataPartialRead");
public static string OPEN_DATA_LOAD_FAILED_FMT =
(string)Application.Current.FindResource("str_OpenDataLoadFailedFmt");
public static string OPEN_DATA_TOO_LARGE_FMT =
(string)Application.Current.FindResource("str_OpenDataTooLargeFmt");
public static string OPEN_DATA_WRONG_CRC_FMT =
(string)Application.Current.FindResource("str_OpenDataWrongCrcFmt");
public static string OPEN_DATA_WRONG_LENGTH_FMT =
(string)Application.Current.FindResource("str_OpenDataWrongLengthFmt");
public static string OPERATION_FAILED =
(string)Application.Current.FindResource("str_OperationFailed");
public static string PROJECT_FIELD_COMMENT =
(string)Application.Current.FindResource("str_ProjectFieldComment");
public static string PROJECT_FIELD_LONG_COMMENT =

View File

@ -62,6 +62,16 @@
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="MainController.cs" />
<Compile Include="ProjWin\DataFileLoadIssue.xaml.cs">
<DependentUpon>DataFileLoadIssue.xaml</DependentUpon>
</Compile>
<Compile Include="ProjWin\DiscardChanges.xaml.cs">
<DependentUpon>DiscardChanges.xaml</DependentUpon>
</Compile>
<Compile Include="ProjWin\ProjectLoadIssue.xaml.cs">
<DependentUpon>ProjectLoadIssue.xaml</DependentUpon>
</Compile>
<Compile Include="PseudoOp.cs" />
<Compile Include="Res\Strings.xaml.cs" />
<Compile Include="RuntimeDataAccess.cs" />
@ -127,10 +137,22 @@
<Resource Include="Res\Logo.png" />
</ItemGroup>
<ItemGroup>
<Page Include="ProjWin\DataFileLoadIssue.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="ProjWin\DiscardChanges.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="ProjWin\MainWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="ProjWin\ProjectLoadIssue.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Res\Strings.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>