diff --git a/projects/GKCore/GDModel/GDMTree.cs b/projects/GKCore/GDModel/GDMTree.cs index fc2334318..c58b82c7d 100644 --- a/projects/GKCore/GDModel/GDMTree.cs +++ b/projects/GKCore/GDModel/GDMTree.cs @@ -302,6 +302,20 @@ public List GetRecords() where T : GDMRecord return result; } + public List GetRecords(GDMRecordType recType) + { + var result = new List(); + + for (int i = 0; i < fRecords.Count; i++) { + var rec = fRecords[i]; + if (recType == GDMRecordType.rtNone || rec.RecordType == recType) { + result.Add(rec); + } + } + + return result; + } + public IGDMTreeEnumerator GetEnumerator(GDMRecordType recType) { return new TreeEnumerator(this, recType); diff --git a/projects/GKCore/GKCore.csproj b/projects/GKCore/GKCore.csproj index b57e05496..66e84ff36 100644 --- a/projects/GKCore/GKCore.csproj +++ b/projects/GKCore/GKCore.csproj @@ -377,6 +377,7 @@ + diff --git a/projects/GKCore/GKCore/HashCode.cs b/projects/GKCore/GKCore/HashCode.cs new file mode 100644 index 000000000..2abd1da82 --- /dev/null +++ b/projects/GKCore/GKCore/HashCode.cs @@ -0,0 +1,219 @@ +/* + The xxHash32 implementation is based on the code published by Yann Collet: + https://raw.githubusercontent.com/Cyan4973/xxHash/5c174cfa4e45a42f94082dc0d4539b39696afea1/xxhash.c + + xxHash - Fast Hash algorithm + Copyright (C) 2012-2016, Yann Collet + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - xxHash homepage: http://www.xxhash.com + - xxHash source repository : https://github.com/Cyan4973/xxHash +*/ + +using System.Runtime.CompilerServices; + +namespace System +{ + public struct HashCode + { + private static readonly uint s_seed = GenerateGlobalSeed(); + + private const uint Prime1 = 2654435761U; + private const uint Prime2 = 2246822519U; + private const uint Prime3 = 3266489917U; + private const uint Prime4 = 668265263U; + private const uint Prime5 = 374761393U; + + private uint _v1, _v2, _v3, _v4; + private uint _queue1, _queue2, _queue3; + private uint _length; + + #region Private and inlined + + private static unsafe uint GenerateGlobalSeed() + { + var rnd = new Random(); + + uint result; + //Interop.GetRandomBytes((byte*)&result, sizeof(uint)); + result = (uint)rnd.Next(); + return result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Initialize(out uint v1, out uint v2, out uint v3, out uint v4) + { + v1 = s_seed + Prime1 + Prime2; + v2 = s_seed + Prime2; + v3 = s_seed; + v4 = s_seed - Prime1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint RotateLeft(uint value, int offset) + { + return (value << offset) | (value >> (32 - offset)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Round(uint hash, uint input) + { + return RotateLeft(hash + input * Prime2, 13) * Prime1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint QueueRound(uint hash, uint queuedValue) + { + return RotateLeft(hash + queuedValue * Prime3, 17) * Prime4; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint MixState(uint v1, uint v2, uint v3, uint v4) + { + return RotateLeft(v1, 1) + RotateLeft(v2, 7) + RotateLeft(v3, 12) + RotateLeft(v4, 18); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint MixEmptyState() + { + return s_seed + Prime5; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint MixFinal(uint hash) + { + hash ^= hash >> 15; + hash *= Prime2; + hash ^= hash >> 13; + hash *= Prime3; + hash ^= hash >> 16; + return hash; + } + + #endregion + + public void AddVal(T value) where T : struct + { + AddHash(value.GetHashCode()); + } + + public void AddObj(T value) where T : class + { + AddHash(value == null ? 0 : value.GetHashCode()); + } + + public void AddHash(int value) + { + // The original xxHash works as follows: + // 0. Initialize immediately. We can't do this in a struct (no + // default ctor). + // 1. Accumulate blocks of length 16 (4 uints) into 4 accumulators. + // 2. Accumulate remaining blocks of length 4 (1 uint) into the + // hash. + // 3. Accumulate remaining blocks of length 1 into the hash. + + // There is no need for #3 as this type only accepts ints. _queue1, + // _queue2 and _queue3 are basically a buffer so that when + // ToHashCode is called we can execute #2 correctly. + + // We need to initialize the xxHash32 state (_v1 to _v4) lazily (see + // #0) nd the last place that can be done if you look at the + // original code is just before the first block of 16 bytes is mixed + // in. The xxHash32 state is never used for streams containing fewer + // than 16 bytes. + + // To see what's really going on here, have a look at the Combine + // methods. + + uint val = (uint)value; + + // Storing the value of _length locally shaves of quite a few bytes + // in the resulting machine code. + uint previousLength = _length++; + uint position = previousLength % 4; + + // Switch can't be inlined. + + if (position == 0) + _queue1 = val; + else if (position == 1) + _queue2 = val; + else if (position == 2) + _queue3 = val; + else // position == 3 + { + if (previousLength == 3) + Initialize(out _v1, out _v2, out _v3, out _v4); + + _v1 = Round(_v1, _queue1); + _v2 = Round(_v2, _queue2); + _v3 = Round(_v3, _queue3); + _v4 = Round(_v4, val); + } + } + + public int ToHashCode() + { + // Storing the value of _length locally shaves of quite a few bytes + // in the resulting machine code. + uint length = _length; + + // position refers to the *next* queue position in this method, so + // position == 1 means that _queue1 is populated; _queue2 would have + // been populated on the next call to Add. + uint position = length % 4; + + // If the length is less than 4, _v1 to _v4 don't contain anything + // yet. xxHash32 treats this differently. + + uint hash = length < 4 ? MixEmptyState() : MixState(_v1, _v2, _v3, _v4); + + // _length is incremented once per Add(Int32) and is therefore 4 + // times too small (xxHash length is in bytes, not ints). + + hash += length * 4; + + // Mix what remains in the queue + + // Switch can't be inlined right now, so use as few branches as + // possible by manually excluding impossible scenarios (position > 1 + // is always false if position is not > 0). + if (position > 0) { + hash = QueueRound(hash, _queue1); + if (position > 1) { + hash = QueueRound(hash, _queue2); + if (position > 2) + hash = QueueRound(hash, _queue3); + } + } + + hash = MixFinal(hash); + return (int)hash; + } + } +} diff --git a/projects/GKCore/GKCore/NetDiff/DiffUtil.cs b/projects/GKCore/GKCore/NetDiff/DiffUtil.cs index 804678743..661d85b66 100644 --- a/projects/GKCore/GKCore/NetDiff/DiffUtil.cs +++ b/projects/GKCore/GKCore/NetDiff/DiffUtil.cs @@ -25,7 +25,7 @@ public class DiffResult { public T Obj1 { get; private set; } public T Obj2 { get; private set; } - public DiffStatus Status { get; private set; } + public DiffStatus Status { get; set; } public DiffResult(T obj1, T obj2, DiffStatus status) { @@ -182,14 +182,14 @@ private static DiffStatus GetStatus(Point current, Point prev) throw new Exception(); } - internal static char GetStatusChar(DiffStatus status) + public static char GetStatusChar(DiffStatus status) { switch (status) { case DiffStatus.Equal: return '='; case DiffStatus.Deleted: return '-'; case DiffStatus.Inserted: return '+'; - case DiffStatus.Modified: return 'M'; + case DiffStatus.Modified: return '≠'; } throw new System.Exception(); @@ -209,7 +209,7 @@ internal struct Point : IEquatable public int X { get; private set; } public int Y { get; private set; } - public Point(int x, int y) + public Point(int x, int y) : this() { X = x; Y = y; diff --git a/projects/GKCore/GKCore/Tools/SyncTool.cs b/projects/GKCore/GKCore/Tools/SyncTool.cs index 14b1f4a09..765e326bd 100644 --- a/projects/GKCore/GKCore/Tools/SyncTool.cs +++ b/projects/GKCore/GKCore/Tools/SyncTool.cs @@ -19,26 +19,24 @@ */ using System; +using System.Collections.Generic; +using System.Linq; using GDModel; using GDModel.Providers.GEDCOM; +using GKCore.NetDiff; namespace GKCore.Tools { - public enum RecordStatus - { - Unknown, // gray - Identical, // white - Changed, // yellow - Deleted, // orange or red - Added, // lightblue or cyan - } - - /// /// /// public class SyncTool { + private GDMTree fMainTree; + private GDMTree fOtherTree; + + public List> Results; + public void LoadOtherFile(GDMTree mainTree, string fileName) { if (mainTree == null) @@ -47,11 +45,52 @@ public void LoadOtherFile(GDMTree mainTree, string fileName) if (string.IsNullOrEmpty(fileName)) throw new ArgumentNullException("fileName"); - using (var extTree = new GDMTree()) { - var gedcomProvider = new GEDCOMProvider(extTree); - gedcomProvider.LoadFromFile(fileName); + fMainTree = mainTree; + + fOtherTree = new GDMTree(); + var gedcomProvider = new GEDCOMProvider(fOtherTree); + gedcomProvider.LoadFromFile(fileName); + } + + public void CompareRecords(GDMRecordType recordType) + { + var records1 = fMainTree.GetRecords(recordType); + var records2 = fOtherTree.GetRecords(recordType); + + var option = new DiffOption(); + option.EqualityComparer = new Stage1Comparer(); + + Results = DiffUtil.Diff(records1, records2, option).ToList(); + CheckModified(); + CheckContents(); + } + + private void CheckModified() + { + foreach (var diffRes in Results) { + if (diffRes.Status != DiffStatus.Equal) continue; + + if ((diffRes.Obj1.XRef != diffRes.Obj2.XRef) || (diffRes.Obj1.ChangeDate.ChangeDateTime != diffRes.Obj2.ChangeDate.ChangeDateTime)) { + diffRes.Status = DiffStatus.Modified; + } + } + } + + private void CheckContents() + { + // IEquatable and GetHashCode() - on all records, structures and tags + } + + internal class Stage1Comparer : IEqualityComparer + { + public bool Equals(GDMRecord x, GDMRecord y) + { + return x.UID == y.UID; + } - // + public int GetHashCode(GDMRecord obj) + { + return obj.GetHashCode(); } } } diff --git a/projects/GKTests/GKCore/ControllerTests.cs b/projects/GKTests/GKCore/ControllerTests.cs index 3a91c4bd7..ea2e35418 100644 --- a/projects/GKTests/GKCore/ControllerTests.cs +++ b/projects/GKTests/GKCore/ControllerTests.cs @@ -821,6 +821,7 @@ public void Test_TreeSplitController() SubstituteControl(view, "btnSelectFamily"); SubstituteControl(view, "btnSelectAncestors"); SubstituteControl(view, "btnSelectDescendants"); + SubstituteControl(view, "btnSelectList"); SubstituteControl(view, "btnDelete"); SubstituteControl(view, "btnSave"); diff --git a/projects/GKv2/GKComponents/GKUI/Components/GKListView.cs b/projects/GKv2/GKComponents/GKUI/Components/GKListView.cs index c3dbac09b..2be317e87 100644 --- a/projects/GKv2/GKComponents/GKUI/Components/GKListView.cs +++ b/projects/GKv2/GKComponents/GKUI/Components/GKListView.cs @@ -681,6 +681,16 @@ public void ClearItems() Items.Clear(); } + public BSDListItem AddItem(object rowData, bool isChecked, Color backColor, params object[] columnValues) + { + var item = AddItem(rowData, columnValues); + if (CheckBoxes) { + ((GKListItem)item).Checked = isChecked; + } + ((GKListItem)item).BackColor = backColor; + return item; + } + public BSDListItem AddItem(object rowData, bool isChecked, params object[] columnValues) { var item = AddItem(rowData, columnValues); diff --git a/projects/plugins/GKTreeSyncPlugin/TSForm.Designer.cs b/projects/plugins/GKTreeSyncPlugin/TSForm.Designer.cs index 20998e283..b863cf5a9 100644 --- a/projects/plugins/GKTreeSyncPlugin/TSForm.Designer.cs +++ b/projects/plugins/GKTreeSyncPlugin/TSForm.Designer.cs @@ -11,11 +11,8 @@ partial class TSForm private System.Windows.Forms.ComboBox cmbRecordTypes; private System.Windows.Forms.RadioButton rbSyncSelected; private System.Windows.Forms.RadioButton rbSyncAll; - private System.Windows.Forms.SplitContainer splitContainer1; - private GKUI.Components.GKListView gkListView1; - private GKUI.Components.GKListView gkListView2; - private System.Windows.Forms.ColumnHeader columnHeader1; - private System.Windows.Forms.ColumnHeader columnHeader2; + private GKUI.Components.GKListView lvRecords; + private System.Windows.Forms.CheckBox chkOnlyModified; protected override void Dispose(bool disposing) { @@ -35,21 +32,15 @@ private void InitializeComponent() this.cmbRecordTypes = new System.Windows.Forms.ComboBox(); this.rbSyncSelected = new System.Windows.Forms.RadioButton(); this.rbSyncAll = new System.Windows.Forms.RadioButton(); - this.splitContainer1 = new System.Windows.Forms.SplitContainer(); - this.gkListView1 = new GKUI.Components.GKListView(); - this.columnHeader1 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.gkListView2 = new GKUI.Components.GKListView(); - this.columnHeader2 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.lvRecords = new GKUI.Components.GKListView(); + this.chkOnlyModified = new System.Windows.Forms.CheckBox(); this.panel1.SuspendLayout(); this.groupBox1.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); - this.splitContainer1.Panel1.SuspendLayout(); - this.splitContainer1.Panel2.SuspendLayout(); - this.splitContainer1.SuspendLayout(); this.SuspendLayout(); // // panel1 // + this.panel1.Controls.Add(this.chkOnlyModified); this.panel1.Controls.Add(this.btnSelectFile); this.panel1.Controls.Add(this.lblFile); this.panel1.Controls.Add(this.txtFile); @@ -123,6 +114,7 @@ private void InitializeComponent() // rbSyncAll // this.rbSyncAll.AutoSize = true; + this.rbSyncAll.Checked = true; this.rbSyncAll.Location = new System.Drawing.Point(9, 19); this.rbSyncAll.Name = "rbSyncAll"; this.rbSyncAll.Size = new System.Drawing.Size(36, 17); @@ -132,90 +124,47 @@ private void InitializeComponent() this.rbSyncAll.UseVisualStyleBackColor = true; this.rbSyncAll.CheckedChanged += new System.EventHandler(this.rbSyncRecords_CheckedChanged); // - // splitContainer1 - // - this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; - this.splitContainer1.Location = new System.Drawing.Point(0, 92); - this.splitContainer1.Name = "splitContainer1"; - // - // splitContainer1.Panel1 - // - this.splitContainer1.Panel1.Controls.Add(this.gkListView1); - // - // splitContainer1.Panel2 - // - this.splitContainer1.Panel2.Controls.Add(this.gkListView2); - this.splitContainer1.Size = new System.Drawing.Size(1047, 536); - this.splitContainer1.SplitterDistance = 349; - this.splitContainer1.TabIndex = 1; - // - // gkListView1 - // - this.gkListView1.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { - this.columnHeader1}); - this.gkListView1.Dock = System.Windows.Forms.DockStyle.Fill; - this.gkListView1.FullRowSelect = true; - this.gkListView1.HideSelection = false; - this.gkListView1.ListMan = null; - this.gkListView1.Location = new System.Drawing.Point(0, 0); - this.gkListView1.Name = "gkListView1"; - this.gkListView1.OwnerDraw = true; - this.gkListView1.SelectedIndex = -1; - this.gkListView1.Size = new System.Drawing.Size(349, 536); - this.gkListView1.SortColumn = 0; - this.gkListView1.SortOrder = GKCore.Design.BSDTypes.SortOrder.None; - this.gkListView1.TabIndex = 0; - this.gkListView1.UseCompatibleStateImageBehavior = false; - this.gkListView1.View = System.Windows.Forms.View.Details; - // - // columnHeader1 - // - this.columnHeader1.Text = "Record"; - this.columnHeader1.Width = 400; - // - // gkListView2 - // - this.gkListView2.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { - this.columnHeader2}); - this.gkListView2.Dock = System.Windows.Forms.DockStyle.Fill; - this.gkListView2.FullRowSelect = true; - this.gkListView2.HideSelection = false; - this.gkListView2.ListMan = null; - this.gkListView2.Location = new System.Drawing.Point(0, 0); - this.gkListView2.Name = "gkListView2"; - this.gkListView2.OwnerDraw = true; - this.gkListView2.SelectedIndex = -1; - this.gkListView2.Size = new System.Drawing.Size(694, 536); - this.gkListView2.SortColumn = 0; - this.gkListView2.SortOrder = GKCore.Design.BSDTypes.SortOrder.None; - this.gkListView2.TabIndex = 0; - this.gkListView2.UseCompatibleStateImageBehavior = false; - this.gkListView2.View = System.Windows.Forms.View.Details; - // - // columnHeader2 - // - this.columnHeader2.Text = "Record"; - this.columnHeader2.Width = 400; + // lvRecords + // + this.lvRecords.Dock = System.Windows.Forms.DockStyle.Fill; + this.lvRecords.FullRowSelect = true; + this.lvRecords.HideSelection = false; + this.lvRecords.ListMan = null; + this.lvRecords.Location = new System.Drawing.Point(0, 92); + this.lvRecords.Name = "lvRecords"; + this.lvRecords.OwnerDraw = true; + this.lvRecords.SelectedIndex = -1; + this.lvRecords.Size = new System.Drawing.Size(1047, 536); + this.lvRecords.SortColumn = 0; + this.lvRecords.SortOrder = GKCore.Design.BSDTypes.SortOrder.None; + this.lvRecords.TabIndex = 0; + this.lvRecords.UseCompatibleStateImageBehavior = false; + this.lvRecords.View = System.Windows.Forms.View.Details; + // + // chkOnlyModified + // + this.chkOnlyModified.AutoSize = true; + this.chkOnlyModified.CheckAlign = System.Drawing.ContentAlignment.MiddleRight; + this.chkOnlyModified.Location = new System.Drawing.Point(381, 55); + this.chkOnlyModified.Name = "chkOnlyModified"; + this.chkOnlyModified.Size = new System.Drawing.Size(89, 17); + this.chkOnlyModified.TabIndex = 4; + this.chkOnlyModified.Text = "Only modified"; + this.chkOnlyModified.UseVisualStyleBackColor = true; // // TSForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(1047, 628); - this.Controls.Add(this.splitContainer1); + this.Controls.Add(this.lvRecords); this.Controls.Add(this.panel1); this.Name = "TSForm"; this.Text = "TSForm"; - this.Load += new System.EventHandler(this.TSForm_Load); - this.Resize += new System.EventHandler(this.TSForm_Resize); this.panel1.ResumeLayout(false); this.panel1.PerformLayout(); this.groupBox1.ResumeLayout(false); this.groupBox1.PerformLayout(); - this.splitContainer1.Panel1.ResumeLayout(false); - this.splitContainer1.Panel2.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); - this.splitContainer1.ResumeLayout(false); this.ResumeLayout(false); } } diff --git a/projects/plugins/GKTreeSyncPlugin/TSForm.cs b/projects/plugins/GKTreeSyncPlugin/TSForm.cs index 3811429d7..65b6992be 100644 --- a/projects/plugins/GKTreeSyncPlugin/TSForm.cs +++ b/projects/plugins/GKTreeSyncPlugin/TSForm.cs @@ -19,10 +19,12 @@ */ using System; +using System.Drawing; using System.Windows.Forms; using GDModel; using GKCore; using GKCore.Interfaces; +using GKCore.NetDiff; using GKCore.Tools; namespace GKTreeSyncPlugin @@ -46,26 +48,16 @@ public TSForm(Plugin plugin, IBaseWindow curBase) : this() fBase = curBase; fSyncTool = new SyncTool(); - } - - public void SetLocale() - { - } - - private void TSForm_Load(object sender, EventArgs e) - { - ResizeSplitter(); - } - private void ResizeSplitter() - { - var diff = splitContainer1.Panel1.Width - splitContainer1.Panel2.Width; - splitContainer1.SplitterDistance -= diff / 2; + lvRecords.CheckBoxes = true; + lvRecords.AddColumn("XRef 1", 100); + lvRecords.AddColumn("XRef 2", 100); + lvRecords.AddColumn("Name 1", 400); + lvRecords.AddColumn("Name 2", 400); } - private void TSForm_Resize(object sender, EventArgs e) + public void SetLocale() { - ResizeSplitter(); } private async void btnSelectFile_ClickAsync(object sender, EventArgs e) @@ -78,11 +70,73 @@ private async void btnSelectFile_ClickAsync(object sender, EventArgs e) txtFile.Text = fileName; fSyncTool.LoadOtherFile(fBase.Context.Tree, fileName); + fSyncTool.CompareRecords(GetRecordType()); + UpdateLists(); } private void rbSyncRecords_CheckedChanged(object sender, EventArgs e) { cmbRecordTypes.Enabled = !rbSyncAll.Checked; } + + private GDMRecordType GetRecordType() + { + if (rbSyncAll.Checked) { + return GDMRecordType.rtNone; + } else { + return (GDMRecordType)(cmbRecordTypes.SelectedIndex + 1); + } + } + + private void UpdateLists() + { + bool onlyModified = chkOnlyModified.Checked; + + lvRecords.BeginUpdate(); + lvRecords.ClearItems(); + + var tree = fBase.Context.Tree; + for (int i = 0; i < fSyncTool.Results.Count; i++) { + var compRes = fSyncTool.Results[i]; + if (onlyModified && compRes.Status == DiffStatus.Equal) continue; + + string item1, item2; + char diffChar = DiffUtil.GetStatusChar(compRes.Status); + Color backColor; + + switch (compRes.Status) { + case DiffStatus.Equal: + default: + item1 = diffChar + " " + compRes.Obj1.XRef; + item2 = diffChar + " " + compRes.Obj2.XRef; + backColor = Color.White; + break; + + case DiffStatus.Deleted: + item1 = diffChar + " " + compRes.Obj1.XRef; + item2 = " "; + backColor = Color.Coral; + break; + + case DiffStatus.Inserted: + item1 = " "; + item2 = diffChar + " " + compRes.Obj2.XRef; + backColor = Color.LightBlue; + break; + + case DiffStatus.Modified: + item1 = diffChar + " " + compRes.Obj1.XRef; + item2 = diffChar + " " + compRes.Obj2.XRef; + backColor = Color.Yellow; + break; + } + + lvRecords.AddItem(compRes, false, backColor, + item1, item2, + GKUtils.GetRecordName(tree, compRes.Obj1, false), GKUtils.GetRecordName(tree, compRes.Obj2, false)); + } + + lvRecords.EndUpdate(); + } } }