From 71bcc0beee916a5a7859047e4e0f392f28deb4ba Mon Sep 17 00:00:00 2001 From: Khaos Date: Tue, 9 May 2023 21:51:53 +0200 Subject: [PATCH 01/13] Upgrade to .net7.0 --- .gitignore | 398 +++++++++++++++++++++++++++++++ App.config | 6 - Program.cs | 1 + Properties/Resources.Designer.cs | 44 ++-- Properties/Settings.Designer.cs | 22 +- charposition.csproj | 89 +------ 6 files changed, 430 insertions(+), 130 deletions(-) create mode 100644 .gitignore delete mode 100644 App.config diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8a30d25 --- /dev/null +++ b/.gitignore @@ -0,0 +1,398 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml diff --git a/App.config b/App.config deleted file mode 100644 index fad249e..0000000 --- a/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/Program.cs b/Program.cs index 4181c1f..12b41f5 100644 --- a/Program.cs +++ b/Program.cs @@ -10,6 +10,7 @@ static class Program static void Main(string[] args) { Application.EnableVisualStyles(); + Application.SetHighDpiMode(HighDpiMode.SystemAware); Application.SetCompatibleTextRenderingDefault(false); string text = diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs index 425ec3d..ae336ab 100644 --- a/Properties/Resources.Designer.cs +++ b/Properties/Resources.Designer.cs @@ -8,10 +8,10 @@ // //------------------------------------------------------------------------------ -namespace charposition.Properties -{ - - +namespace charposition.Properties { + using System; + + /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -19,51 +19,43 @@ namespace charposition.Properties // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources - { - + internal class Resources { + private static global::System.Resources.ResourceManager resourceMan; - + private static global::System.Globalization.CultureInfo resourceCulture; - + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() - { + internal Resources() { } - + /// /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if ((resourceMan == null)) - { + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("charposition.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } - + /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture - { - get - { + internal static global::System.Globalization.CultureInfo Culture { + get { return resourceCulture; } - set - { + set { resourceCulture = value; } } diff --git a/Properties/Settings.Designer.cs b/Properties/Settings.Designer.cs index 37cf637..f9ac842 100644 --- a/Properties/Settings.Designer.cs +++ b/Properties/Settings.Designer.cs @@ -8,21 +8,17 @@ // //------------------------------------------------------------------------------ -namespace charposition.Properties -{ - - +namespace charposition.Properties { + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase - { - + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.5.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default - { - get - { + + public static Settings Default { + get { return defaultInstance; } } diff --git a/charposition.csproj b/charposition.csproj index 28fa6bd..0262238 100644 --- a/charposition.csproj +++ b/charposition.csproj @@ -1,89 +1,8 @@ - - - + - Debug - AnyCPU - {AF4CC8D5-70C5-44EF-B1D1-A2EF3FC24B04} + net7.0-windows WinExe - Properties - charposition - charposition - v4.5 - 512 + false + true - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - Form - - - Form1.cs - - - - - - Form1.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - - - - - - - \ No newline at end of file From 1cad51a66266ac60167107e9011363ff5e4984dc Mon Sep 17 00:00:00 2001 From: Khaos Date: Sun, 14 May 2023 11:40:20 +0200 Subject: [PATCH 02/13] Refactored to use WPF --- App.xaml | 4 + App.xaml.cs | 42 ++++ Controls/Character.cs | 43 ++++ Controls/CharsView.cs | 277 ++++++++++++++++++++++++ DummyData.cs | 46 ++++ Form1.Designer.cs | 64 ------ Form1.cs | 23 -- Form1.resx | 120 ----------- MainWindow.xaml | 15 ++ MainWindow.xaml.cs | 35 ++++ MainWindowModel.cs | 25 +++ Program.cs | 41 ---- Properties/AssemblyInfo.cs | 36 ---- Properties/Resources.Designer.cs | 63 ------ Properties/Resources.resx | 117 ----------- Properties/Settings.Designer.cs | 26 --- Properties/Settings.settings | 7 - RenderFile.cs | 350 +++++++++++++++---------------- Services/FileReader.cs | 8 + Services/IFileReader.cs | 6 + Services/ILineSplitter.cs | 8 + Services/LineSplitter.cs | 17 ++ charposition.csproj | 15 +- 23 files changed, 714 insertions(+), 674 deletions(-) create mode 100644 App.xaml create mode 100644 App.xaml.cs create mode 100644 Controls/Character.cs create mode 100644 Controls/CharsView.cs create mode 100644 DummyData.cs delete mode 100644 Form1.Designer.cs delete mode 100644 Form1.cs delete mode 100644 Form1.resx create mode 100644 MainWindow.xaml create mode 100644 MainWindow.xaml.cs create mode 100644 MainWindowModel.cs delete mode 100644 Program.cs delete mode 100644 Properties/AssemblyInfo.cs delete mode 100644 Properties/Resources.Designer.cs delete mode 100644 Properties/Resources.resx delete mode 100644 Properties/Settings.Designer.cs delete mode 100644 Properties/Settings.settings create mode 100644 Services/FileReader.cs create mode 100644 Services/IFileReader.cs create mode 100644 Services/ILineSplitter.cs create mode 100644 Services/LineSplitter.cs diff --git a/App.xaml b/App.xaml new file mode 100644 index 0000000..bb46346 --- /dev/null +++ b/App.xaml @@ -0,0 +1,4 @@ + diff --git a/App.xaml.cs b/App.xaml.cs new file mode 100644 index 0000000..1e7d5e7 --- /dev/null +++ b/App.xaml.cs @@ -0,0 +1,42 @@ +using Autofac; +using Autofac.Builder; +using charposition.Services; +using System.Windows; + +namespace charposition; + +/// +/// Interaction logic for App.xaml +/// +public partial class App : Application +{ + private void Application_Startup(object sender, StartupEventArgs e) + { + // Default: dummy data + string text = DummyData.Text; + string title = DummyData.Title; + string semantics = DummyData.Semantics; + + // Services + ContainerBuilder services = new(); + services.RegisterType().As(); + services.RegisterType().As(); + services.RegisterType(); + + var container = services.Build(); + + // read args + var fileReader = container.Resolve(); + if (e.Args.Length > 0) + { + text = fileReader.ReadAllText(e.Args[0]); + title = e.Args[0]; + semantics = e.Args.Length == 1 ? string.Empty : fileReader.ReadAllText(e.Args[1]); + } + + var window = container.Resolve(); + window.Title = title; + window.SetText(text); + window.Show(); + } +} diff --git a/Controls/Character.cs b/Controls/Character.cs new file mode 100644 index 0000000..bdc5384 --- /dev/null +++ b/Controls/Character.cs @@ -0,0 +1,43 @@ +using System.Windows.Controls; +using System.Windows.Media; + +namespace charposition.Controls; + +public class Character : Canvas +{ + public Character(int totalChars, char character) + { + string display = DisplayCharacter(character); + TextBlock label = new() + { + Text = display, + Foreground = display != character.ToString() ? Brushes.Gray : Brushes.Black, + HorizontalAlignment = System.Windows.HorizontalAlignment.Left, + VerticalAlignment = System.Windows.VerticalAlignment.Top, + }; + this.Children.Add(label); + Canvas.SetTop(label, 2); + Canvas.SetLeft(label, 8); + + TextBlock index = new() + { + Text = totalChars.ToString(), + Foreground = Brushes.Gray, + HorizontalAlignment = System.Windows.HorizontalAlignment.Right, + VerticalAlignment = System.Windows.VerticalAlignment.Bottom, + FontSize = 10 + }; + this.Children.Add(index); + Canvas.SetBottom(index, 2); + Canvas.SetRight(index, 2); + } + + private static string DisplayCharacter(char character) => + character switch + { + '\r' => "\\r", + '\n' => "\\n", + '\t' => "\\t", + _ => character.ToString(), + }; +} diff --git a/Controls/CharsView.cs b/Controls/CharsView.cs new file mode 100644 index 0000000..0e48675 --- /dev/null +++ b/Controls/CharsView.cs @@ -0,0 +1,277 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Media; +using System.Windows.Shapes; + +namespace charposition.Controls; + +public class CharsView : UserControl +{ + private const int CellWidth = 24; + private const int CellHeight = 32; + + private readonly Brush LineBrush = Brushes.Gray; + private readonly Brush LabelBrush = Brushes.Gray; + + public int LineCount + { + get => (int)GetValue(LineCountProperty); + set => SetValue(LineCountProperty, value); + } + public static readonly DependencyProperty LineCountProperty = + DependencyProperty.Register("LineCount", typeof(int), typeof(CharsView), new PropertyMetadata(1, Redraw)); + + public int MaxLineLength + { + get => (int)GetValue(MaxLineLengthProperty); + set => SetValue(MaxLineLengthProperty, value); + } + public static readonly DependencyProperty MaxLineLengthProperty = + DependencyProperty.Register("MaxLineLength", typeof(int), typeof(CharsView), new PropertyMetadata(1, Redraw)); + + public IEnumerable LineChars + { + get => (IEnumerable)GetValue(LineCharsProperty); + set => SetValue(LineCharsProperty, value); + } + public static readonly DependencyProperty LineCharsProperty = + DependencyProperty.Register("LineChars", typeof(IEnumerable), typeof(CharsView), new PropertyMetadata(null, OnLineCharsChanged)); + + static void OnLineCharsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (e.NewValue is ObservableCollection coll && d is CharsView ctrl) + { + coll.CollectionChanged += (object? sender, NotifyCollectionChangedEventArgs e) => ctrl.Draw(); + } + } + + public CharsView() + { + this.Canvas = new Canvas(); + this.Content = this.Canvas; + + Canvas.SetBinding(WidthProperty, + new Binding { Path = new PropertyPath(ActualWidthProperty), Source = this }); + Canvas.SetBinding(HeightProperty, + new Binding { Path = new PropertyPath(ActualHeightProperty), Source = this }); + + this.SizeChanged += (s, e) => this.Draw(); + + this.HorizontalLine = new Line + { + Stroke = LineBrush, + StrokeThickness = 1, + X1 = CellWidth, + Y1 = CellWidth, + Y2 = CellWidth + }; + this.Canvas.Children.Add(this.HorizontalLine); + + this.VerticalLine = new Line + { + Stroke = LineBrush, + StrokeThickness = 1, + X1 = CellWidth, + X2 = CellWidth, + Y1 = CellWidth + }; + this.Canvas.Children.Add(this.VerticalLine); + } + + private static void Redraw(DependencyObject d, DependencyPropertyChangedEventArgs e) => + ((CharsView)d).Draw(); + + private void Draw() + { + this.UpdateBorder(); + this.UpdateLineNumbers(); + this.UpdateColumnNumbers(); + this.UpdateRowLines(); + this.UpdateColumnLines(); + this.UpdateCharacters(); + } + + private void UpdateCharacters() + { + int totalChars = 0; + int lineNo = 0; + foreach (var line in this.LineChars) + { + if (line is not char[] chars) + { + continue; + } + + for (int i = 0; i < chars.Length; i++) + { + var character = new Character(totalChars++, chars[i]) + { + Width = CellWidth, + Height = CellHeight + }; + this.Canvas.Children.Add(character); + Canvas.SetTop(character, CellWidth + (lineNo * CellHeight)); + Canvas.SetLeft(character, (i + 1) * CellWidth); + } + lineNo++; + } + } + + private void UpdateColumnLines() + { + int row = 0; + int index = 0; + foreach (char[] lineChars in this.LineChars) + { + for (int i = 1; i <= lineChars.Length; i++) + { + if (this.ColumnLines.Count <= index) + { + var line = new Line + { + Stroke = LineBrush, + StrokeThickness = 0.5, + }; + this.Canvas.Children.Add(line); + this.ColumnLines.Add(line); + } + var columnLine = this.ColumnLines[index++]; + + columnLine.X1 = columnLine.X2 = CellWidth + (i * CellWidth); + columnLine.Y1 = CellWidth + (row * CellHeight); + columnLine.Y2 = CellWidth + ((row + 1) * CellHeight); + } + + row++; + } + + while (this.ColumnLines.Count > index) + { + this.Canvas.Children.Remove(this.ColumnLines[^1]); + this.ColumnLines.RemoveAt(this.ColumnLines.Count - 1); + } + } + + private void UpdateRowLines() + { + while (this.RowLines.Count > this.LineCount) + { + this.Canvas.Children.Remove(this.RowLines[^1]); + this.RowLines.RemoveAt(this.RowLines.Count - 1); + } + while (this.RowLines.Count < this.LineCount) + { + var line = new Line + { + Stroke = LineBrush, + StrokeThickness = 0.5, + X1 = CellWidth + }; + this.Canvas.Children.Add(line); + this.RowLines.Add(line); + } + int row = 0; + int lastLineLength = 0; + foreach (char[] lineChars in this.LineChars) + { + if (row == 0) + { + row++; + lastLineLength = lineChars.Length; + continue; + } + int length = Math.Max(lastLineLength, lineChars.Length); + UpdateRowLineLength(row, length); + lastLineLength = lineChars.Length; + row++; + } + UpdateRowLineLength(row, lastLineLength); + } + + private void UpdateRowLineLength(int row, int length) + { + if (row < 1) + { + return; + } + + var line = this.RowLines[row - 1]; + line.Y1 = CellWidth + (row * CellHeight); + line.Y2 = CellWidth + (row * CellHeight); + line.X2 = CellWidth + (length * CellWidth); + } + + private void UpdateColumnNumbers() + { + while (this.ColumnLabels.Count > this.MaxLineLength) + { + this.Canvas.Children.Remove(this.ColumnLabels[^1]); + this.ColumnLabels.RemoveAt(this.ColumnLabels.Count - 1); + } + while (this.ColumnLabels.Count < this.MaxLineLength) + { + var label = new TextBlock + { + Text = (this.ColumnLabels.Count + 1).ToString(), + Foreground = LabelBrush, + FontSize = 12, + Width = CellWidth, + Height = CellHeight, + TextAlignment = TextAlignment.Center + }; + this.Canvas.Children.Add(label); + this.ColumnLabels.Add(label); + Canvas.SetTop(label, 0); + Canvas.SetLeft(label, this.ColumnLabels.Count * CellWidth); + } + } + + private void UpdateLineNumbers() + { + while (this.LineNoLabels.Count > this.LineCount) + { + this.Canvas.Children.Remove(this.LineNoLabels[^1]); + this.LineNoLabels.RemoveAt(this.LineNoLabels.Count - 1); + } + while (this.LineNoLabels.Count < this.LineCount) + { + var label = new TextBlock + { + Text = (this.LineNoLabels.Count + 1).ToString(), + Foreground = LabelBrush, + FontSize = 12, + Width = CellWidth, + Height = CellHeight, + TextAlignment = TextAlignment.Center + }; + this.Canvas.Children.Add(label); + this.LineNoLabels.Add(label); + Canvas.SetTop(label, this.LineNoLabels.Count * CellHeight); + Canvas.SetLeft(label, 0); + } + } + + private void UpdateBorder() + { + this.HorizontalLine.X2 = this.ActualWidth; + this.VerticalLine.Y2 = this.ActualHeight; + } + + private Canvas Canvas { get; } + private Line HorizontalLine { get; } + private Line VerticalLine { get; } + + private List LineNoLabels { get; } = new(); + private List ColumnLabels { get; } = new(); + + private List RowLines { get; } = new(); + private List ColumnLines { get; } = new(); + + private List Characters { get; } = new(); +} diff --git a/DummyData.cs b/DummyData.cs new file mode 100644 index 0000000..6d0ba49 --- /dev/null +++ b/DummyData.cs @@ -0,0 +1,46 @@ +namespace charposition; + +internal static class DummyData +{ + public const string Title = "Sample code"; + + public const string Text = +@"class Socket +{ + void Connect(string server) + { + SocketLibrary.Connect(mSocket, server); + } + + void Disconnect() + { + SocketLibrary.Disconnect(mSocket); + } +}"; + + public const string Semantics = +@"--- +type: file +name: FooSocket.csharp +locationSpan : {start: [1, 0], end: [12, 1]} +footerSpan : [0,-1] +parsingErrorsDetected : false +children: + + - type : class + name : Socket + locationSpan : {start: [1, 0], end: [12, 1]} + headerSpan : [0, 16] + footerSpan : [186, 186] + children : + + - type : method + name : Connect + locationSpan : {start: [3, 0], end: [7,2]} + span : [17, 109] + + - type : method + name : Disconnect + locationSpan : {start: [8,0], end: [11,6]} + span : [110, 185]"; +} diff --git a/Form1.Designer.cs b/Form1.Designer.cs deleted file mode 100644 index 463bbef..0000000 --- a/Form1.Designer.cs +++ /dev/null @@ -1,64 +0,0 @@ -namespace charposition -{ - partial class Form1 - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.pictureBox1 = new System.Windows.Forms.PictureBox(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); - this.SuspendLayout(); - // - // pictureBox1 - // - this.pictureBox1.Location = new System.Drawing.Point(0, 0); - this.pictureBox1.Name = "pictureBox1"; - this.pictureBox1.Size = new System.Drawing.Size(100, 50); - this.pictureBox1.TabIndex = 0; - this.pictureBox1.TabStop = false; - // - // Form1 - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.AutoScroll = true; - this.BackColor = System.Drawing.Color.White; - this.ClientSize = new System.Drawing.Size(862, 385); - this.Controls.Add(this.pictureBox1); - this.Name = "Form1"; - this.Text = "Form1"; - ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); - this.ResumeLayout(false); - - } - - #endregion - - private System.Windows.Forms.PictureBox pictureBox1; - - } -} - diff --git a/Form1.cs b/Form1.cs deleted file mode 100644 index d3637ce..0000000 --- a/Form1.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Drawing; -using System.Windows.Forms; - -namespace charposition -{ - public partial class Form1 : Form - { - public Form1(string title, string text) - { - InitializeComponent(); - - this.Text = title; - - Bitmap bmp = RenderFile.Draw(text); - - pictureBox1.Width = bmp.Width; - pictureBox1.Height = bmp.Height; - pictureBox1.Left = 0; - pictureBox1.Top = 0; - pictureBox1.Image = bmp; - } - } -} diff --git a/Form1.resx b/Form1.resx deleted file mode 100644 index 29dcb1b..0000000 --- a/Form1.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/MainWindow.xaml b/MainWindow.xaml new file mode 100644 index 0000000..9a8cc4f --- /dev/null +++ b/MainWindow.xaml @@ -0,0 +1,15 @@ + + + + + diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs new file mode 100644 index 0000000..018fb25 --- /dev/null +++ b/MainWindow.xaml.cs @@ -0,0 +1,35 @@ +using charposition.Services; +using System; +using System.Windows; + +namespace charposition; + +/// +/// Interaction logic for MainWindow.xaml +/// +public partial class MainWindow : Window +{ + private readonly MainWindowModel model; + private readonly ILineSplitter lineSplitter; + + public MainWindow(ILineSplitter lineSplitter) + { + DataContext = model = new MainWindowModel(); + InitializeComponent(); + this.lineSplitter = lineSplitter; + } + + public void SetText(string text) + { + model.MaximumLineLength = 0; + model.LineCount = 0; + model.LineChars.Clear(); + + foreach (char[] line in lineSplitter.SplitLines(text)) + { + model.LineCount++; + model.MaximumLineLength = Math.Max(model.MaximumLineLength, line.Length); + model.LineChars.Add(line); + } + } +} diff --git a/MainWindowModel.cs b/MainWindowModel.cs new file mode 100644 index 0000000..f54ee71 --- /dev/null +++ b/MainWindowModel.cs @@ -0,0 +1,25 @@ +using System.Collections.ObjectModel; +using System.Windows; + +namespace charposition; + +internal class MainWindowModel : DependencyObject +{ + public int MaximumLineLength + { + get => (int)GetValue(MaximumLineLengthProperty); + set => SetValue(MaximumLineLengthProperty, value); + } + public static readonly DependencyProperty MaximumLineLengthProperty = + DependencyProperty.Register("MaximumLineLength", typeof(int), typeof(MainWindowModel), new PropertyMetadata(0)); + + public int LineCount + { + get => (int)GetValue(LineCountProperty); + set => SetValue(LineCountProperty, value); + } + public static readonly DependencyProperty LineCountProperty = + DependencyProperty.Register("LineCount", typeof(int), typeof(MainWindowModel), new PropertyMetadata(0)); + + public ObservableCollection LineChars { get; } = new(); +} diff --git a/Program.cs b/Program.cs deleted file mode 100644 index 12b41f5..0000000 --- a/Program.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.IO; -using System.Windows.Forms; - -namespace charposition -{ - static class Program - { - [STAThread] - static void Main(string[] args) - { - Application.EnableVisualStyles(); - Application.SetHighDpiMode(HighDpiMode.SystemAware); - Application.SetCompatibleTextRenderingDefault(false); - - string text = -@"class Socket -{ - void Connect(string server) - { - SocketLibrary.Connect(mSocket, server); - } - - void Disconnect() - { - SocketLibrary.Disconnect(mSocket); - } -}"; - - string title = "Sample code"; - - if (args.Length > 0) - { - text = File.ReadAllText(args[0]); - title = args[0]; - } - - Application.Run(new Form1(title, text)); - } - } -} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs deleted file mode 100644 index 050835b..0000000 --- a/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("charposition")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("charposition")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("97110a2b-dd77-4e0e-bb10-dc44a64be4b8")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs deleted file mode 100644 index ae336ab..0000000 --- a/Properties/Resources.Designer.cs +++ /dev/null @@ -1,63 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace charposition.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("charposition.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - } -} diff --git a/Properties/Resources.resx b/Properties/Resources.resx deleted file mode 100644 index ffecec8..0000000 --- a/Properties/Resources.resx +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/Properties/Settings.Designer.cs b/Properties/Settings.Designer.cs deleted file mode 100644 index f9ac842..0000000 --- a/Properties/Settings.Designer.cs +++ /dev/null @@ -1,26 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace charposition.Properties { - - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.5.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default { - get { - return defaultInstance; - } - } - } -} diff --git a/Properties/Settings.settings b/Properties/Settings.settings deleted file mode 100644 index abf36c5..0000000 --- a/Properties/Settings.settings +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/RenderFile.cs b/RenderFile.cs index 7d8e54d..0ce02a0 100644 --- a/RenderFile.cs +++ b/RenderFile.cs @@ -1,182 +1,182 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Drawing2D; -using System.Drawing.Imaging; -using System.Drawing.Text; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Forms; +//using System; +//using System.Collections.Generic; +//using System.Drawing; +//using System.Drawing.Drawing2D; +//using System.Drawing.Imaging; +//using System.Drawing.Text; +//using System.Linq; +//using System.Text; +//using System.Threading.Tasks; +//using System.Windows.Forms; -namespace charposition -{ - static class RenderFile - { - static internal Bitmap Draw(string text) - { - Size maxTextSize = GetTextSizeInLinesAndCols(text); +//namespace charposition +//{ +// static class RenderFile +// { +// static internal Bitmap Draw(string text) +// { +// Size maxTextSize = GetTextSizeInLinesAndCols(text); - Font codeFont = new Font("Consolas", 14.0f); - Size codeTextSize = TextRenderer.MeasureText("Z", codeFont); +// Font codeFont = new Font("Consolas", 14.0f); +// Size codeTextSize = TextRenderer.MeasureText("Z", codeFont); - int xSpace = codeTextSize.Width + 2; - int ySpace = codeTextSize.Height + 10; +// int xSpace = codeTextSize.Width + 2; +// int ySpace = codeTextSize.Height + 10; - int xInitialMargin = xSpace; - int yInitialMargin = ySpace; +// int xInitialMargin = xSpace; +// int yInitialMargin = ySpace; - var bitmap = new Bitmap( - maxTextSize.Width * xSpace + xInitialMargin +1 , - maxTextSize.Height * ySpace + yInitialMargin + 1, - PixelFormat.Format32bppArgb); +// var bitmap = new Bitmap( +// maxTextSize.Width * xSpace + xInitialMargin +1 , +// maxTextSize.Height * ySpace + yInitialMargin + 1, +// PixelFormat.Format32bppArgb); - var g = Graphics.FromImage(bitmap); +// var g = Graphics.FromImage(bitmap); - g.SmoothingMode = SmoothingMode.AntiAlias; - g.TextRenderingHint = TextRenderingHint.AntiAlias; - - RenderNumbers numbers = new RenderNumbers(); - numbers.RenderColumns(g, maxTextSize.Width, xSpace, xInitialMargin); - numbers.RenderLines(g, maxTextSize.Height, ySpace, yInitialMargin); - - Font numberFont = new Font("Consolas", 7.0f); - Font eolFont = new Font("Consolas", 11.0f); - - var eolBrush = new SolidBrush(Color.Gray); - - var brush = new SolidBrush(Color.Navy); - var pen = new Pen(brush); - - var linePen = new Pen(new SolidBrush(Color.Gray)); - - Size numberTextSize = TextRenderer.MeasureText("Z", numberFont); - - int x = xInitialMargin; - int y = yInitialMargin; - - for (int pos = 0; pos < text.Length; ++pos) - { - string charToDraw = text[pos].ToString(); - - Font currentFont = codeFont; - Brush currentBrush = brush; - - if (text[pos] == '\r') - { - charToDraw = "\\r"; - currentFont = eolFont; - currentBrush = eolBrush; - } - - if (text[pos] == '\n') - { - charToDraw = "\\n"; - currentFont = eolFont; - currentBrush = eolBrush; - } - - g.DrawString(charToDraw, currentFont, currentBrush, new PointF(x, y)); - - g.DrawRectangle(linePen, new Rectangle( - x, y, - xSpace, - ySpace)); - - string posString = pos.ToString(); - - g.DrawString(posString, numberFont, eolBrush, - new PointF( - x + xSpace - TextRenderer.MeasureText(posString, numberFont).Width, - y + ySpace - numberTextSize.Height)); - - x += xSpace; - - if (text[pos] == '\n') - { - y += ySpace; - x = xInitialMargin; - } - } - - return bitmap; - } - - static Size GetTextSizeInLinesAndCols(string text) - { - int lines = 1; - int maxCols = 0; - - int lineWidth = 0; - - for (int i = 0; i < text.Length; ++i) - { - ++lineWidth; - - if (text[i] == '\n') - { - ++lines; - - if (maxCols < lineWidth) - maxCols = lineWidth; - - lineWidth = 0; - } - } - - return new Size(maxCols, lines); - } - - class RenderNumbers - { - internal void RenderColumns( - Graphics g, - int columns, - int xSpace, - int xInitialMargin) - { - int x = xInitialMargin; - int y = 10; - - for (int i = 1; i <= columns; ++i) - { - Size numberTextSize = TextRenderer.MeasureText(i.ToString(), mNumberFont); - - g.DrawString(i.ToString(), mNumberFont, mGrayBrush, - new PointF( - x + xSpace / 2 - (numberTextSize.Width / 2), - y) - ); - - x += xSpace; - } - } - - internal void RenderLines( - Graphics g, - int lines, - int ySpace, - int yInitialMargin) - { - int x = 5; - int y = yInitialMargin; - - for (int i = 1; i <= lines; ++i) - { - Size numberTextSize = TextRenderer.MeasureText(i.ToString(), mNumberFont); - - g.DrawString(i.ToString(), mNumberFont, mGrayBrush, - new PointF( - x, - y + ySpace / 2 - (numberTextSize.Height / 2)) - ); - - y += ySpace; - } - } - - Font mNumberFont = new Font("Consolas", 9.0f); - Brush mGrayBrush = new SolidBrush(Color.Gray); - } - } -} +// g.SmoothingMode = SmoothingMode.AntiAlias; +// g.TextRenderingHint = TextRenderingHint.AntiAlias; + +// RenderNumbers numbers = new RenderNumbers(); +// numbers.RenderColumns(g, maxTextSize.Width, xSpace, xInitialMargin); +// numbers.RenderLines(g, maxTextSize.Height, ySpace, yInitialMargin); + +// Font numberFont = new Font("Consolas", 7.0f); +// Font eolFont = new Font("Consolas", 11.0f); + +// var eolBrush = new SolidBrush(Color.Gray); + +// var brush = new SolidBrush(Color.Navy); +// var pen = new Pen(brush); + +// var linePen = new Pen(new SolidBrush(Color.Gray)); + +// Size numberTextSize = TextRenderer.MeasureText("Z", numberFont); + +// int x = xInitialMargin; +// int y = yInitialMargin; + +// for (int pos = 0; pos < text.Length; ++pos) +// { +// string charToDraw = text[pos].ToString(); + +// Font currentFont = codeFont; +// Brush currentBrush = brush; + +// if (text[pos] == '\r') +// { +// charToDraw = "\\r"; +// currentFont = eolFont; +// currentBrush = eolBrush; +// } + +// if (text[pos] == '\n') +// { +// charToDraw = "\\n"; +// currentFont = eolFont; +// currentBrush = eolBrush; +// } + +// g.DrawString(charToDraw, currentFont, currentBrush, new PointF(x, y)); + +// g.DrawRectangle(linePen, new Rectangle( +// x, y, +// xSpace, +// ySpace)); + +// string posString = pos.ToString(); + +// g.DrawString(posString, numberFont, eolBrush, +// new PointF( +// x + xSpace - TextRenderer.MeasureText(posString, numberFont).Width, +// y + ySpace - numberTextSize.Height)); + +// x += xSpace; + +// if (text[pos] == '\n') +// { +// y += ySpace; +// x = xInitialMargin; +// } +// } + +// return bitmap; +// } + +// static Size GetTextSizeInLinesAndCols(string text) +// { +// int lines = 1; +// int maxCols = 0; + +// int lineWidth = 0; + +// for (int i = 0; i < text.Length; ++i) +// { +// ++lineWidth; + +// if (text[i] == '\n') +// { +// ++lines; + +// if (maxCols < lineWidth) +// maxCols = lineWidth; + +// lineWidth = 0; +// } +// } + +// return new Size(maxCols, lines); +// } + +// class RenderNumbers +// { +// internal void RenderColumns( +// Graphics g, +// int columns, +// int xSpace, +// int xInitialMargin) +// { +// int x = xInitialMargin; +// int y = 10; + +// for (int i = 1; i <= columns; ++i) +// { +// Size numberTextSize = TextRenderer.MeasureText(i.ToString(), mNumberFont); + +// g.DrawString(i.ToString(), mNumberFont, mGrayBrush, +// new PointF( +// x + xSpace / 2 - (numberTextSize.Width / 2), +// y) +// ); + +// x += xSpace; +// } +// } + +// internal void RenderLines( +// Graphics g, +// int lines, +// int ySpace, +// int yInitialMargin) +// { +// int x = 5; +// int y = yInitialMargin; + +// for (int i = 1; i <= lines; ++i) +// { +// Size numberTextSize = TextRenderer.MeasureText(i.ToString(), mNumberFont); + +// g.DrawString(i.ToString(), mNumberFont, mGrayBrush, +// new PointF( +// x, +// y + ySpace / 2 - (numberTextSize.Height / 2)) +// ); + +// y += ySpace; +// } +// } + +// Font mNumberFont = new Font("Consolas", 9.0f); +// Brush mGrayBrush = new SolidBrush(Color.Gray); +// } +// } +//} diff --git a/Services/FileReader.cs b/Services/FileReader.cs new file mode 100644 index 0000000..ed6f581 --- /dev/null +++ b/Services/FileReader.cs @@ -0,0 +1,8 @@ +using System.IO; + +namespace charposition.Services; + +internal class FileReader : IFileReader +{ + public string ReadAllText(string path) => File.ReadAllText(path); +} diff --git a/Services/IFileReader.cs b/Services/IFileReader.cs new file mode 100644 index 0000000..e7f548a --- /dev/null +++ b/Services/IFileReader.cs @@ -0,0 +1,6 @@ +namespace charposition.Services; + +public interface IFileReader +{ + string ReadAllText(string path); +} diff --git a/Services/ILineSplitter.cs b/Services/ILineSplitter.cs new file mode 100644 index 0000000..b37f003 --- /dev/null +++ b/Services/ILineSplitter.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace charposition.Services; + +public interface ILineSplitter +{ + IEnumerable SplitLines(string text); +} diff --git a/Services/LineSplitter.cs b/Services/LineSplitter.cs new file mode 100644 index 0000000..872ddc5 --- /dev/null +++ b/Services/LineSplitter.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace charposition.Services; + +internal class LineSplitter : ILineSplitter +{ + public IEnumerable SplitLines(string text) + { + int index; + while ((index = text.IndexOf('\n')) > -1) + { + yield return text[..(index + 1)].ToCharArray(); + text = text[(index + 1)..]; + } + yield return text.ToCharArray(); + } +} diff --git a/charposition.csproj b/charposition.csproj index 0262238..9c85e37 100644 --- a/charposition.csproj +++ b/charposition.csproj @@ -2,7 +2,18 @@ net7.0-windows WinExe - false - true + enable + true + 1.0.0.0 + CharPosition + CharPosition + CharPosition (c) 2023 + + + + + + + \ No newline at end of file From b5ceb3427e0ddf4cb28c1c1afe679cc0045f76b7 Mon Sep 17 00:00:00 2001 From: Khaos Date: Sun, 14 May 2023 13:28:10 +0200 Subject: [PATCH 03/13] Fixed character memory leak --- Controls/Character.cs | 45 ++++++++--- Controls/CharsView.cs | 37 ++++++--- RenderFile.cs | 182 ------------------------------------------ 3 files changed, 58 insertions(+), 206 deletions(-) delete mode 100644 RenderFile.cs diff --git a/Controls/Character.cs b/Controls/Character.cs index bdc5384..ee99508 100644 --- a/Controls/Character.cs +++ b/Controls/Character.cs @@ -5,31 +5,50 @@ namespace charposition.Controls; public class Character : Canvas { - public Character(int totalChars, char character) + public int Line { get; set; } + public int Column { get; set; } + + public int CharIndex + { + set + { + this.Index.Text = value.ToString(); + } + } + public char CharCode + { + set + { + string display = DisplayCharacter(value); + this.Label.Text = display; + this.Label.Foreground = display != value.ToString() ? Brushes.Gray : Brushes.Black; + } + } + + private TextBlock Label { get; } + private TextBlock Index { get; } + + public Character() { - string display = DisplayCharacter(character); - TextBlock label = new() + this.Label = new() { - Text = display, - Foreground = display != character.ToString() ? Brushes.Gray : Brushes.Black, HorizontalAlignment = System.Windows.HorizontalAlignment.Left, VerticalAlignment = System.Windows.VerticalAlignment.Top, }; - this.Children.Add(label); - Canvas.SetTop(label, 2); - Canvas.SetLeft(label, 8); + this.Children.Add(this.Label); + Canvas.SetTop(this.Label, 2); + Canvas.SetLeft(this.Label, 8); - TextBlock index = new() + this.Index = new() { - Text = totalChars.ToString(), Foreground = Brushes.Gray, HorizontalAlignment = System.Windows.HorizontalAlignment.Right, VerticalAlignment = System.Windows.VerticalAlignment.Bottom, FontSize = 10 }; - this.Children.Add(index); - Canvas.SetBottom(index, 2); - Canvas.SetRight(index, 2); + this.Children.Add(this.Index); + Canvas.SetBottom(this.Index, 2); + Canvas.SetRight(this.Index, 2); } private static string DisplayCharacter(char character) => diff --git a/Controls/CharsView.cs b/Controls/CharsView.cs index 0e48675..50b4ef9 100644 --- a/Controls/CharsView.cs +++ b/Controls/CharsView.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; +using System.Runtime.InteropServices; using System.Windows; using System.Windows.Controls; using System.Windows.Data; @@ -101,26 +102,40 @@ private void UpdateCharacters() { int totalChars = 0; int lineNo = 0; - foreach (var line in this.LineChars) + foreach (char[] chars in this.LineChars) { - if (line is not char[] chars) - { - continue; - } - for (int i = 0; i < chars.Length; i++) { - var character = new Character(totalChars++, chars[i]) + if (this.Characters.Count <= totalChars) { - Width = CellWidth, - Height = CellHeight - }; - this.Canvas.Children.Add(character); + var @char = new Character() + { + Width = CellWidth, + Height = CellHeight, + Background = Brushes.Transparent, + }; + this.Canvas.Children.Add(@char); + this.Characters.Add(@char); + } + + var character = this.Characters[totalChars]; + character.CharIndex = totalChars; + character.CharCode = chars[i]; + character.Line = lineNo; + character.Column = i; Canvas.SetTop(character, CellWidth + (lineNo * CellHeight)); Canvas.SetLeft(character, (i + 1) * CellWidth); + + totalChars++; } lineNo++; } + + while (this.Characters.Count > totalChars) + { + this.Canvas.Children.Remove(this.Characters[^1]); + this.Characters.RemoveAt(this.Characters.Count - 1); + } } private void UpdateColumnLines() diff --git a/RenderFile.cs b/RenderFile.cs deleted file mode 100644 index 0ce02a0..0000000 --- a/RenderFile.cs +++ /dev/null @@ -1,182 +0,0 @@ -//using System; -//using System.Collections.Generic; -//using System.Drawing; -//using System.Drawing.Drawing2D; -//using System.Drawing.Imaging; -//using System.Drawing.Text; -//using System.Linq; -//using System.Text; -//using System.Threading.Tasks; -//using System.Windows.Forms; - -//namespace charposition -//{ -// static class RenderFile -// { -// static internal Bitmap Draw(string text) -// { -// Size maxTextSize = GetTextSizeInLinesAndCols(text); - -// Font codeFont = new Font("Consolas", 14.0f); -// Size codeTextSize = TextRenderer.MeasureText("Z", codeFont); - -// int xSpace = codeTextSize.Width + 2; -// int ySpace = codeTextSize.Height + 10; - -// int xInitialMargin = xSpace; -// int yInitialMargin = ySpace; - -// var bitmap = new Bitmap( -// maxTextSize.Width * xSpace + xInitialMargin +1 , -// maxTextSize.Height * ySpace + yInitialMargin + 1, -// PixelFormat.Format32bppArgb); - -// var g = Graphics.FromImage(bitmap); - -// g.SmoothingMode = SmoothingMode.AntiAlias; -// g.TextRenderingHint = TextRenderingHint.AntiAlias; - -// RenderNumbers numbers = new RenderNumbers(); -// numbers.RenderColumns(g, maxTextSize.Width, xSpace, xInitialMargin); -// numbers.RenderLines(g, maxTextSize.Height, ySpace, yInitialMargin); - -// Font numberFont = new Font("Consolas", 7.0f); -// Font eolFont = new Font("Consolas", 11.0f); - -// var eolBrush = new SolidBrush(Color.Gray); - -// var brush = new SolidBrush(Color.Navy); -// var pen = new Pen(brush); - -// var linePen = new Pen(new SolidBrush(Color.Gray)); - -// Size numberTextSize = TextRenderer.MeasureText("Z", numberFont); - -// int x = xInitialMargin; -// int y = yInitialMargin; - -// for (int pos = 0; pos < text.Length; ++pos) -// { -// string charToDraw = text[pos].ToString(); - -// Font currentFont = codeFont; -// Brush currentBrush = brush; - -// if (text[pos] == '\r') -// { -// charToDraw = "\\r"; -// currentFont = eolFont; -// currentBrush = eolBrush; -// } - -// if (text[pos] == '\n') -// { -// charToDraw = "\\n"; -// currentFont = eolFont; -// currentBrush = eolBrush; -// } - -// g.DrawString(charToDraw, currentFont, currentBrush, new PointF(x, y)); - -// g.DrawRectangle(linePen, new Rectangle( -// x, y, -// xSpace, -// ySpace)); - -// string posString = pos.ToString(); - -// g.DrawString(posString, numberFont, eolBrush, -// new PointF( -// x + xSpace - TextRenderer.MeasureText(posString, numberFont).Width, -// y + ySpace - numberTextSize.Height)); - -// x += xSpace; - -// if (text[pos] == '\n') -// { -// y += ySpace; -// x = xInitialMargin; -// } -// } - -// return bitmap; -// } - -// static Size GetTextSizeInLinesAndCols(string text) -// { -// int lines = 1; -// int maxCols = 0; - -// int lineWidth = 0; - -// for (int i = 0; i < text.Length; ++i) -// { -// ++lineWidth; - -// if (text[i] == '\n') -// { -// ++lines; - -// if (maxCols < lineWidth) -// maxCols = lineWidth; - -// lineWidth = 0; -// } -// } - -// return new Size(maxCols, lines); -// } - -// class RenderNumbers -// { -// internal void RenderColumns( -// Graphics g, -// int columns, -// int xSpace, -// int xInitialMargin) -// { -// int x = xInitialMargin; -// int y = 10; - -// for (int i = 1; i <= columns; ++i) -// { -// Size numberTextSize = TextRenderer.MeasureText(i.ToString(), mNumberFont); - -// g.DrawString(i.ToString(), mNumberFont, mGrayBrush, -// new PointF( -// x + xSpace / 2 - (numberTextSize.Width / 2), -// y) -// ); - -// x += xSpace; -// } -// } - -// internal void RenderLines( -// Graphics g, -// int lines, -// int ySpace, -// int yInitialMargin) -// { -// int x = 5; -// int y = yInitialMargin; - -// for (int i = 1; i <= lines; ++i) -// { -// Size numberTextSize = TextRenderer.MeasureText(i.ToString(), mNumberFont); - -// g.DrawString(i.ToString(), mNumberFont, mGrayBrush, -// new PointF( -// x, -// y + ySpace / 2 - (numberTextSize.Height / 2)) -// ); - -// y += ySpace; -// } -// } - -// Font mNumberFont = new Font("Consolas", 9.0f); -// Brush mGrayBrush = new SolidBrush(Color.Gray); -// } -// } -//} From 5211f48a1f60241a9feb42423bfac061901a0392 Mon Sep 17 00:00:00 2001 From: Khaos Date: Sun, 14 May 2023 15:02:49 +0200 Subject: [PATCH 04/13] Added hover effect --- Controls/CharsView.cs | 62 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/Controls/CharsView.cs b/Controls/CharsView.cs index 50b4ef9..d3cdbcd 100644 --- a/Controls/CharsView.cs +++ b/Controls/CharsView.cs @@ -3,10 +3,11 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; -using System.Runtime.InteropServices; +using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Data; +using System.Windows.Input; using System.Windows.Media; using System.Windows.Shapes; @@ -19,6 +20,7 @@ public class CharsView : UserControl private readonly Brush LineBrush = Brushes.Gray; private readonly Brush LabelBrush = Brushes.Gray; + private readonly Brush HighlightBrush = Brushes.PaleGreen; public int LineCount { @@ -54,6 +56,8 @@ static void OnLineCharsChanged(DependencyObject d, DependencyPropertyChangedEven public CharsView() { + this.Background = Brushes.Transparent; + this.Canvas = new Canvas(); this.Content = this.Canvas; @@ -83,6 +87,58 @@ public CharsView() Y1 = CellWidth }; this.Canvas.Children.Add(this.VerticalLine); + + this.HighlightColumn = new() + { + Width = CellWidth, + Fill = HighlightBrush + }; + this.HighlightLine = new() + { + Height = CellHeight, + Fill = HighlightBrush + }; + + this.HighlightLine.SetBinding(WidthProperty, + new Binding { Path = new PropertyPath(ActualWidthProperty), Source = this }); + this.HighlightColumn.SetBinding(HeightProperty, + new Binding { Path = new PropertyPath(ActualHeightProperty), Source = this }); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + var mouseOverChar = this.Characters.FirstOrDefault(c => c.IsMouseOver); + this.HighlightCharacter(mouseOverChar); + } + + private void HighlightCharacter(Character? character) + { + if (this.HighlightedCharacter != null) + { + this.HighlightedCharacter.Background = Brushes.Transparent; + this.LineNoLabels[this.HighlightedCharacter.Line].Foreground = LabelBrush; + this.ColumnLabels[this.HighlightedCharacter.Column].Foreground = LabelBrush; + } + this.HighlightedCharacter = character; + if (character == null) + { + this.Canvas.Children.Remove(this.HighlightColumn); + this.Canvas.Children.Remove(this.HighlightLine); + return; + } + character.Background = Brushes.Lime; + this.LineNoLabels[character.Line].Foreground = Brushes.Green; + this.ColumnLabels[character.Column].Foreground = Brushes.Green; + Canvas.SetLeft(this.HighlightColumn, (character.Column + 1) * CellWidth); + Canvas.SetTop(this.HighlightLine, CellWidth + (character.Line * CellHeight)); + if (!this.Canvas.Children.Contains(this.HighlightColumn)) + { + this.Canvas.Children.Insert(0, this.HighlightColumn); + } + if (!this.Canvas.Children.Contains(this.HighlightLine)) + { + this.Canvas.Children.Insert(0, this.HighlightLine); + } } private static void Redraw(DependencyObject d, DependencyPropertyChangedEventArgs e) => @@ -289,4 +345,8 @@ private void UpdateBorder() private List ColumnLines { get; } = new(); private List Characters { get; } = new(); + private Character? HighlightedCharacter { get; set; } + + private Rectangle HighlightLine { get; } + private Rectangle HighlightColumn { get; } } From 3841c8270cec2842c92e4fbb7a59265b481f9ea2 Mon Sep 17 00:00:00 2001 From: Khaos Date: Wed, 24 May 2023 21:29:00 +0200 Subject: [PATCH 05/13] Added scrollviewer to CharsView Hover is broken --- App.xaml.cs | 1 - Controls/CharsCanvas.cs | 253 ++++++++++++++++++++++ Controls/CharsView.cs | 288 +++++++++----------------- Converters/SizeAdjustmentConverter.cs | 16 ++ charposition.csproj | 1 - 5 files changed, 364 insertions(+), 195 deletions(-) create mode 100644 Controls/CharsCanvas.cs create mode 100644 Converters/SizeAdjustmentConverter.cs diff --git a/App.xaml.cs b/App.xaml.cs index 1e7d5e7..2639abb 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -1,5 +1,4 @@ using Autofac; -using Autofac.Builder; using charposition.Services; using System.Windows; diff --git a/Controls/CharsCanvas.cs b/Controls/CharsCanvas.cs new file mode 100644 index 0000000..d32d91c --- /dev/null +++ b/Controls/CharsCanvas.cs @@ -0,0 +1,253 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Media; +using System.Windows.Shapes; + +namespace charposition.Controls; + +public class CharsCanvas : UserControl +{ + public int LineCount + { + get => (int)GetValue(LineCountProperty); + set => SetValue(LineCountProperty, value); + } + public static readonly DependencyProperty LineCountProperty = + DependencyProperty.Register("LineCount", typeof(int), typeof(CharsCanvas), new PropertyMetadata(1, Redraw)); + + public int MaxLineLength + { + get => (int)GetValue(MaxLineLengthProperty); + set => SetValue(MaxLineLengthProperty, value); + } + public static readonly DependencyProperty MaxLineLengthProperty = + DependencyProperty.Register("MaxLineLength", typeof(int), typeof(CharsCanvas), new PropertyMetadata(1, Redraw)); + + public IEnumerable LineChars + { + get => (IEnumerable)GetValue(LineCharsProperty); + set => SetValue(LineCharsProperty, value); + } + public static readonly DependencyProperty LineCharsProperty = + DependencyProperty.Register("LineChars", typeof(IEnumerable), typeof(CharsCanvas), new PropertyMetadata(null, OnLineCharsChanged)); + + static void OnLineCharsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (e.NewValue is ObservableCollection coll && d is CharsCanvas ctrl) + { + coll.CollectionChanged += (object? sender, NotifyCollectionChangedEventArgs e) => ctrl.Draw(); + } + } + + public CharsCanvas() + { + this.Background = Brushes.Transparent; + + this.Canvas = new Canvas(); + this.Content = this.Canvas; + + Canvas.SetBinding(WidthProperty, + new Binding { Path = new PropertyPath(ActualWidthProperty), Source = this }); + Canvas.SetBinding(HeightProperty, + new Binding { Path = new PropertyPath(ActualHeightProperty), Source = this }); + + this.SizeChanged += (s, e) => this.Draw(); + + this.HighlightColumn = new() + { + Width = CharsView.CellWidth, + Fill = CharsView.HighlightBrush + }; + this.HighlightLine = new() + { + Height = CharsView.CellHeight, + Fill = CharsView.HighlightBrush + }; + + this.HighlightLine.SetBinding(WidthProperty, + new Binding { Path = new PropertyPath(ActualWidthProperty), Source = this }); + this.HighlightColumn.SetBinding(HeightProperty, + new Binding { Path = new PropertyPath(ActualHeightProperty), Source = this }); + } + + protected override Size MeasureOverride(Size constraint) => + new(this.ColumnLines.Max(l => l.X1), this.RowLines.Max(r => r.Y1)); + + internal void HighlightCharacter(Character? character) + { + if (this.HighlightedCharacter != null) + { + this.HighlightedCharacter.Background = Brushes.Transparent; + } + this.HighlightedCharacter = character; + if (character == null) + { + this.Canvas.Children.Remove(this.HighlightColumn); + this.Canvas.Children.Remove(this.HighlightLine); + return; + } + character.Background = Brushes.Lime; + Canvas.SetLeft(this.HighlightColumn, (character.Column + 1) * CharsView.CellWidth); + Canvas.SetTop(this.HighlightLine, CharsView.CellWidth + (character.Line * CharsView.CellHeight)); + if (!this.Canvas.Children.Contains(this.HighlightColumn)) + { + this.Canvas.Children.Insert(0, this.HighlightColumn); + } + if (!this.Canvas.Children.Contains(this.HighlightLine)) + { + this.Canvas.Children.Insert(0, this.HighlightLine); + } + } + + private static void Redraw(DependencyObject d, DependencyPropertyChangedEventArgs e) => + ((CharsCanvas)d).Draw(); + + private void Draw() + { + this.UpdateRowLines(); + this.UpdateColumnLines(); + this.UpdateCharacters(); + } + + private void UpdateCharacters() + { + int totalChars = 0; + int lineNo = 0; + foreach (char[] chars in this.LineChars) + { + for (int i = 0; i < chars.Length; i++) + { + if (this.Characters.Count <= totalChars) + { + var @char = new Character() + { + Width = CharsView.CellWidth, + Height = CharsView.CellHeight, + Background = Brushes.Transparent, + }; + this.Canvas.Children.Add(@char); + this.Characters.Add(@char); + } + + var character = this.Characters[totalChars]; + character.CharIndex = totalChars; + character.CharCode = chars[i]; + character.Line = lineNo; + character.Column = i; + Canvas.SetTop(character, lineNo * CharsView.CellHeight); + Canvas.SetLeft(character, i * CharsView.CellWidth); + + totalChars++; + } + lineNo++; + } + + while (this.Characters.Count > totalChars) + { + this.Canvas.Children.Remove(this.Characters[^1]); + this.Characters.RemoveAt(this.Characters.Count - 1); + } + } + + private void UpdateColumnLines() + { + int row = 0; + int index = 0; + foreach (char[] lineChars in this.LineChars) + { + for (int i = 1; i <= lineChars.Length; i++) + { + if (this.ColumnLines.Count <= index) + { + var line = new Line + { + Stroke = CharsView.LineBrush, + StrokeThickness = 0.5, + }; + this.Canvas.Children.Add(line); + this.ColumnLines.Add(line); + } + var columnLine = this.ColumnLines[index++]; + + columnLine.X1 = columnLine.X2 = (i * CharsView.CellWidth); + columnLine.Y1 = (row * CharsView.CellHeight); + columnLine.Y2 = ((row + 1) * CharsView.CellHeight); + } + + row++; + } + + while (this.ColumnLines.Count > index) + { + this.Canvas.Children.Remove(this.ColumnLines[^1]); + this.ColumnLines.RemoveAt(this.ColumnLines.Count - 1); + } + } + + private void UpdateRowLines() + { + while (this.RowLines.Count > this.LineCount) + { + this.Canvas.Children.Remove(this.RowLines[^1]); + this.RowLines.RemoveAt(this.RowLines.Count - 1); + } + while (this.RowLines.Count < this.LineCount) + { + var line = new Line + { + Stroke = CharsView.LineBrush, + StrokeThickness = 0.5, + X1 = 0 + }; + this.Canvas.Children.Add(line); + this.RowLines.Add(line); + } + int row = 0; + int lastLineLength = 0; + foreach (char[] lineChars in this.LineChars) + { + if (row == 0) + { + row++; + lastLineLength = lineChars.Length; + continue; + } + int length = Math.Max(lastLineLength, lineChars.Length); + UpdateRowLineLength(row, length); + lastLineLength = lineChars.Length; + row++; + } + UpdateRowLineLength(row, lastLineLength); + } + + private void UpdateRowLineLength(int row, int length) + { + if (row < 1) + { + return; + } + + var line = this.RowLines[row - 1]; + line.Y1 = row * CharsView.CellHeight; + line.Y2 = row * CharsView.CellHeight; + line.X2 = length * CharsView.CellWidth; + } + + private Canvas Canvas { get; } + + private List RowLines { get; } = new(); + private List ColumnLines { get; } = new(); + + private List Characters { get; } = new(); + private Character? HighlightedCharacter { get; set; } + + private Rectangle HighlightLine { get; } + private Rectangle HighlightColumn { get; } +} diff --git a/Controls/CharsView.cs b/Controls/CharsView.cs index d3cdbcd..5a3b0a2 100644 --- a/Controls/CharsView.cs +++ b/Controls/CharsView.cs @@ -1,13 +1,11 @@ -using System; +using charposition.Converters; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; -using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Data; -using System.Windows.Input; using System.Windows.Media; using System.Windows.Shapes; @@ -15,12 +13,12 @@ namespace charposition.Controls; public class CharsView : UserControl { - private const int CellWidth = 24; - private const int CellHeight = 32; + internal const int CellWidth = 24; + internal const int CellHeight = 32; - private readonly Brush LineBrush = Brushes.Gray; - private readonly Brush LabelBrush = Brushes.Gray; - private readonly Brush HighlightBrush = Brushes.PaleGreen; + internal static readonly Brush LineBrush = Brushes.Gray; + internal static readonly Brush LabelBrush = Brushes.Gray; + internal static readonly Brush HighlightBrush = Brushes.PaleGreen; public int LineCount { @@ -88,58 +86,81 @@ public CharsView() }; this.Canvas.Children.Add(this.VerticalLine); - this.HighlightColumn = new() + this.ScrollViewer = new() { - Width = CellWidth, - Fill = HighlightBrush + HorizontalScrollBarVisibility = ScrollBarVisibility.Visible, + VerticalScrollBarVisibility = ScrollBarVisibility.Visible }; - this.HighlightLine = new() - { - Height = CellHeight, - Fill = HighlightBrush - }; - - this.HighlightLine.SetBinding(WidthProperty, - new Binding { Path = new PropertyPath(ActualWidthProperty), Source = this }); - this.HighlightColumn.SetBinding(HeightProperty, - new Binding { Path = new PropertyPath(ActualHeightProperty), Source = this }); - } - - protected override void OnMouseMove(MouseEventArgs e) - { - var mouseOverChar = this.Characters.FirstOrDefault(c => c.IsMouseOver); - this.HighlightCharacter(mouseOverChar); + this.ScrollViewer.ScrollChanged += (s, e) => this.Draw(); + Canvas.SetTop(this.ScrollViewer, CellWidth + 1); + Canvas.SetLeft(this.ScrollViewer, CellWidth + 1); + ScrollViewer.SetBinding(WidthProperty, + new Binding + { + Path = new PropertyPath(ActualWidthProperty), + Source = this, + Converter = new SizeAdjustmentConverter { Adjustment = -1 * CellWidth } + }); + ScrollViewer.SetBinding(HeightProperty, + new Binding + { + Path = new PropertyPath(ActualHeightProperty), + Source = this, + Converter = new SizeAdjustmentConverter { Adjustment = -1 * CellWidth } + }); + this.Canvas.Children.Add(this.ScrollViewer); + + this.CharsCanvas = new(); + this.ScrollViewer.Content = this.CharsCanvas; + + CharsCanvas.SetBinding(CharsCanvas.LineCharsProperty, + new Binding { Path = new PropertyPath(LineCharsProperty), Source = this }); + CharsCanvas.SetBinding(CharsCanvas.MaxLineLengthProperty, + new Binding { Path = new PropertyPath(MaxLineLengthProperty), Source = this }); + CharsCanvas.SetBinding(CharsCanvas.LineCountProperty, + new Binding { Path = new PropertyPath(LineCountProperty), Source = this }); + + //this.HighlightColumn = new() + //{ + // Width = CellWidth, + // Fill = HighlightBrush + //}; + //this.HighlightLine = new() + //{ + // Height = CellHeight, + // Fill = HighlightBrush + //}; + + //this.HighlightLine.SetBinding(WidthProperty, + // new Binding { Path = new PropertyPath(ActualWidthProperty), Source = this }); + //this.HighlightColumn.SetBinding(HeightProperty, + // new Binding { Path = new PropertyPath(ActualHeightProperty), Source = this }); } - private void HighlightCharacter(Character? character) - { - if (this.HighlightedCharacter != null) - { - this.HighlightedCharacter.Background = Brushes.Transparent; - this.LineNoLabels[this.HighlightedCharacter.Line].Foreground = LabelBrush; - this.ColumnLabels[this.HighlightedCharacter.Column].Foreground = LabelBrush; - } - this.HighlightedCharacter = character; - if (character == null) - { - this.Canvas.Children.Remove(this.HighlightColumn); - this.Canvas.Children.Remove(this.HighlightLine); - return; - } - character.Background = Brushes.Lime; - this.LineNoLabels[character.Line].Foreground = Brushes.Green; - this.ColumnLabels[character.Column].Foreground = Brushes.Green; - Canvas.SetLeft(this.HighlightColumn, (character.Column + 1) * CellWidth); - Canvas.SetTop(this.HighlightLine, CellWidth + (character.Line * CellHeight)); - if (!this.Canvas.Children.Contains(this.HighlightColumn)) - { - this.Canvas.Children.Insert(0, this.HighlightColumn); - } - if (!this.Canvas.Children.Contains(this.HighlightLine)) - { - this.Canvas.Children.Insert(0, this.HighlightLine); - } - } + //protected override void OnMouseMove(MouseEventArgs e) + //{ + // var mouseOverChar = this.Characters.Find(c => c.IsMouseOver); + // this.HighlightCharacter(mouseOverChar); + //} + + //private void HighlightCharacter(Character? character) + //{ + // if (this.HighlightedCharacter != null) + // { + // this.HighlightedCharacter.Background = Brushes.Transparent; + // this.LineNoLabels[this.HighlightedCharacter.Line].Foreground = LabelBrush; + // this.ColumnLabels[this.HighlightedCharacter.Column].Foreground = LabelBrush; + // } + // this.HighlightedCharacter = character; + // if (character == null) + // { + // this.Canvas.Children.Remove(this.HighlightColumn); + // this.Canvas.Children.Remove(this.HighlightLine); + // return; + // } + // this.LineNoLabels[character.Line].Foreground = Brushes.Green; + // this.ColumnLabels[character.Column].Foreground = Brushes.Green; + //} private static void Redraw(DependencyObject d, DependencyPropertyChangedEventArgs e) => ((CharsView)d).Draw(); @@ -149,133 +170,6 @@ private void Draw() this.UpdateBorder(); this.UpdateLineNumbers(); this.UpdateColumnNumbers(); - this.UpdateRowLines(); - this.UpdateColumnLines(); - this.UpdateCharacters(); - } - - private void UpdateCharacters() - { - int totalChars = 0; - int lineNo = 0; - foreach (char[] chars in this.LineChars) - { - for (int i = 0; i < chars.Length; i++) - { - if (this.Characters.Count <= totalChars) - { - var @char = new Character() - { - Width = CellWidth, - Height = CellHeight, - Background = Brushes.Transparent, - }; - this.Canvas.Children.Add(@char); - this.Characters.Add(@char); - } - - var character = this.Characters[totalChars]; - character.CharIndex = totalChars; - character.CharCode = chars[i]; - character.Line = lineNo; - character.Column = i; - Canvas.SetTop(character, CellWidth + (lineNo * CellHeight)); - Canvas.SetLeft(character, (i + 1) * CellWidth); - - totalChars++; - } - lineNo++; - } - - while (this.Characters.Count > totalChars) - { - this.Canvas.Children.Remove(this.Characters[^1]); - this.Characters.RemoveAt(this.Characters.Count - 1); - } - } - - private void UpdateColumnLines() - { - int row = 0; - int index = 0; - foreach (char[] lineChars in this.LineChars) - { - for (int i = 1; i <= lineChars.Length; i++) - { - if (this.ColumnLines.Count <= index) - { - var line = new Line - { - Stroke = LineBrush, - StrokeThickness = 0.5, - }; - this.Canvas.Children.Add(line); - this.ColumnLines.Add(line); - } - var columnLine = this.ColumnLines[index++]; - - columnLine.X1 = columnLine.X2 = CellWidth + (i * CellWidth); - columnLine.Y1 = CellWidth + (row * CellHeight); - columnLine.Y2 = CellWidth + ((row + 1) * CellHeight); - } - - row++; - } - - while (this.ColumnLines.Count > index) - { - this.Canvas.Children.Remove(this.ColumnLines[^1]); - this.ColumnLines.RemoveAt(this.ColumnLines.Count - 1); - } - } - - private void UpdateRowLines() - { - while (this.RowLines.Count > this.LineCount) - { - this.Canvas.Children.Remove(this.RowLines[^1]); - this.RowLines.RemoveAt(this.RowLines.Count - 1); - } - while (this.RowLines.Count < this.LineCount) - { - var line = new Line - { - Stroke = LineBrush, - StrokeThickness = 0.5, - X1 = CellWidth - }; - this.Canvas.Children.Add(line); - this.RowLines.Add(line); - } - int row = 0; - int lastLineLength = 0; - foreach (char[] lineChars in this.LineChars) - { - if (row == 0) - { - row++; - lastLineLength = lineChars.Length; - continue; - } - int length = Math.Max(lastLineLength, lineChars.Length); - UpdateRowLineLength(row, length); - lastLineLength = lineChars.Length; - row++; - } - UpdateRowLineLength(row, lastLineLength); - } - - private void UpdateRowLineLength(int row, int length) - { - if (row < 1) - { - return; - } - - var line = this.RowLines[row - 1]; - line.Y1 = CellWidth + (row * CellHeight); - line.Y2 = CellWidth + (row * CellHeight); - line.X2 = CellWidth + (length * CellWidth); } private void UpdateColumnNumbers() @@ -299,7 +193,14 @@ private void UpdateColumnNumbers() this.Canvas.Children.Add(label); this.ColumnLabels.Add(label); Canvas.SetTop(label, 0); - Canvas.SetLeft(label, this.ColumnLabels.Count * CellWidth); + } + + for (int i = 0; i < this.ColumnLabels.Count; i++) + { + var label = this.ColumnLabels[i]; + double left = ((i + 1) * CellWidth) - this.ScrollViewer.HorizontalOffset; + Canvas.SetLeft(label, left); + label.Visibility = left >= CellWidth / 2 ? Visibility.Visible : Visibility.Hidden; } } @@ -323,9 +224,16 @@ private void UpdateLineNumbers() }; this.Canvas.Children.Add(label); this.LineNoLabels.Add(label); - Canvas.SetTop(label, this.LineNoLabels.Count * CellHeight); Canvas.SetLeft(label, 0); } + + for (int i = 0; i < this.LineNoLabels.Count; i++) + { + var label = this.LineNoLabels[i]; + double top = ((i + 1) * CellHeight) - this.ScrollViewer.VerticalOffset; + Canvas.SetTop(label, top); + label.Visibility = top >= CellWidth / 2 ? Visibility.Visible : Visibility.Hidden; + } } private void UpdateBorder() @@ -341,12 +249,6 @@ private void UpdateBorder() private List LineNoLabels { get; } = new(); private List ColumnLabels { get; } = new(); - private List RowLines { get; } = new(); - private List ColumnLines { get; } = new(); - - private List Characters { get; } = new(); - private Character? HighlightedCharacter { get; set; } - - private Rectangle HighlightLine { get; } - private Rectangle HighlightColumn { get; } + private ScrollViewer ScrollViewer { get; } + private CharsCanvas CharsCanvas { get; } } diff --git a/Converters/SizeAdjustmentConverter.cs b/Converters/SizeAdjustmentConverter.cs new file mode 100644 index 0000000..e1f98a2 --- /dev/null +++ b/Converters/SizeAdjustmentConverter.cs @@ -0,0 +1,16 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace charposition.Converters; + +public class SizeAdjustmentConverter : IValueConverter +{ + public double Adjustment { get; set; } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => + value is double size ? size + Adjustment : value; + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => + value is double size ? size - Adjustment : value; +} diff --git a/charposition.csproj b/charposition.csproj index 9c85e37..39c2819 100644 --- a/charposition.csproj +++ b/charposition.csproj @@ -13,7 +13,6 @@ - \ No newline at end of file From 2ed779d9ee8090020142496920c183399ca067d7 Mon Sep 17 00:00:00 2001 From: Khaos Date: Wed, 24 May 2023 21:49:42 +0200 Subject: [PATCH 06/13] Fixed hover effect --- Controls/CharsCanvas.cs | 32 +++++++++++++++- Controls/CharsView.cs | 83 +++++++++++++++++++++-------------------- 2 files changed, 73 insertions(+), 42 deletions(-) diff --git a/Controls/CharsCanvas.cs b/Controls/CharsCanvas.cs index d32d91c..04dad3f 100644 --- a/Controls/CharsCanvas.cs +++ b/Controls/CharsCanvas.cs @@ -7,6 +7,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Data; +using System.Windows.Input; using System.Windows.Media; using System.Windows.Shapes; @@ -14,6 +15,8 @@ namespace charposition.Controls; public class CharsCanvas : UserControl { + public event EventHandler? HoveredCharChanged; + public int LineCount { get => (int)GetValue(LineCountProperty); @@ -80,12 +83,30 @@ public CharsCanvas() protected override Size MeasureOverride(Size constraint) => new(this.ColumnLines.Max(l => l.X1), this.RowLines.Max(r => r.Y1)); + protected override void OnMouseMove(MouseEventArgs e) + { + var mouseOverChar = this.Characters.Find(c => c.IsMouseOver); + this.HighlightCharacter(mouseOverChar); + } + internal void HighlightCharacter(Character? character) { + if (this.HighlightedCharacter == character) + { + return; + } + + this.HoveredCharChanged?.Invoke(this, new CharChangedArgs + { + Column = character?.Column, + Line = character?.Line + }); + if (this.HighlightedCharacter != null) { this.HighlightedCharacter.Background = Brushes.Transparent; } + this.HighlightedCharacter = character; if (character == null) { @@ -93,9 +114,10 @@ internal void HighlightCharacter(Character? character) this.Canvas.Children.Remove(this.HighlightLine); return; } + character.Background = Brushes.Lime; - Canvas.SetLeft(this.HighlightColumn, (character.Column + 1) * CharsView.CellWidth); - Canvas.SetTop(this.HighlightLine, CharsView.CellWidth + (character.Line * CharsView.CellHeight)); + Canvas.SetLeft(this.HighlightColumn, character.Column * CharsView.CellWidth); + Canvas.SetTop(this.HighlightLine, character.Line * CharsView.CellHeight); if (!this.Canvas.Children.Contains(this.HighlightColumn)) { this.Canvas.Children.Insert(0, this.HighlightColumn); @@ -250,4 +272,10 @@ private void UpdateRowLineLength(int row, int length) private Rectangle HighlightLine { get; } private Rectangle HighlightColumn { get; } + + public class CharChangedArgs : EventArgs + { + public int? Line { get; set; } + public int? Column { get; set; } + } } diff --git a/Controls/CharsView.cs b/Controls/CharsView.cs index 5a3b0a2..56e1596 100644 --- a/Controls/CharsView.cs +++ b/Controls/CharsView.cs @@ -18,7 +18,7 @@ public class CharsView : UserControl internal static readonly Brush LineBrush = Brushes.Gray; internal static readonly Brush LabelBrush = Brushes.Gray; - internal static readonly Brush HighlightBrush = Brushes.PaleGreen; + internal static readonly Brush HighlightBrush = new SolidColorBrush(Color.FromRgb(230, 255, 236)); public int LineCount { @@ -111,6 +111,7 @@ public CharsView() this.Canvas.Children.Add(this.ScrollViewer); this.CharsCanvas = new(); + this.CharsCanvas.HoveredCharChanged += (s, e) => HighlightCharacter(e.Line, e.Column); this.ScrollViewer.Content = this.CharsCanvas; CharsCanvas.SetBinding(CharsCanvas.LineCharsProperty, @@ -120,47 +121,46 @@ public CharsView() CharsCanvas.SetBinding(CharsCanvas.LineCountProperty, new Binding { Path = new PropertyPath(LineCountProperty), Source = this }); - //this.HighlightColumn = new() - //{ - // Width = CellWidth, - // Fill = HighlightBrush - //}; - //this.HighlightLine = new() - //{ - // Height = CellHeight, - // Fill = HighlightBrush - //}; - - //this.HighlightLine.SetBinding(WidthProperty, - // new Binding { Path = new PropertyPath(ActualWidthProperty), Source = this }); - //this.HighlightColumn.SetBinding(HeightProperty, - // new Binding { Path = new PropertyPath(ActualHeightProperty), Source = this }); + this.HighlightColumn = new() + { + Width = CellWidth, + Height = CellWidth, + Fill = HighlightBrush, + Visibility = Visibility.Collapsed + }; + this.Canvas.Children.Add(this.HighlightColumn); + this.HighlightLine = new() + { + Width = CellWidth, + Height = CellHeight, + Fill = HighlightBrush, + Visibility = Visibility.Collapsed + }; + this.Canvas.Children.Add(this.HighlightLine); } - //protected override void OnMouseMove(MouseEventArgs e) - //{ - // var mouseOverChar = this.Characters.Find(c => c.IsMouseOver); - // this.HighlightCharacter(mouseOverChar); - //} - - //private void HighlightCharacter(Character? character) - //{ - // if (this.HighlightedCharacter != null) - // { - // this.HighlightedCharacter.Background = Brushes.Transparent; - // this.LineNoLabels[this.HighlightedCharacter.Line].Foreground = LabelBrush; - // this.ColumnLabels[this.HighlightedCharacter.Column].Foreground = LabelBrush; - // } - // this.HighlightedCharacter = character; - // if (character == null) - // { - // this.Canvas.Children.Remove(this.HighlightColumn); - // this.Canvas.Children.Remove(this.HighlightLine); - // return; - // } - // this.LineNoLabels[character.Line].Foreground = Brushes.Green; - // this.ColumnLabels[character.Column].Foreground = Brushes.Green; - //} + private void HighlightCharacter(int? line, int? column) + { + if (line.HasValue) + { + this.HighlightLine.Visibility = Visibility.Visible; + Canvas.SetTop(this.HighlightLine, CellWidth + (line.Value * CellHeight) - this.ScrollViewer.VerticalOffset); + } + else + { + this.HighlightLine.Visibility = Visibility.Collapsed; + } + + if (column.HasValue) + { + this.HighlightColumn.Visibility = Visibility.Visible; + Canvas.SetLeft(this.HighlightColumn, ((column.Value + 1) * CellWidth) - this.ScrollViewer.HorizontalOffset); + } + else + { + this.HighlightColumn.Visibility = Visibility.Collapsed; + } + } private static void Redraw(DependencyObject d, DependencyPropertyChangedEventArgs e) => ((CharsView)d).Draw(); @@ -251,4 +251,7 @@ private void UpdateBorder() private ScrollViewer ScrollViewer { get; } private CharsCanvas CharsCanvas { get; } + + private Rectangle HighlightLine { get; } + private Rectangle HighlightColumn { get; } } From f6ed3a32533bd40cd247af8a026377bfbfa4f436 Mon Sep 17 00:00:00 2001 From: Khaos Date: Fri, 26 May 2023 21:08:12 +0200 Subject: [PATCH 07/13] Display semantic treeview --- App.xaml.cs | 5 +- Converters/NotNullVisibilityConverter.cs | 15 +++ MainWindow.xaml | 162 ++++++++++++++++++++++- MainWindow.xaml.cs | 22 +-- MainWindowModel.cs | 83 +++++++++++- ParserModel/ChildNode.cs | 12 ++ ParserModel/FileNode.cs | 13 ++ ParserModel/LocationSpan.cs | 7 + charposition.csproj | 4 +- 9 files changed, 294 insertions(+), 29 deletions(-) create mode 100644 Converters/NotNullVisibilityConverter.cs create mode 100644 ParserModel/ChildNode.cs create mode 100644 ParserModel/FileNode.cs create mode 100644 ParserModel/LocationSpan.cs diff --git a/App.xaml.cs b/App.xaml.cs index 2639abb..0c9302f 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -20,6 +20,7 @@ private void Application_Startup(object sender, StartupEventArgs e) ContainerBuilder services = new(); services.RegisterType().As(); services.RegisterType().As(); + services.RegisterType().SingleInstance(); services.RegisterType(); var container = services.Build(); @@ -33,9 +34,11 @@ private void Application_Startup(object sender, StartupEventArgs e) semantics = e.Args.Length == 1 ? string.Empty : fileReader.ReadAllText(e.Args[1]); } + var model = container.Resolve(); + model.LoadData(text, semantics); + var window = container.Resolve(); window.Title = title; - window.SetText(text); window.Show(); } } diff --git a/Converters/NotNullVisibilityConverter.cs b/Converters/NotNullVisibilityConverter.cs new file mode 100644 index 0000000..f50201e --- /dev/null +++ b/Converters/NotNullVisibilityConverter.cs @@ -0,0 +1,15 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace charposition.Converters; + +public class NotNullVisibilityConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => + value != null ? Visibility.Visible : Visibility.Collapsed; + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => + throw new NotImplementedException(); +} diff --git a/MainWindow.xaml b/MainWindow.xaml index 9a8cc4f..5045454 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -2,14 +2,170 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="clr-namespace:charposition.Controls" + xmlns:converters="clr-namespace:charposition.Converters" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:parser="clr-namespace:charposition.ParserModel" Title="MainWindow" - Width="800" - Height="450" + Width="1000" + Height="600" UseLayoutRounding="True" mc:Ignorable="d"> + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index 018fb25..3c37c7d 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -9,27 +9,9 @@ namespace charposition; /// public partial class MainWindow : Window { - private readonly MainWindowModel model; - private readonly ILineSplitter lineSplitter; - - public MainWindow(ILineSplitter lineSplitter) + public MainWindow(MainWindowModel model) { - DataContext = model = new MainWindowModel(); + DataContext = model; InitializeComponent(); - this.lineSplitter = lineSplitter; - } - - public void SetText(string text) - { - model.MaximumLineLength = 0; - model.LineCount = 0; - model.LineChars.Clear(); - - foreach (char[] line in lineSplitter.SplitLines(text)) - { - model.LineCount++; - model.MaximumLineLength = Math.Max(model.MaximumLineLength, line.Length); - model.LineChars.Add(line); - } } } diff --git a/MainWindowModel.cs b/MainWindowModel.cs index f54ee71..cadffc0 100644 --- a/MainWindowModel.cs +++ b/MainWindowModel.cs @@ -1,9 +1,15 @@ -using System.Collections.ObjectModel; +using charposition.ParserModel; +using charposition.Services; +using System; +using System.Collections.ObjectModel; +using System.IO; using System.Windows; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; namespace charposition; -internal class MainWindowModel : DependencyObject +public class MainWindowModel : DependencyObject { public int MaximumLineLength { @@ -21,5 +27,78 @@ public int LineCount public static readonly DependencyProperty LineCountProperty = DependencyProperty.Register("LineCount", typeof(int), typeof(MainWindowModel), new PropertyMetadata(0)); + public FileNode? SemanticFile + { + get => (FileNode?)GetValue(SemanticFileProperty); + set => SetValue(SemanticFileProperty, value); + } + public static readonly DependencyProperty SemanticFileProperty = + DependencyProperty.Register("SemanticFile", typeof(FileNode), typeof(MainWindowModel), new PropertyMetadata(null)); + + public string? ErrorMessage + { + get => (string?)GetValue(ErrorMessageProperty); + set => SetValue(ErrorMessageProperty, value); + } + public static readonly DependencyProperty ErrorMessageProperty = + DependencyProperty.Register("ErrorMessage", typeof(string), typeof(MainWindowModel), new PropertyMetadata(null)); + + public ObservableCollection Semantics { get; } = new(); + public ObservableCollection LineChars { get; } = new(); + + private readonly ILineSplitter lineSplitter; + + public MainWindowModel(ILineSplitter lineSplitter) + { + this.lineSplitter = lineSplitter; + } + + public void LoadData(string text, string semantics) + { + this.Clear(); + this.LoadText(text); + this.LoadSemantics(semantics); + } + + private void LoadSemantics(string semantics) + { + if (string.IsNullOrEmpty(semantics)) + { + return; + } + + try + { + var deserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .Build(); + this.SemanticFile = deserializer.Deserialize(new StringReader(semantics)); + this.Semantics.Add(this.SemanticFile); + } + catch (Exception ex) + { + this.ErrorMessage = ex.Message; + } + } + + private void LoadText(string text) + { + foreach (char[] line in lineSplitter.SplitLines(text)) + { + this.LineCount++; + this.MaximumLineLength = Math.Max(this.MaximumLineLength, line.Length); + this.LineChars.Add(line); + } + } + + private void Clear() + { + this.ErrorMessage = null; + this.SemanticFile = null; + this.Semantics.Clear(); + this.MaximumLineLength = 0; + this.LineCount = 0; + this.LineChars.Clear(); + } } diff --git a/ParserModel/ChildNode.cs b/ParserModel/ChildNode.cs new file mode 100644 index 0000000..e8b5e7e --- /dev/null +++ b/ParserModel/ChildNode.cs @@ -0,0 +1,12 @@ +namespace charposition.ParserModel; + +public class ChildNode +{ + public string? Type { get; set; } + public string? Name { get; set; } + public LocationSpan? LocationSpan { get; set; } + public int[]? Span { get; set; } + public int[]? HeaderSpan { get; set; } + public int[]? FooterSpan { get; set; } + public ChildNode[]? Children { get; set; } +} diff --git a/ParserModel/FileNode.cs b/ParserModel/FileNode.cs new file mode 100644 index 0000000..c673219 --- /dev/null +++ b/ParserModel/FileNode.cs @@ -0,0 +1,13 @@ +namespace charposition.ParserModel; + +public class FileNode +{ + public string? Type { get; set; } + public string? Name { get; set; } + public LocationSpan? LocationSpan { get; set; } + public int[]? Span { get; set; } + public int[]? FooterSpan { get; set; } + public bool ParsingErrorsDetected { get; set; } + public int[]? ParsingErrors { get; set; } + public ChildNode[]? Children { get; set; } +} diff --git a/ParserModel/LocationSpan.cs b/ParserModel/LocationSpan.cs new file mode 100644 index 0000000..f1d6f56 --- /dev/null +++ b/ParserModel/LocationSpan.cs @@ -0,0 +1,7 @@ +namespace charposition.ParserModel; + +public class LocationSpan +{ + public int[]? Start { get; set; } + public int[]? End { get; set; } +} diff --git a/charposition.csproj b/charposition.csproj index 39c2819..1c8ac02 100644 --- a/charposition.csproj +++ b/charposition.csproj @@ -11,8 +11,6 @@ - - - + \ No newline at end of file From 32688d282159d27489b447d38114c47a0531df72 Mon Sep 17 00:00:00 2001 From: Khaos Date: Fri, 26 May 2023 21:58:32 +0200 Subject: [PATCH 08/13] Added selection effect --- Controls/CharsCanvas.cs | 37 +++++++++++++++++++++++++++++++++++-- Controls/CharsView.cs | 12 ++++++++++++ MainWindow.xaml | 8 ++++++-- MainWindow.xaml.cs | 19 +++++++++++++++++-- MainWindowModel.cs | 8 ++++++++ 5 files changed, 78 insertions(+), 6 deletions(-) diff --git a/Controls/CharsCanvas.cs b/Controls/CharsCanvas.cs index 04dad3f..2e5e51f 100644 --- a/Controls/CharsCanvas.cs +++ b/Controls/CharsCanvas.cs @@ -1,4 +1,5 @@ -using System; +using charposition.ParserModel; +using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -49,6 +50,14 @@ static void OnLineCharsChanged(DependencyObject d, DependencyPropertyChangedEven } } + public LocationSpan? SelectedSpan + { + get => (LocationSpan?)GetValue(SelectedSpanProperty); + set => SetValue(SelectedSpanProperty, value); + } + public static readonly DependencyProperty SelectedSpanProperty = + DependencyProperty.Register("SelectedSpan", typeof(LocationSpan), typeof(CharsCanvas), new PropertyMetadata(null, Redraw)); + public CharsCanvas() { this.Background = Brushes.Transparent; @@ -104,7 +113,7 @@ internal void HighlightCharacter(Character? character) if (this.HighlightedCharacter != null) { - this.HighlightedCharacter.Background = Brushes.Transparent; + this.UpdateCharacterSelection(this.HighlightedCharacter); } this.HighlightedCharacter = character; @@ -163,6 +172,7 @@ private void UpdateCharacters() character.CharCode = chars[i]; character.Line = lineNo; character.Column = i; + this.UpdateCharacterSelection(character); Canvas.SetTop(character, lineNo * CharsView.CellHeight); Canvas.SetLeft(character, i * CharsView.CellWidth); @@ -178,6 +188,29 @@ private void UpdateCharacters() } } + private void UpdateCharacterSelection(Character character) + { + bool isSelected = false; + if (this.SelectedSpan?.Start != null && this.SelectedSpan?.End != null) + { + int row = character.Line + 1; + if (row == this.SelectedSpan.Start[0]) + { + isSelected = character.Column + 1 >= this.SelectedSpan.Start[1]; + } + else if (row > this.SelectedSpan.Start[0] && character.Line < this.SelectedSpan.End[0]) + { + isSelected = true; + } + else if (row == this.SelectedSpan.End[0]) + { + isSelected = character.Column + 1 <= this.SelectedSpan.End[1]; + } + } + + character.Background = isSelected ? CharsView.SelectionBrush : Brushes.Transparent; + } + private void UpdateColumnLines() { int row = 0; diff --git a/Controls/CharsView.cs b/Controls/CharsView.cs index 56e1596..00c3220 100644 --- a/Controls/CharsView.cs +++ b/Controls/CharsView.cs @@ -1,4 +1,5 @@ using charposition.Converters; +using charposition.ParserModel; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -19,6 +20,7 @@ public class CharsView : UserControl internal static readonly Brush LineBrush = Brushes.Gray; internal static readonly Brush LabelBrush = Brushes.Gray; internal static readonly Brush HighlightBrush = new SolidColorBrush(Color.FromRgb(230, 255, 236)); + internal static readonly Brush SelectionBrush = new SolidColorBrush(Color.FromArgb(100, 192, 232, 250)); public int LineCount { @@ -52,6 +54,14 @@ static void OnLineCharsChanged(DependencyObject d, DependencyPropertyChangedEven } } + public LocationSpan? SelectedSpan + { + get => (LocationSpan?)GetValue(SelectedSpanProperty); + set => SetValue(SelectedSpanProperty, value); + } + public static readonly DependencyProperty SelectedSpanProperty = + DependencyProperty.Register("SelectedSpan", typeof(LocationSpan), typeof(CharsView), new PropertyMetadata(null)); + public CharsView() { this.Background = Brushes.Transparent; @@ -120,6 +130,8 @@ public CharsView() new Binding { Path = new PropertyPath(MaxLineLengthProperty), Source = this }); CharsCanvas.SetBinding(CharsCanvas.LineCountProperty, new Binding { Path = new PropertyPath(LineCountProperty), Source = this }); + CharsCanvas.SetBinding(CharsCanvas.SelectedSpanProperty, + new Binding { Path = new PropertyPath(SelectedSpanProperty), Source = this }); this.HighlightColumn = new() { diff --git a/MainWindow.xaml b/MainWindow.xaml index 5045454..71c5fb3 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -39,7 +39,8 @@ ClipToBounds="True" LineChars="{Binding LineChars}" LineCount="{Binding LineCount}" - MaxLineLength="{Binding MaximumLineLength}" /> + MaxLineLength="{Binding MaximumLineLength}" + SelectedSpan="{Binding SelectedSpan}" /> - + diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index 3c37c7d..3425421 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -1,6 +1,6 @@ -using charposition.Services; -using System; +using charposition.ParserModel; using System.Windows; +using System.Windows.Controls; namespace charposition; @@ -14,4 +14,19 @@ public MainWindow(MainWindowModel model) DataContext = model; InitializeComponent(); } + + private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs e) + { + if (sender is not TreeView tree || this.DataContext is not MainWindowModel model) + { + return; + } + + model.SelectedSpan = tree.SelectedItem switch + { + FileNode file => file.LocationSpan, + ChildNode node => node.LocationSpan, + _ => null, + }; + } } diff --git a/MainWindowModel.cs b/MainWindowModel.cs index cadffc0..7614af0 100644 --- a/MainWindowModel.cs +++ b/MainWindowModel.cs @@ -43,6 +43,14 @@ public string? ErrorMessage public static readonly DependencyProperty ErrorMessageProperty = DependencyProperty.Register("ErrorMessage", typeof(string), typeof(MainWindowModel), new PropertyMetadata(null)); + public LocationSpan? SelectedSpan + { + get => (LocationSpan?)GetValue(SelectedSpanProperty); + set => SetValue(SelectedSpanProperty, value); + } + public static readonly DependencyProperty SelectedSpanProperty = + DependencyProperty.Register("SelectedSpan", typeof(LocationSpan), typeof(MainWindowModel), new PropertyMetadata(null)); + public ObservableCollection Semantics { get; } = new(); public ObservableCollection LineChars { get; } = new(); From 01c3fdcff63f3e45c9dd2231462aeb2f3e5f24a4 Mon Sep 17 00:00:00 2001 From: Khaos Date: Fri, 26 May 2023 22:00:20 +0200 Subject: [PATCH 09/13] New screenshot --- screenshot/charpositionscreenshot.png | Bin 31526 -> 135237 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/screenshot/charpositionscreenshot.png b/screenshot/charpositionscreenshot.png index d50d903cea947904d0ca9e2b66e79f890715e036..89062f14c27fdbed3be24dc49f06da1f18f576d4 100644 GIT binary patch literal 135237 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Ξ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 JHPjp_Eh<^ll8vM&27xP@j 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 U2k&}WI{*Lx07*qoM6N<$f>w4TF#rGn literal 31526 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

BB17vV;JhdVYB`e+UkByDI&-b1ZWCq8 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 From 12bd134ac3f3e75b2a44e34f02645cce7809df4e Mon Sep 17 00:00:00 2001 From: Khaos Date: Thu, 1 Jun 2023 22:44:04 +0200 Subject: [PATCH 10/13] Better handling of FileNotFound --- App.xaml.cs | 26 ++++++++++++++++++++++---- MainWindow.xaml | 13 ++++++++++++- Properties/launchSettings.json | 11 +++++++++++ 3 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 Properties/launchSettings.json diff --git a/App.xaml.cs b/App.xaml.cs index 0c9302f..0bc4dd6 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -1,5 +1,7 @@ using Autofac; using charposition.Services; +using System; +using System.Data; using System.Windows; namespace charposition; @@ -15,6 +17,7 @@ private void Application_Startup(object sender, StartupEventArgs e) string text = DummyData.Text; string title = DummyData.Title; string semantics = DummyData.Semantics; + string error = string.Empty; // Services ContainerBuilder services = new(); @@ -25,18 +28,33 @@ private void Application_Startup(object sender, StartupEventArgs e) var container = services.Build(); + var model = container.Resolve(); + // read args var fileReader = container.Resolve(); if (e.Args.Length > 0) { - text = fileReader.ReadAllText(e.Args[0]); - title = e.Args[0]; - semantics = e.Args.Length == 1 ? string.Empty : fileReader.ReadAllText(e.Args[1]); + text = string.Empty; + semantics = string.Empty; + try + { + title = e.Args[0]; + text = fileReader.ReadAllText(e.Args[0]); + semantics = e.Args.Length == 1 ? string.Empty : fileReader.ReadAllText(e.Args[1]); + } + catch (Exception ex) + { + error = ex.Message; + } } - var model = container.Resolve(); model.LoadData(text, semantics); + if (error != string.Empty) + { + model.ErrorMessage = error; + } + var window = container.Resolve(); window.Title = title; window.Show(); diff --git a/MainWindow.xaml b/MainWindow.xaml index 71c5fb3..51d8a75 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -28,7 +28,18 @@ - + + + + + diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json new file mode 100644 index 0000000..e711530 --- /dev/null +++ b/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "charposition": { + "commandName": "Project" + }, + "Profile 1": { + "commandName": "Project", + "commandLineArgs": "E:\\WEEK70\\PRGS\\aadd.prg C:\\Users\\buchfink\\AppData\\Local\\Temp\\tmpD.yaml" + } + } +} \ No newline at end of file From 963d9153ea3c05c1d89d695c5ee73e6c2dcb2f6c Mon Sep 17 00:00:00 2001 From: Khaos Date: Mon, 5 Jun 2023 10:23:50 +0200 Subject: [PATCH 11/13] Optimized loading of text --- MainWindowModel.cs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/MainWindowModel.cs b/MainWindowModel.cs index 7614af0..59ae18c 100644 --- a/MainWindowModel.cs +++ b/MainWindowModel.cs @@ -53,7 +53,14 @@ public LocationSpan? SelectedSpan public ObservableCollection Semantics { get; } = new(); - public ObservableCollection LineChars { get; } = new(); + public ObservableCollection LineChars + { + get => (ObservableCollection)GetValue(LineCharsProperty); + set => SetValue(LineCharsProperty, value); + } + public static readonly DependencyProperty LineCharsProperty = + DependencyProperty.Register("LineChars", typeof(ObservableCollection), typeof(MainWindowModel), + new PropertyMetadata(new ObservableCollection())); private readonly ILineSplitter lineSplitter; @@ -87,17 +94,28 @@ private void LoadSemantics(string semantics) catch (Exception ex) { this.ErrorMessage = ex.Message; + if (ex.InnerException != null) + { + this.ErrorMessage += $": {ex.InnerException.Message}"; + } } } private void LoadText(string text) { + int lineCount = 0; + int maxLineLen = 0; + System.Collections.Generic.List lines = new(); foreach (char[] line in lineSplitter.SplitLines(text)) { - this.LineCount++; - this.MaximumLineLength = Math.Max(this.MaximumLineLength, line.Length); - this.LineChars.Add(line); + lineCount++; + maxLineLen = Math.Max(maxLineLen, line.Length); + lines.Add(line); } + + this.LineCount = lineCount; + this.MaximumLineLength = maxLineLen; + this.LineChars = new ObservableCollection(lines); } private void Clear() From a0ddda1439c374a30a710b1ed95d4ee15551a92e Mon Sep 17 00:00:00 2001 From: Khaos Date: Mon, 5 Jun 2023 11:23:54 +0200 Subject: [PATCH 12/13] Render only whats visible --- Controls/CharsCanvas.cs | 134 ++++++++++++++++++++++++++-------------- Controls/CharsView.cs | 2 +- 2 files changed, 88 insertions(+), 48 deletions(-) diff --git a/Controls/CharsCanvas.cs b/Controls/CharsCanvas.cs index 2e5e51f..8e1f5e7 100644 --- a/Controls/CharsCanvas.cs +++ b/Controls/CharsCanvas.cs @@ -1,6 +1,5 @@ using charposition.ParserModel; using System; -using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; @@ -34,13 +33,13 @@ public int MaxLineLength public static readonly DependencyProperty MaxLineLengthProperty = DependencyProperty.Register("MaxLineLength", typeof(int), typeof(CharsCanvas), new PropertyMetadata(1, Redraw)); - public IEnumerable LineChars + public IEnumerable LineChars { - get => (IEnumerable)GetValue(LineCharsProperty); + get => (IEnumerable)GetValue(LineCharsProperty); set => SetValue(LineCharsProperty, value); } public static readonly DependencyProperty LineCharsProperty = - DependencyProperty.Register("LineChars", typeof(IEnumerable), typeof(CharsCanvas), new PropertyMetadata(null, OnLineCharsChanged)); + DependencyProperty.Register("LineChars", typeof(IEnumerable), typeof(CharsCanvas), new PropertyMetadata(null, OnLineCharsChanged)); static void OnLineCharsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { @@ -58,7 +57,9 @@ public LocationSpan? SelectedSpan public static readonly DependencyProperty SelectedSpanProperty = DependencyProperty.Register("SelectedSpan", typeof(LocationSpan), typeof(CharsCanvas), new PropertyMetadata(null, Redraw)); - public CharsCanvas() + private readonly ScrollViewer scrollViewer; + + public CharsCanvas(ScrollViewer scrollViewer) { this.Background = Brushes.Transparent; @@ -87,10 +88,13 @@ public CharsCanvas() new Binding { Path = new PropertyPath(ActualWidthProperty), Source = this }); this.HighlightColumn.SetBinding(HeightProperty, new Binding { Path = new PropertyPath(ActualHeightProperty), Source = this }); + + this.scrollViewer = scrollViewer; + this.scrollViewer.ScrollChanged += (s, e) => this.Draw(); } protected override Size MeasureOverride(Size constraint) => - new(this.ColumnLines.Max(l => l.X1), this.RowLines.Max(r => r.Y1)); + new(this.MaxLineLength * CharsView.CellWidth, this.LineCount * CharsView.CellHeight); protected override void OnMouseMove(MouseEventArgs e) { @@ -142,6 +146,14 @@ private static void Redraw(DependencyObject d, DependencyPropertyChangedEventArg private void Draw() { + // Calculate visible columns + this.visibleLines = (int)Math.Ceiling(this.scrollViewer.ViewportHeight / CharsView.CellHeight) + 2; + this.visibleColumns = (int)Math.Ceiling(this.scrollViewer.ViewportWidth / CharsView.CellWidth) + 2; + + // Update columns + this.startRow = (int)Math.Max(0, Math.Floor(this.scrollViewer.VerticalOffset / CharsView.CellHeight) - 1); + this.startCol = (int)Math.Max(0, Math.Floor(this.scrollViewer.HorizontalOffset / CharsView.CellWidth) - 1); + this.UpdateRowLines(); this.UpdateColumnLines(); this.UpdateCharacters(); @@ -149,13 +161,15 @@ private void Draw() private void UpdateCharacters() { - int totalChars = 0; - int lineNo = 0; - foreach (char[] chars in this.LineChars) + int charIndex = 0; + int rowIndex = 0; + int skipedChars = this.LineChars.Take(this.startRow).Select(l => l.Length).Sum(); + foreach (char[] lineChars in this.LineChars.Skip(this.startRow).Take(this.visibleLines)) { - for (int i = 0; i < chars.Length; i++) + var endCol = Math.Min(lineChars.Length, this.startCol + this.visibleColumns); + for (int i = this.startCol; i < endCol; i++) { - if (this.Characters.Count <= totalChars) + if (this.Characters.Count <= charIndex) { var @char = new Character() { @@ -167,24 +181,28 @@ private void UpdateCharacters() this.Characters.Add(@char); } - var character = this.Characters[totalChars]; - character.CharIndex = totalChars; - character.CharCode = chars[i]; - character.Line = lineNo; + var character = this.Characters[charIndex]; + character.CharIndex = charIndex + skipedChars; + character.CharCode = lineChars[i]; + character.Line = rowIndex + this.startRow; character.Column = i; this.UpdateCharacterSelection(character); - Canvas.SetTop(character, lineNo * CharsView.CellHeight); + Canvas.SetTop(character, character.Line * CharsView.CellHeight); Canvas.SetLeft(character, i * CharsView.CellWidth); - totalChars++; + charIndex++; } - lineNo++; + rowIndex++; } - while (this.Characters.Count > totalChars) + // Cleanup unneeded chars + if (this.Characters.Count > charIndex + 100) { - this.Canvas.Children.Remove(this.Characters[^1]); - this.Characters.RemoveAt(this.Characters.Count - 1); + while (this.Characters.Count > charIndex) + { + this.Canvas.Children.Remove(this.Characters[^1]); + this.Characters.RemoveAt(this.Characters.Count - 1); + } } } @@ -213,13 +231,14 @@ private void UpdateCharacterSelection(Character character) private void UpdateColumnLines() { - int row = 0; - int index = 0; - foreach (char[] lineChars in this.LineChars) + int rowIndex = 0; + int colIndex = 0; + foreach (char[] lineChars in this.LineChars.Skip(this.startRow).Take(this.visibleLines)) { - for (int i = 1; i <= lineChars.Length; i++) + var endCol = Math.Min(lineChars.Length, this.startCol + this.visibleColumns); + for (int i = this.startCol; i <= endCol; i++) { - if (this.ColumnLines.Count <= index) + if (this.ColumnLines.Count <= colIndex) { var line = new Line { @@ -229,31 +248,43 @@ private void UpdateColumnLines() this.Canvas.Children.Add(line); this.ColumnLines.Add(line); } - var columnLine = this.ColumnLines[index++]; + var columnLine = this.ColumnLines[colIndex++]; columnLine.X1 = columnLine.X2 = (i * CharsView.CellWidth); + + int row = this.startRow + rowIndex; columnLine.Y1 = (row * CharsView.CellHeight); columnLine.Y2 = ((row + 1) * CharsView.CellHeight); } - row++; + rowIndex++; } - while (this.ColumnLines.Count > index) + // Cleanup unneeded columns + if (this.ColumnLines.Count > colIndex + 100) { - this.Canvas.Children.Remove(this.ColumnLines[^1]); - this.ColumnLines.RemoveAt(this.ColumnLines.Count - 1); + while (this.ColumnLines.Count > colIndex) + { + this.Canvas.Children.Remove(this.ColumnLines[^1]); + this.ColumnLines.RemoveAt(this.ColumnLines.Count - 1); + } } } private void UpdateRowLines() { - while (this.RowLines.Count > this.LineCount) + // Cleanup unneeded lines + if (this.RowLines.Count > this.visibleLines + 25) { - this.Canvas.Children.Remove(this.RowLines[^1]); - this.RowLines.RemoveAt(this.RowLines.Count - 1); + while (this.RowLines.Count > this.visibleLines) + { + this.Canvas.Children.Remove(this.RowLines[^1]); + this.RowLines.RemoveAt(this.RowLines.Count - 1); + } } - while (this.RowLines.Count < this.LineCount) + + // Create new lines + while (this.RowLines.Count < this.visibleLines) { var line = new Line { @@ -264,34 +295,38 @@ private void UpdateRowLines() this.Canvas.Children.Add(line); this.RowLines.Add(line); } - int row = 0; + + // Adjust line lengths int lastLineLength = 0; - foreach (char[] lineChars in this.LineChars) + int rowIndex = 0; + foreach (char[] lineChars in this.LineChars.Skip(this.startRow).Take(this.visibleLines)) { - if (row == 0) + if (lastLineLength == 0) { - row++; + rowIndex++; lastLineLength = lineChars.Length; continue; } + int length = Math.Max(lastLineLength, lineChars.Length); - UpdateRowLineLength(row, length); + UpdateRowLineLength(rowIndex, this.startRow + rowIndex, length); lastLineLength = lineChars.Length; - row++; + rowIndex++; } - UpdateRowLineLength(row, lastLineLength); + + UpdateRowLineLength(rowIndex, this.startRow + rowIndex, lastLineLength); } - private void UpdateRowLineLength(int row, int length) + private void UpdateRowLineLength(int rowIndex, int lineNo, int length) { - if (row < 1) + if (rowIndex < 1 || rowIndex >= this.RowLines.Count) { return; } - var line = this.RowLines[row - 1]; - line.Y1 = row * CharsView.CellHeight; - line.Y2 = row * CharsView.CellHeight; + var line = this.RowLines[rowIndex - 1]; + line.Y1 = lineNo * CharsView.CellHeight; + line.Y2 = lineNo * CharsView.CellHeight; line.X2 = length * CharsView.CellWidth; } @@ -306,6 +341,11 @@ private void UpdateRowLineLength(int row, int length) private Rectangle HighlightLine { get; } private Rectangle HighlightColumn { get; } + private int visibleLines; + private int visibleColumns; + private int startRow; + private int startCol; + public class CharChangedArgs : EventArgs { public int? Line { get; set; } diff --git a/Controls/CharsView.cs b/Controls/CharsView.cs index 00c3220..d522bd1 100644 --- a/Controls/CharsView.cs +++ b/Controls/CharsView.cs @@ -120,7 +120,7 @@ public CharsView() }); this.Canvas.Children.Add(this.ScrollViewer); - this.CharsCanvas = new(); + this.CharsCanvas = new(this.ScrollViewer); this.CharsCanvas.HoveredCharChanged += (s, e) => HighlightCharacter(e.Line, e.Column); this.ScrollViewer.Content = this.CharsCanvas; From 497f5eb6ad922fca8e0e6605dbcd2bef20806952 Mon Sep 17 00:00:00 2001 From: Khaos Date: Mon, 5 Jun 2023 11:37:57 +0200 Subject: [PATCH 13/13] Fixed char index bug --- Controls/CharsCanvas.cs | 37 +++++++++++++++++++------------------ MainWindowModel.cs | 25 +++++++++++++++++-------- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/Controls/CharsCanvas.cs b/Controls/CharsCanvas.cs index 8e1f5e7..e9c7e21 100644 --- a/Controls/CharsCanvas.cs +++ b/Controls/CharsCanvas.cs @@ -33,17 +33,17 @@ public int MaxLineLength public static readonly DependencyProperty MaxLineLengthProperty = DependencyProperty.Register("MaxLineLength", typeof(int), typeof(CharsCanvas), new PropertyMetadata(1, Redraw)); - public IEnumerable LineChars + public IEnumerable> LineChars { - get => (IEnumerable)GetValue(LineCharsProperty); + get => (IEnumerable>)GetValue(LineCharsProperty); set => SetValue(LineCharsProperty, value); } public static readonly DependencyProperty LineCharsProperty = - DependencyProperty.Register("LineChars", typeof(IEnumerable), typeof(CharsCanvas), new PropertyMetadata(null, OnLineCharsChanged)); + DependencyProperty.Register("LineChars", typeof(IEnumerable>), typeof(CharsCanvas), new PropertyMetadata(null, OnLineCharsChanged)); static void OnLineCharsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - if (e.NewValue is ObservableCollection coll && d is CharsCanvas ctrl) + if (e.NewValue is ObservableCollection> coll && d is CharsCanvas ctrl) { coll.CollectionChanged += (object? sender, NotifyCollectionChangedEventArgs e) => ctrl.Draw(); } @@ -163,11 +163,11 @@ private void UpdateCharacters() { int charIndex = 0; int rowIndex = 0; - int skipedChars = this.LineChars.Take(this.startRow).Select(l => l.Length).Sum(); - foreach (char[] lineChars in this.LineChars.Skip(this.startRow).Take(this.visibleLines)) + foreach (var lineChars in this.LineChars.Skip(this.startRow).Take(this.visibleLines)) { - var endCol = Math.Min(lineChars.Length, this.startCol + this.visibleColumns); - for (int i = this.startCol; i < endCol; i++) + var endCol = Math.Min(lineChars.Count, this.startCol + this.visibleColumns); + var col = startCol; + foreach(var c in lineChars.Skip(this.startCol).Take(endCol)) { if (this.Characters.Count <= charIndex) { @@ -182,15 +182,16 @@ private void UpdateCharacters() } var character = this.Characters[charIndex]; - character.CharIndex = charIndex + skipedChars; - character.CharCode = lineChars[i]; + character.CharIndex = c.Key; + character.CharCode = c.Value; character.Line = rowIndex + this.startRow; - character.Column = i; + character.Column = col; this.UpdateCharacterSelection(character); Canvas.SetTop(character, character.Line * CharsView.CellHeight); - Canvas.SetLeft(character, i * CharsView.CellWidth); + Canvas.SetLeft(character, col * CharsView.CellWidth); charIndex++; + col++; } rowIndex++; } @@ -233,9 +234,9 @@ private void UpdateColumnLines() { int rowIndex = 0; int colIndex = 0; - foreach (char[] lineChars in this.LineChars.Skip(this.startRow).Take(this.visibleLines)) + foreach (var lineChars in this.LineChars.Skip(this.startRow).Take(this.visibleLines)) { - var endCol = Math.Min(lineChars.Length, this.startCol + this.visibleColumns); + var endCol = Math.Min(lineChars.Count, this.startCol + this.visibleColumns); for (int i = this.startCol; i <= endCol; i++) { if (this.ColumnLines.Count <= colIndex) @@ -299,18 +300,18 @@ private void UpdateRowLines() // Adjust line lengths int lastLineLength = 0; int rowIndex = 0; - foreach (char[] lineChars in this.LineChars.Skip(this.startRow).Take(this.visibleLines)) + foreach (var lineChars in this.LineChars.Skip(this.startRow).Take(this.visibleLines)) { if (lastLineLength == 0) { rowIndex++; - lastLineLength = lineChars.Length; + lastLineLength = lineChars.Count; continue; } - int length = Math.Max(lastLineLength, lineChars.Length); + int length = Math.Max(lastLineLength, lineChars.Count); UpdateRowLineLength(rowIndex, this.startRow + rowIndex, length); - lastLineLength = lineChars.Length; + lastLineLength = lineChars.Count; rowIndex++; } diff --git a/MainWindowModel.cs b/MainWindowModel.cs index 59ae18c..e9c8962 100644 --- a/MainWindowModel.cs +++ b/MainWindowModel.cs @@ -1,9 +1,11 @@ using charposition.ParserModel; using charposition.Services; using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Windows; +using YamlDotNet.RepresentationModel; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; @@ -53,14 +55,14 @@ public LocationSpan? SelectedSpan public ObservableCollection Semantics { get; } = new(); - public ObservableCollection LineChars + public ObservableCollection> LineChars { - get => (ObservableCollection)GetValue(LineCharsProperty); + get => (ObservableCollection>)GetValue(LineCharsProperty); set => SetValue(LineCharsProperty, value); } public static readonly DependencyProperty LineCharsProperty = - DependencyProperty.Register("LineChars", typeof(ObservableCollection), typeof(MainWindowModel), - new PropertyMetadata(new ObservableCollection())); + DependencyProperty.Register("LineChars", typeof(ObservableCollection>), typeof(MainWindowModel), + new PropertyMetadata(new ObservableCollection>())); private readonly ILineSplitter lineSplitter; @@ -105,17 +107,24 @@ private void LoadText(string text) { int lineCount = 0; int maxLineLen = 0; - System.Collections.Generic.List lines = new(); - foreach (char[] line in lineSplitter.SplitLines(text)) + int charIndex = 0; + List> lines = new(); + foreach (char[] lineChars in lineSplitter.SplitLines(text)) { lineCount++; - maxLineLen = Math.Max(maxLineLen, line.Length); + maxLineLen = Math.Max(maxLineLen, lineChars.Length); + + Dictionary line = new(); + foreach(char c in lineChars) + { + line[charIndex++] = c; + } lines.Add(line); } this.LineCount = lineCount; this.MaximumLineLength = maxLineLen; - this.LineChars = new ObservableCollection(lines); + this.LineChars = new ObservableCollection>(lines); } private void Clear()