Finish basic visualization editor implementation

The Visualization and Visualization Set editors are now fully
functional.  You can create, edit, and rearrange sets, and they're
now stored in the project file.
This commit is contained in:
Andy McFadden 2019-12-02 16:38:32 -08:00
parent 365864ccdf
commit 125080dbda
16 changed files with 480 additions and 132 deletions

BIN
ImageSrc/RedX.xcf Normal file

Binary file not shown.

View File

@ -449,9 +449,21 @@ namespace SourceGen {
return parts;
}
public static FormattedParts CreateVisualizationSet(string summary) {
public static FormattedParts CreateVisualizationSet(VisualizationSet visSet) {
FormattedParts parts = new FormattedParts();
parts.Comment = summary;
if (visSet.Count == 0) {
// not expected
parts.Comment = "!EMPTY!";
} else {
string fmt;
if (visSet.Count == 1) {
fmt = Res.Strings.VIS_SET_SINGLE_FMT;
} else {
fmt = Res.Strings.VIS_SET_MULTIPLE_FMT;
}
parts.Comment = string.Format(fmt, visSet[0].Tag, visSet.Count - 1);
}
// TODO(xyzzy): show image thumbnails
parts.IsVisualizationSet = true;
return parts;
}

View File

@ -506,11 +506,9 @@ namespace SourceGen {
parts = GenerateLvTableLine(line.FileOffset, line.SubLineIndex);
break;
case Line.Type.VisualizationSet:
// TODO(xyzzy)
mProject.VisualizationSets.TryGetValue(line.FileOffset,
out VisualizationSet visSet);
parts = FormattedParts.CreateVisualizationSet("!VISUALIZATION SET! " +
(visSet != null ? "VS:" + visSet.Count : "???"));
parts = FormattedParts.CreateVisualizationSet(visSet);
break;
case Line.Type.Blank:
// Nothing to do.

View File

@ -2161,7 +2161,7 @@ namespace SourceGen {
mProject.VisualizationSets.TryGetValue(offset, out VisualizationSet curVisSet);
EditVisualizationSet dlg = new EditVisualizationSet(mMainWin, mProject,
mOutputFormatter, curVisSet);
mOutputFormatter, curVisSet, offset);
if (dlg.ShowDialog() != true) {
return;
}

View File

@ -359,6 +359,29 @@ namespace SourceGen {
ClearPrevious = varTab.ClearPrevious;
}
}
public class SerVisualization {
public string Tag { get; set; }
public string VisGenIdent { get; set; }
public Dictionary<string, object> VisGenParams { get; set; }
public SerVisualization() { }
public SerVisualization(Visualization vis) {
Tag = vis.Tag;
VisGenIdent = vis.VisGenIdent;
VisGenParams = vis.VisGenParams; // Dictionary
}
}
public class SerVisualizationSet {
public List<SerVisualization> Items { get; set; }
public SerVisualizationSet() { }
public SerVisualizationSet(VisualizationSet visSet) {
Items = new List<SerVisualization>(visSet.Count);
foreach (Visualization vis in visSet) {
Items.Add(new SerVisualization(vis));
}
}
}
// Fields are serialized to/from JSON. Do not change the field names.
public int _ContentVersion { get; set; }
@ -374,6 +397,7 @@ namespace SourceGen {
public Dictionary<string, SerSymbol> UserLabels { get; set; }
public Dictionary<string, SerFormatDescriptor> OperandFormats { get; set; }
public Dictionary<string, SerLocalVariableTable> LvTables { get; set; }
public Dictionary<string, SerVisualizationSet> VisualizationSets { get; set; }
/// <summary>
/// Serializes a DisasmProject into an augmented JSON string.
@ -463,6 +487,11 @@ namespace SourceGen {
spf.LvTables.Add(kvp.Key.ToString(), new SerLocalVariableTable(kvp.Value));
}
spf.VisualizationSets = new Dictionary<string, SerVisualizationSet>();
foreach (KeyValuePair<int, VisualizationSet> kvp in proj.VisualizationSets) {
spf.VisualizationSets.Add(kvp.Key.ToString(), new SerVisualizationSet(kvp.Value));
}
spf.ProjectProps = new SerProjectProperties(proj.ProjectProps);
JavaScriptSerializer ser = new JavaScriptSerializer();
@ -714,6 +743,25 @@ namespace SourceGen {
}
}
// Deserialize visualization sets. These were added in v1.5.
if (spf.VisualizationSets != null) {
foreach (KeyValuePair<string, SerVisualizationSet> kvp in spf.VisualizationSets) {
if (!ParseValidateKey(kvp.Key, spf.FileDataLength,
Res.Strings.PROJECT_FIELD_LV_TABLE, report, out int intKey)) {
continue;
}
if (!CreateVisualizationSet(kvp.Value, report, out VisualizationSet visSet)) {
report.Add(FileLoadItem.Type.Warning,
string.Format(Res.Strings.ERR_BAD_VISUALIZATION_SET_FMT, intKey));
continue;
}
proj.VisualizationSets[intKey] = visSet;
}
}
return true;
}
@ -924,6 +972,42 @@ namespace SourceGen {
return true;
}
private static bool CreateVisualizationSet(SerVisualizationSet serVisSet,
FileLoadReport report, out VisualizationSet outVisSet) {
outVisSet = new VisualizationSet();
foreach (SerVisualization serVis in serVisSet.Items) {
string trimTag = Visualization.TrimAndValidateTag(serVis.Tag, out bool isTagValid);
if (!isTagValid) {
Debug.WriteLine("Visualization with invalid tag: " + serVis.Tag);
string str = string.Format(Res.Strings.ERR_BAD_VISUALIZATION_FMT, trimTag);
report.Add(FileLoadItem.Type.Warning, str);
continue;
}
if (string.IsNullOrEmpty(serVis.VisGenIdent) || serVis.VisGenParams == null) {
string str = string.Format(Res.Strings.ERR_BAD_VISUALIZATION_FMT,
"ident/params");
report.Add(FileLoadItem.Type.Warning, str);
continue;
}
// The JavaScript deserialization turns floats into Decimal. Change it back
// so we don't have to deal with it later.
Dictionary<string, object> parms =
new Dictionary<string, object>(serVis.VisGenParams.Count);
foreach (KeyValuePair<string, object> kvp in serVis.VisGenParams) {
object val = kvp.Value;
if (val is decimal) {
val = (double)((decimal)val);
}
parms.Add(kvp.Key, val);
}
Visualization vis = new Visualization(serVis.Tag, serVis.VisGenIdent, parms);
outVisSet.Add(vis);
}
return true;
}
/// <summary>
/// Parses an integer key that was stored as a string, and checks to see if the
/// value falls within an acceptable range.

BIN
SourceGen/Res/RedX.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B

View File

@ -54,6 +54,8 @@ limitations under the License.
<system:String x:Key="str_ErrBadSymbolSt">Unknown Source or Type in symbol</system:String>
<system:String x:Key="str_ErrBadSymrefPart">Bad symbol reference part</system:String>
<system:String x:Key="str_ErrBadTypeHint">Type hint not recognized</system:String>
<system:String x:Key="str_ErrBadVisualizationFmt">Invalid visualization item: {0}</system:String>
<system:String x:Key="str_ErrBadVisualizationSetFmt">Invalid visualization set at +{0:x6}</system:String>
<system:String x:Key="str_ErrDuplicateLabelFmt">Removed duplicate label '{0}' (offset +{1:x6})</system:String>
<system:String x:Key="str_ErrFileCopyFailedFmt">Failed copying {0} to {1}: {2}.</system:String>
<system:String x:Key="str_ErrFileExistsNotDirFmt">The file {0} exists, but is not a directory.</system:String>
@ -173,4 +175,6 @@ limitations under the License.
<system:String x:Key="str_TitleNewProject">[new project]</system:String>
<system:String x:Key="str_TitleReadOnly">*READ-ONLY*</system:String>
<system:String x:Key="str_Unset">[unset]</system:String>
<system:String x:Key="str_VisSetMultipleFmt">Vis: {0} (+{1} more)</system:String>
<system:String x:Key="str_VisSetSingleFmt">Vis: {0}</system:String>
</ResourceDictionary>

View File

@ -89,6 +89,10 @@ namespace SourceGen.Res {
(string)Application.Current.FindResource("str_ErrBadSymrefPart");
public static string ERR_BAD_TYPE_HINT =
(string)Application.Current.FindResource("str_ErrBadTypeHint");
public static string ERR_BAD_VISUALIZATION_FMT =
(string)Application.Current.FindResource("str_ErrBadVisualizationFmt");
public static string ERR_BAD_VISUALIZATION_SET_FMT =
(string)Application.Current.FindResource("str_ErrBadVisualizationSetFmt");
public static string ERR_DUPLICATE_LABEL_FMT =
(string)Application.Current.FindResource("str_ErrDuplicateLabelFmt");
public static string ERR_FILE_COPY_FAILED_FMT =
@ -327,5 +331,9 @@ namespace SourceGen.Res {
(string)Application.Current.FindResource("str_TitleReadOnly");
public static string UNSET =
(string)Application.Current.FindResource("str_Unset");
public static string VIS_SET_MULTIPLE_FMT =
(string)Application.Current.FindResource("str_VisSetMultipleFmt");
public static string VIS_SET_SINGLE_FMT =
(string)Application.Current.FindResource("str_VisSetSingleFmt");
}
}

View File

@ -29,9 +29,9 @@ namespace RuntimeData.Apple {
private byte[] mFileData;
private AddressTranslate mAddrTrans;
// Visualization identifiers; DO NOT change or projects will break.
// Visualization identifiers; DO NOT change or projects that use them will break.
private const string VIS_GEN_BITMAP = "apple2-hi-res-bitmap";
private const string VIS_GEN_MULTI_MAP = "apple2-hi-res-multi-map";
private const string VIS_GEN_BITMAP_FONT = "apple2-hi-res-bitmap-font";
private const string P_OFFSET = "offset";
private const string P_BYTE_WIDTH = "byteWidth";
@ -52,8 +52,7 @@ namespace RuntimeData.Apple {
new VisDescr(VIS_GEN_BITMAP, "Apple II Hi-Res Bitmap", VisDescr.VisType.Bitmap,
new VisParamDescr[] {
new VisParamDescr("File offset (hex)",
P_OFFSET, typeof(int), 0, 0x00ffffff, VisParamDescr.SpecialMode.Offset,
0x2000),
P_OFFSET, typeof(int), 0, 0x00ffffff, VisParamDescr.SpecialMode.Offset, 0),
new VisParamDescr("Width (in bytes)",
P_BYTE_WIDTH, typeof(int), 1, 40, 0, 1),
new VisParamDescr("Height",
@ -69,11 +68,10 @@ namespace RuntimeData.Apple {
new VisParamDescr("Test Float",
"floaty", typeof(float), -5.0f, 5.0f, 0, 0.1f),
}),
new VisDescr(VIS_GEN_MULTI_MAP, "Apple II Hi-Res Multi-Map", VisDescr.VisType.Bitmap,
new VisDescr(VIS_GEN_BITMAP_FONT, "Apple II Hi-Res Bitmap Font", VisDescr.VisType.Bitmap,
new VisParamDescr[] {
new VisParamDescr("File offset (hex)",
P_OFFSET, typeof(int), 0, 0x00ffffff, VisParamDescr.SpecialMode.Offset,
0x1000),
P_OFFSET, typeof(int), 0, 0x00ffffff, VisParamDescr.SpecialMode.Offset, 0),
new VisParamDescr("Item width (in bytes)",
P_ITEM_BYTE_WIDTH, typeof(int), 1, 40, 0, 1),
new VisParamDescr("Item height",
@ -109,8 +107,8 @@ namespace RuntimeData.Apple {
switch (descr.Ident) {
case VIS_GEN_BITMAP:
return GenerateBitmap(parms);
case VIS_GEN_MULTI_MAP:
// TODO
case VIS_GEN_BITMAP_FONT:
// TODO (xyzzy)
return null;
default:
mAppRef.DebugLog("Unknown ident " + descr.Ident);
@ -140,16 +138,24 @@ namespace RuntimeData.Apple {
if (offset < 0 || offset >= mFileData.Length ||
byteWidth <= 0 || byteWidth > MAX_DIM ||
height <= 0 || height > MAX_DIM ||
colStride <= 0 || colStride > MAX_DIM ||
rowStride < byteWidth * colStride - (colStride-1) || rowStride > MAX_DIM) {
height <= 0 || height > MAX_DIM) {
// the UI should flag these based on range (and ideally wouldn't have called us)
mAppRef.DebugLog("Invalid parameter");
return null;
}
if (colStride <= 0 || colStride > MAX_DIM) {
mAppRef.DebugLog("Invalid column stride");
return null;
}
if (rowStride < byteWidth * colStride - (colStride-1) || rowStride > MAX_DIM) {
mAppRef.DebugLog("Invalid row stride");
return null;
}
int lastOffset = offset + rowStride * height - (colStride - 1) - 1;
if (lastOffset >= mFileData.Length) {
mAppRef.DebugLog("Bitmap runs off end of file (lastOffset=" + lastOffset + ")");
mAppRef.DebugLog("Bitmap runs off end of file (last offset +" +
lastOffset.ToString("x6") + ")");
return null;
}
@ -235,7 +241,7 @@ namespace RuntimeData.Apple {
}
#else
// Color conversion similar to what CiderPress does, but without the half-pixel
// shift (we're trying to create a 1:1 bitmap).
// shift (we're trying to create a 1:1 bitmap, not 1:2).
bool[] lineBits = new bool[byteWidth * 7];
bool[] hiFlags = new bool[byteWidth * 7]; // overkill, but simplifies things
int[] colorBuf = new int[byteWidth * 7];
@ -326,7 +332,7 @@ namespace RuntimeData.Apple {
White1 = 7
}
// HiRes: black0, green, purple, white0, black1, orange, blue, white1
// Maps HiResColors to the palette entries.
private static readonly byte[] sHiResColorMap = new byte[8] {
1, 3, 4, 2, 1, 5, 6, 2
};
@ -334,7 +340,7 @@ namespace RuntimeData.Apple {
private void SetHiResPalette(VisBitmap8 vb) {
// These don't match directly to hi-res color numbers because we want to
// avoid adding black/white twice.
vb.AddColor(0xff, 0xff, 0, 0); // 0=transparent (xyzzy: all 0)
vb.AddColor(0, 0, 0, 0); // 0=transparent
vb.AddColor(0xff, 0x00, 0x00, 0x00); // 1=black0/black1
vb.AddColor(0xff, 0xff, 0xff, 0xff); // 2=white0/white1
vb.AddColor(0xff, 0x11, 0xdd, 0x00); // 3=green

View File

@ -419,5 +419,8 @@
<ItemGroup>
<Resource Include="Res\AboutImage.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Res\RedX.png" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -38,6 +38,13 @@ namespace SourceGen {
/// </summary>
public Dictionary<string, object> VisGenParams { get; private set; }
/// <summary>
/// Cached reference to thumbnail.
/// </summary>
/// <remarks>
/// Because the underlying data never changes, we only need to regenerate the
/// thumbnail if the set of active plugins changes.
/// </remarks>
private BitmapSource Thumbnail { get; set; } // TODO - 64x64(?) bitmap
@ -54,11 +61,30 @@ namespace SourceGen {
VisGenParams = visGenParams;
}
/// <summary>
/// Trims a tag, removing leading/trailing whitespace, and checks it for validity.
/// </summary>
/// <param name="tag">Tag to trim and validate.</param>
/// <param name="isValid">Set to true if the tag is valid.</param>
/// <returns>Trimmed tag string. Returns an empty string if tag is null.</returns>
public static string TrimAndValidateTag(string tag, out bool isValid) {
if (tag == null) {
isValid = false;
return string.Empty;
}
string trimTag = tag.Trim();
if (trimTag.Length < 2) {
isValid = false;
} else {
isValid = true;
}
return trimTag;
}
/// <summary>
/// Converts an IVisualization2d to a BitmapSource for display.
/// </summary>
/// <param name="vis2d"></param>
/// <returns></returns>
public static BitmapSource ConvertToBitmapSource(IVisualization2d vis2d) {
// Create indexed color palette.
int[] intPal = vis2d.GetPalette();

View File

@ -16,12 +16,14 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace SourceGen {
/// <summary>
/// Ordered list of visualization objects.
/// </summary>
/// <remarks>
/// Right now the only thing separating this from a plain List<> is the operator== stuff.
/// </remarks>
public class VisualizationSet : IEnumerable<Visualization> {
/// <summary>
/// Object list.
@ -32,9 +34,9 @@ namespace SourceGen {
/// <summary>
/// Constructor.
/// </summary>
/// <param name="cap">Initial capacity.</param>
public VisualizationSet(int cap = 1) {
mList = new List<Visualization>(cap);
/// <param name="initialCap">Initial capacity.</param>
public VisualizationSet(int initialCap = 1) {
mList = new List<Visualization>(initialCap);
}
// IEnumerable
@ -54,10 +56,24 @@ namespace SourceGen {
get { return mList.Count; }
}
/// <summary>
/// Accesses the Nth element.
/// </summary>
/// <param name="key">Element number.</param>
public Visualization this[int key] {
get {
return mList[key];
}
}
public void Add(Visualization vis) {
mList.Add(vis);
}
public void Remove(Visualization vis) {
mList.Remove(vis);
}
public override string ToString() {
return "[VS: " + mList.Count + " items]";

View File

@ -25,7 +25,6 @@ limitations under the License.
Title="Edit Visualization"
Width="460" SizeToContent="Height" ResizeMode="NoResize"
ShowInTaskbar="False" WindowStartupLocation="CenterOwner"
Loaded="Window_Loaded"
Closed="Window_Closed">
<Window.Resources>
@ -41,7 +40,7 @@ limitations under the License.
Text="{Binding UiString}" Foreground="{Binding ForegroundBrush}"/>
<CheckBox Grid.Column="1" Margin="0,0,0,4" IsChecked="{Binding Value}"
Checked="CheckBox_Changed" Unchecked="CheckBox_Changed"/>
<TextBlock Grid.Column="2" Text="{Binding RangeText}" Margin="0,1,0,0"
<TextBlock Grid.Column="2" Text="{Binding RangeText}" Margin="4,1,0,0"
FontFamily="{StaticResource GeneralMonoFont}"/>
</Grid>
</DataTemplate>
@ -58,7 +57,7 @@ limitations under the License.
Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}"
FontFamily="{StaticResource GeneralMonoFont}"
TextChanged="TextBox_TextChanged"/>
<TextBlock Grid.Column="2" Text="{Binding RangeText}" Margin="0,1,0,0"
<TextBlock Grid.Column="2" Text="{Binding RangeText}" Margin="4,1,0,0"
FontFamily="{StaticResource GeneralMonoFont}"/>
</Grid>
</DataTemplate>
@ -75,7 +74,7 @@ limitations under the License.
Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}"
FontFamily="{StaticResource GeneralMonoFont}"
TextChanged="TextBox_TextChanged"/>
<TextBlock Grid.Column="2" Text="{Binding RangeText}" Margin="0,1,0,0"
<TextBlock Grid.Column="2" Text="{Binding RangeText}" Margin="4,1,0,0"
FontFamily="{StaticResource GeneralMonoFont}"/>
</Grid>
</DataTemplate>
@ -88,6 +87,7 @@ limitations under the License.
FloatTemplate="{StaticResource FloatTemplate}"/>
</Window.Resources>
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
@ -108,7 +108,7 @@ limitations under the License.
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" HorizontalAlignment="Right" Margin="0,0,4,0"
<TextBlock Grid.Column="0" Grid.Row="0" HorizontalAlignment="Right" Margin="0,3,4,0"
Text="Visualizer:"/>
<ComboBox Name="visComboBox" Grid.Column="1" Grid.Row="0" Margin="0,0,0,4"
ItemsSource="{Binding VisualizationList}" DisplayMemberPath="VisDescriptor.UiName"
@ -116,7 +116,7 @@ limitations under the License.
<TextBlock Grid.Column="0" Grid.Row="1" HorizontalAlignment="Right" Margin="0,0,4,0"
Text="Tag:" Foreground="{Binding TagLabelBrush}"/>
<TextBox Grid.Column="1" Grid.Row="1"
<TextBox Grid.Column="1" Grid.Row="1" Margin="0,1,0,0"
Text="{Binding TagString, UpdateSourceTrigger=PropertyChanged}"
FontFamily="{StaticResource GeneralMonoFont}"/>
@ -125,23 +125,30 @@ limitations under the License.
<TextBlock Grid.Column="0" Grid.Row="3" Grid.ColumnSpan="2" Margin="0,20,0,4" Text="Preview:"/>
</Grid>
<Border Grid.Row="1" BorderThickness="1"
<Border Grid.Row="1" BorderThickness="1" HorizontalAlignment="Center"
BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}"
Background="LightGray">
<Image Name="previewImage" Width="400" Height="400" Source="/Res/AboutImage.png"
<Image Name="previewImage" Width="320" Height="320" Source="/Res/RedX.png"
RenderOptions.BitmapScalingMode="NearestNeighbor"/>
<!--<Button Width="400" Height="400" Content="Test"/>-->
</Border>
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Parameters:" Margin="0,0,0,4"/>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,4">
<!--<TextBlock Text="Message:" Margin="0,0,8,0"/>-->
<TextBlock Text="{Binding PluginErrMessage}" Foreground="Red"/>
</StackPanel>
<TextBlock Grid.Row="1" Text="Parameters:" Margin="0,0,0,4"/>
<!-- generated controls are placed here -->
<ItemsControl Grid.Row="1"
<ItemsControl Grid.Row="2"
ItemsSource="{Binding ParameterList}"
ItemTemplateSelector="{StaticResource ParameterTemplateSelector}">
</ItemsControl>

View File

@ -14,16 +14,13 @@
* limitations under the License.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Asm65;
@ -34,8 +31,6 @@ namespace SourceGen.WpfGui {
/// Visualization editor.
/// </summary>
public partial class EditVisualization : Window, INotifyPropertyChanged {
private const int MIN_TRIMMED_TAG_LEN = 2;
/// <summary>
/// Dialog result.
/// </summary>
@ -43,6 +38,7 @@ namespace SourceGen.WpfGui {
private DisasmProject mProject;
private Formatter mFormatter;
private int mSetOffset;
private Visualization mOrigVis;
private Brush mDefaultLabelColor = SystemColors.WindowTextBrush;
@ -64,7 +60,7 @@ namespace SourceGen.WpfGui {
/// </summary>
public string TagString {
get { return mTagString; }
set { mTagString = value; OnPropertyChanged(); }
set { mTagString = value; OnPropertyChanged(); UpdateControls(); }
}
private string mTagString;
@ -74,6 +70,13 @@ namespace SourceGen.WpfGui {
}
private Brush mTagLabelBrush;
/// <summary>
/// Item for combo box.
/// </summary>
/// <remarks>
/// Strictly speaking we could just create an ItemsSource from VisDescr objects, but
/// the plugin reference saves a lookup later.
/// </remarks>
public class VisualizationItem {
public IPlugin_Visualizer Plugin { get; private set; }
public VisDescr VisDescriptor { get; private set; }
@ -88,6 +91,21 @@ namespace SourceGen.WpfGui {
/// </summary>
public List<VisualizationItem> VisualizationList { get; private set; }
/// <summary>
/// Error message, shown in red.
/// </summary>
public string PluginErrMessage {
get { return mPluginErrMessage; }
set { mPluginErrMessage = value; OnPropertyChanged(); }
}
private string mPluginErrMessage = string.Empty;
/// <summary>
/// Set by the plugin callback. WPF doesn't like it when we try to fire off a
/// property changed event from here.
/// </summary>
public string LastPluginMessage { get; set; }
/// <summary>
/// ItemsSource for the ItemsControl with the generated parameter controls.
/// </summary>
@ -101,10 +119,17 @@ namespace SourceGen.WpfGui {
}
private class ScriptSupport : MarshalByRefObject, PluginCommon.IApplication {
public ScriptSupport() { }
private EditVisualization mOuter;
public ScriptSupport(EditVisualization outer) {
mOuter = outer;
}
public void DebugLog(string msg) {
Debug.WriteLine("Vis plugin: " + msg);
mOuter.LastPluginMessage = msg;
}
public bool SetOperandFormat(int offset, DataSubType subType, string label) {
throw new InvalidOperationException();
}
@ -113,7 +138,13 @@ namespace SourceGen.WpfGui {
throw new InvalidOperationException();
}
}
private ScriptSupport mScriptSupport = new ScriptSupport();
private ScriptSupport mScriptSupport;
/// <summary>
/// URI for the image we show when we fail to generate a visualization.
/// </summary>
private static BitmapImage sBadParamsImage =
new BitmapImage(new Uri("pack://application:,,,/Res/RedX.png"));
/// <summary>
@ -124,17 +155,23 @@ namespace SourceGen.WpfGui {
/// <param name="formatter">Text formatter.</param>
/// <param name="vis">Visualization to edit, or null if this is new.</param>
public EditVisualization(Window owner, DisasmProject proj, Formatter formatter,
Visualization vis) {
int setOffset, Visualization vis) {
InitializeComponent();
Owner = owner;
DataContext = this;
mProject = proj;
mFormatter = formatter;
mSetOffset = setOffset;
mOrigVis = vis;
mScriptSupport = new ScriptSupport(this);
if (vis != null) {
TagString = vis.Tag;
} else {
// Could make this unique, but probably not worth the bother.
TagString = "vis" + mSetOffset.ToString("x6");
}
int visSelection = 0;
@ -174,9 +211,19 @@ namespace SourceGen.WpfGui {
foreach (VisParamDescr vpd in paramDescrs) {
string rangeStr = string.Empty;
object defaultVal = vpd.DefaultValue;
if (mOrigVis.VisGenParams.TryGetValue(vpd.Name, out object val)) {
// Do we need to confirm that val has the correct type?
defaultVal = val;
// If we're editing a visualization, use the values from that as default.
if (mOrigVis != null) {
if (mOrigVis.VisGenParams.TryGetValue(vpd.Name, out object val)) {
// Do we need to confirm that val has the correct type?
defaultVal = val;
}
} else {
// New visualization. Use the set's offset as the default value for
// any parameter called "offset".
if (vpd.Name.ToLowerInvariant() == "offset") {
defaultVal = mSetOffset;
}
}
// Set up rangeStr, if appropriate.
@ -201,9 +248,6 @@ namespace SourceGen.WpfGui {
}
}
private void Window_Loaded(object sender, RoutedEventArgs e) {
}
private void Window_Closed(object sender, EventArgs e) {
mProject.UnprepareScripts();
}
@ -212,7 +256,9 @@ namespace SourceGen.WpfGui {
VisualizationItem item = (VisualizationItem)visComboBox.SelectedItem;
Debug.Assert(item != null);
Dictionary<string, object> valueDict = CreateVisGenParams();
NewVis = new Visualization(TagString, item.VisDescriptor.Ident, valueDict);
string trimTag = Visualization.TrimAndValidateTag(TagString, out bool isTagValid);
Debug.Assert(isTagValid);
NewVis = new Visualization(trimTag, item.VisDescriptor.Ident, valueDict);
DialogResult = true;
}
@ -226,22 +272,12 @@ namespace SourceGen.WpfGui {
Debug.Assert(pv.Value is bool);
valueDict.Add(pv.Descr.Name, (bool)pv.Value);
} else if (pv.Descr.CsType == typeof(int)) {
int intVal;
if (pv.Value is int) {
intVal = (int)pv.Value;
} else {
bool ok = ParseInt((string)pv.Value, pv.Descr.Special, out intVal);
Debug.Assert(ok);
}
bool ok = ParseIntObj(pv.Value, pv.Descr.Special, out int intVal);
Debug.Assert(ok);
valueDict.Add(pv.Descr.Name, intVal);
} else if (pv.Descr.CsType == typeof(float)) {
float floatVal;
if (pv.Value is float || pv.Value is double) {
floatVal = (float)pv.Value;
} else {
bool ok = float.TryParse((string)pv.Value, out floatVal);
Debug.Assert(ok);
}
bool ok = ParseFloatObj(pv.Value, out float floatVal);
Debug.Assert(ok);
valueDict.Add(pv.Descr.Name, floatVal);
} else {
// skip it
@ -252,7 +288,12 @@ namespace SourceGen.WpfGui {
return valueDict;
}
private bool ParseInt(string str, VisParamDescr.SpecialMode special, out int intVal) {
private bool ParseIntObj(object val, VisParamDescr.SpecialMode special, out int intVal) {
if (val is int) {
intVal = (int)val;
return true;
}
string str = (string)val;
int numBase = (special == VisParamDescr.SpecialMode.Offset) ? 16 : 10;
string trimStr = str.Trim();
@ -281,18 +322,29 @@ namespace SourceGen.WpfGui {
}
}
private bool ParseFloatObj(object val, out float floatVal) {
if (val is float) {
floatVal = (float)val;
return true;
} else if (val is double) {
floatVal = (float)(double)val;
return true;
} else if (val is int) {
floatVal = (int)val;
return true;
}
string str = (string)val;
if (!float.TryParse(str, out floatVal)) {
floatVal = 0.0f;
return false;
}
return true;
}
private void UpdateControls() {
IsValid = true;
string trimTag = TagString.Trim();
if (trimTag.Length < MIN_TRIMMED_TAG_LEN) {
IsValid = false;
TagLabelBrush = mErrorLabelColor;
} else {
TagLabelBrush = mDefaultLabelColor;
}
// TODO: verify tag is unique
foreach (ParameterValue pv in ParameterList) {
pv.ForegroundBrush = mDefaultLabelColor;
if (pv.Descr.CsType == typeof(bool)) {
@ -300,14 +352,7 @@ namespace SourceGen.WpfGui {
continue;
} else if (pv.Descr.CsType == typeof(int)) {
// integer, possibly Offset special
bool ok = true;
int intVal;
if (pv.Value is int) {
// happens initially, before the TextBox can futz with it
intVal = (int)pv.Value;
} else if (!ParseInt((string)pv.Value, pv.Descr.Special, out intVal)) {
ok = false;
}
bool ok = ParseIntObj(pv.Value, pv.Descr.Special, out int intVal);
if (ok && (intVal < (int)pv.Descr.Min || intVal > (int)pv.Descr.Max)) {
// TODO(someday): make the range text red instead of the label
ok = false;
@ -318,17 +363,28 @@ namespace SourceGen.WpfGui {
}
} else if (pv.Descr.CsType == typeof(float)) {
// float
bool ok = ParseFloatObj(pv.Value, out float floatVal);
if (ok && (floatVal < (float)pv.Descr.Min || floatVal > (float)pv.Descr.Max)) {
ok = false;
}
if (!ok) {
pv.ForegroundBrush = mErrorLabelColor;
IsValid = false;
}
} else {
// unexpected
Debug.Assert(false);
}
}
if (!IsValid) {
// TODO(xyzzy): default to a meaningful image
previewImage.Source = new BitmapImage(new Uri("pack://application:,,,/Res/Logo.png"));
VisualizationItem item = (VisualizationItem)visComboBox.SelectedItem;
if (!IsValid || item == null) {
previewImage.Source = sBadParamsImage;
} else {
VisualizationItem item = (VisualizationItem)visComboBox.SelectedItem;
// Invoke the plugin.
PluginErrMessage = string.Empty;
IVisualization2d vis2d;
try {
vis2d = item.Plugin.Generate2d(item.VisDescriptor,
@ -337,16 +393,48 @@ namespace SourceGen.WpfGui {
Debug.WriteLine("Vis generator returned null");
}
} catch (Exception ex) {
// TODO(xyzzy): use different image for failure
Debug.WriteLine("Vis generation failed: " + ex.Message);
Debug.WriteLine("Vis generation failed: " + ex);
vis2d = null;
}
if (vis2d == null) {
previewImage.Source = new BitmapImage(new Uri("pack://application:,,,/Res/Logo.png"));
previewImage.Source = sBadParamsImage;
if (!string.IsNullOrEmpty(LastPluginMessage)) {
// Report the last message we got as an error.
PluginErrMessage = LastPluginMessage;
}
} else {
previewImage.Source = Visualization.ConvertToBitmapSource(vis2d);
}
}
string trimTag = Visualization.TrimAndValidateTag(TagString, out bool tagOk);
Visualization match = FindVisualizationByTag(trimTag);
if (match != null && match != mOrigVis) {
// Another vis already has this tag.
tagOk = false;
}
if (!tagOk) {
TagLabelBrush = mErrorLabelColor;
IsValid = false;
} else {
TagLabelBrush = mDefaultLabelColor;
}
}
/// <summary>
/// Finds a Visualization with a matching tag, searching across all sets.
/// </summary>
/// <param name="tag">Tag to search for.</param>
/// <returns>Matching Visualization, or null if not found.</returns>
private Visualization FindVisualizationByTag(string tag) {
foreach (KeyValuePair<int, VisualizationSet> kvp in mProject.VisualizationSets) {
foreach (Visualization vis in kvp.Value) {
if (vis.Tag == tag) {
return vis;
}
}
}
return null;
}
private void VisComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) {

View File

@ -19,12 +19,18 @@ limitations under the License.
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:system="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:SourceGen.WpfGui"
mc:Ignorable="d"
Title="Edit Visualization Set"
Width="460" Height="400" ResizeMode="NoResize"
Width="500" Height="400" ResizeMode="NoResize"
ShowInTaskbar="False" WindowStartupLocation="CenterOwner"
Loaded="Window_Loaded">
Closing="Window_Closing">
<Window.Resources>
<system:String x:Key="str_ConfirmDiscardChanges">Are you sure you wish to discard your changes?</system:String>
<system:String x:Key="str_ConfirmDiscardChangesCaption">Discard Changes?</system:String>
</Window.Resources>
<Grid Margin="8">
<Grid.ColumnDefinitions>
@ -36,7 +42,7 @@ limitations under the License.
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DataGrid Name="visualizationList" Grid.Column="0" Grid.Row="0" Margin="4,4,4,0"
<DataGrid Name="visualizationGrid" Grid.Column="0" Grid.Row="0" Margin="4,4,4,0"
ItemsSource="{Binding VisualizationList}"
IsReadOnly="True"
FontFamily="{StaticResource GeneralMonoFont}"
@ -60,22 +66,22 @@ limitations under the License.
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header="Tag" Width="155" Binding="{Binding Tag}"/>
<DataGridTextColumn Header="Visualization Generator" Width="180" Binding="{Binding VisGenName}"/>
<DataGridTextColumn Header="Visualization Generator" Width="220" Binding="{Binding VisGenIdent}"/>
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Column="1" Grid.Row="0">
<Button Width="75" Margin="4" Content="_New..."
Click="NewButton_Click"/>
IsEnabled="{Binding HasVisPlugins}" Click="NewButton_Click"/>
<Button Width="75" Margin="4" Content="_Edit"
Click="EditButton_Click"/>
IsEnabled="{Binding IsEditEnabled}" Click="EditButton_Click"/>
<Button Width="75" Margin="4" Content="_Remove"
Click="RemoveButton_Click"/>
IsEnabled="{Binding IsRemoveEnabled}" Click="RemoveButton_Click"/>
<Button Width="75" Margin="4,20,4,4" Content="_Up"
Click="UpButton_Click"/>
IsEnabled="{Binding IsUpEnabled}" Click="UpButton_Click"/>
<Button Width="75" Margin="4,4" Content="_Down"
Click="DownButton_Click"/>
IsEnabled="{Binding IsDownEnabled}" Click="DownButton_Click"/>
</StackPanel>
<DockPanel Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" Margin="0,8,0,0" LastChildFill="False">

View File

@ -38,10 +38,45 @@ namespace SourceGen.WpfGui {
private DisasmProject mProject;
private Formatter mFormatter;
private VisualizationSet mOrigSet;
private int mOffset;
public ObservableCollection<Visualization> VisualizationList { get; private set; } =
new ObservableCollection<Visualization>();
/// <summary>
/// True if there are plugins that implement the visualization generation interface.
/// </summary>
public bool HasVisPlugins {
get { return mHasVisPlugins; }
set { mHasVisPlugins = value; OnPropertyChanged(); }
}
private bool mHasVisPlugins;
public bool IsEditEnabled {
get { return mIsEditEnabled; }
set { mIsEditEnabled = value; OnPropertyChanged(); }
}
private bool mIsEditEnabled;
public bool IsRemoveEnabled {
get { return mIsRemoveEnabled; }
set { mIsRemoveEnabled = value; OnPropertyChanged(); }
}
private bool mIsRemoveEnabled;
public bool IsUpEnabled {
get { return mIsUpEnabled; }
set { mIsUpEnabled = value; OnPropertyChanged(); }
}
private bool mIsUpEnabled;
public bool IsDownEnabled {
get { return mIsDownEnabled; }
set { mIsDownEnabled = value; OnPropertyChanged(); }
}
private bool mIsDownEnabled;
// INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "") {
@ -49,78 +84,133 @@ namespace SourceGen.WpfGui {
}
public EditVisualizationSet(Window owner, DisasmProject project, Formatter formatter,
VisualizationSet curSet) {
VisualizationSet curSet, int offset) {
InitializeComponent();
Owner = owner;
DataContext = this;
mProject = project;
mFormatter = formatter;
mOrigSet = curSet;
mOffset = offset;
if (curSet != null) {
// Populate the ItemsSource.
foreach (Visualization vis in curSet) {
VisualizationList.Add(vis);
}
}
}
private void Window_Loaded(object sender, RoutedEventArgs e) {
// Check to see if we have any relevant plugins. If not, disable New/Edit.
List<IPlugin> plugins = project.GetActivePlugins();
foreach (IPlugin chkPlug in plugins) {
if (chkPlug is IPlugin_Visualizer) {
HasVisPlugins = true;
break;
}
}
}
private void OkButton_Click(object sender, RoutedEventArgs e) {
if (VisualizationList.Count == 0) {
NewVisSet = null;
} else {
NewVisSet = new VisualizationSet(VisualizationList.Count);
foreach (Visualization vis in VisualizationList) {
NewVisSet.Add(vis);
NewVisSet = MakeVisSet();
DialogResult = true;
}
private void Window_Closing(object sender, CancelEventArgs e) {
if (DialogResult == true) {
return;
}
// Check to see if changes have been made.
VisualizationSet newSet = MakeVisSet();
if (newSet != mOrigSet) {
string msg = (string)FindResource("str_ConfirmDiscardChanges");
string caption = (string)FindResource("str_ConfirmDiscardChangesCaption");
MessageBoxResult result = MessageBox.Show(msg, caption, MessageBoxButton.OKCancel,
MessageBoxImage.Question);
if (result == MessageBoxResult.Cancel) {
e.Cancel = true;
}
}
DialogResult = true;
}
private VisualizationSet MakeVisSet() {
if (VisualizationList.Count == 0) {
return null;
}
VisualizationSet newSet = new VisualizationSet(VisualizationList.Count);
foreach (Visualization vis in VisualizationList) {
newSet.Add(vis);
}
return newSet;
}
private void VisualizationList_SelectionChanged(object sender,
SelectionChangedEventArgs e) {
Debug.WriteLine("SEL CHANGE");
bool isItemSelected = (visualizationGrid.SelectedItem != null);
IsEditEnabled = HasVisPlugins && isItemSelected;
IsRemoveEnabled = isItemSelected;
IsUpEnabled = isItemSelected && visualizationGrid.SelectedIndex != 0;
IsDownEnabled = isItemSelected &&
visualizationGrid.SelectedIndex != VisualizationList.Count - 1;
}
private void VisualizationList_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
Debug.WriteLine("DBL CLICK");
EditSelectedItem();
}
private void NewButton_Click(object sender, RoutedEventArgs e) {
VisualizationList.Add(new Visualization("VIS #" + VisualizationList.Count,
"apple2-hi-res-bitmap", new Dictionary<string, object>()));
// TODO: disable New button if no appropriate vis plugins can be found (or maybe
// MessageBox here)
EditVisualization dlg = new EditVisualization(this, mProject, mFormatter, mOffset,
null);
if (dlg.ShowDialog() != true) {
return;
}
VisualizationList.Add(dlg.NewVis);
}
private void EditButton_Click(object sender, RoutedEventArgs e) {
Dictionary<string, object> testDict = new Dictionary<string, object>();
testDict.Add("offset", 0);
testDict.Add("byteWidth", 2);
testDict.Add("height", 7);
EditVisualization dlg = new EditVisualization(this, mProject, mFormatter,
new Visualization("arbitrary tag", "apple2-hi-res-bitmap", testDict));
if (dlg.ShowDialog() == true) {
Visualization newVis = dlg.NewVis;
Debug.WriteLine("New vis: " + newVis);
EditSelectedItem();
}
private void EditSelectedItem() {
if (!IsEditEnabled) {
// can happen on a double-click
return;
}
Visualization item = (Visualization)visualizationGrid.SelectedItem;
EditVisualization dlg = new EditVisualization(this, mProject, mFormatter, mOffset,
item);
if (dlg.ShowDialog() != true) {
return;
}
// TODO: disable edit button if matching vis can't be found (or maybe MessageBox)
int index = VisualizationList.IndexOf(item);
VisualizationList.Remove(item);
VisualizationList.Insert(index, dlg.NewVis);
}
private void RemoveButton_Click(object sender, RoutedEventArgs e) {
Visualization item = (Visualization)visualizationGrid.SelectedItem;
VisualizationList.Remove(item);
}
private void UpButton_Click(object sender, RoutedEventArgs e) {
Visualization item = (Visualization)visualizationGrid.SelectedItem;
int index = VisualizationList.IndexOf(item);
Debug.Assert(index > 0);
VisualizationList.Remove(item);
VisualizationList.Insert(index - 1, item);
visualizationGrid.SelectedIndex = index - 1;
}
private void DownButton_Click(object sender, RoutedEventArgs e) {
Visualization item = (Visualization)visualizationGrid.SelectedItem;
int index = VisualizationList.IndexOf(item);
Debug.Assert(index < VisualizationList.Count - 1);
VisualizationList.Remove(item);
VisualizationList.Insert(index + 1, item);
visualizationGrid.SelectedIndex = index + 1;
}
}
}