From 7716edfa85d8f36ed63f0d4e83a6956013aa0bf4 Mon Sep 17 00:00:00 2001
From: "ankostis@host:STUW025" <ankostis@gmail.com>
Date: Wed, 4 Jun 2014 21:36:32 +0200
Subject: [PATCH] PrefsUI: Add OK, Save, Cancel buttons, store only when Dirty.

* json: Allow null for /Header/StrictBody.
* log: Show full ex in ErrorTab when logLevelThreshold <= 2.
---
 CSE/GUI/F_Preferences.designer.vb |  36 +++++++---
 CSE/GUI/F_Preferences.vb          | 107 ++++++++++++++++++++++--------
 CSE/IO/cJsonFile.vb               |  66 ++++++++++++------
 CSE/IO/cPreferences.vb            |  12 ++--
 CSE/utils.vb                      |  10 ++-
 5 files changed, 166 insertions(+), 65 deletions(-)

diff --git a/CSE/GUI/F_Preferences.designer.vb b/CSE/GUI/F_Preferences.designer.vb
index a6e7f44..0e35fae 100644
--- a/CSE/GUI/F_Preferences.designer.vb
+++ b/CSE/GUI/F_Preferences.designer.vb
@@ -41,13 +41,14 @@ Partial Class F_Preferences
         Me.ButtonSelectNotepad = New System.Windows.Forms.Button()
         Me.editor = New System.Windows.Forms.TextBox()
         Me.GroupBox1 = New System.Windows.Forms.GroupBox()
+        Me.hideUsername = New System.Windows.Forms.CheckBox()
         Me.strictBodies = New System.Windows.Forms.CheckBox()
         Me.includeSchemas = New System.Windows.Forms.CheckBox()
         Me.TextBox1 = New System.Windows.Forms.TextBox()
         Me.Label3 = New System.Windows.Forms.Label()
         Me.CheckBox1 = New System.Windows.Forms.CheckBox()
         Me.ButtonReload = New System.Windows.Forms.Button()
-        Me.hideUsername = New System.Windows.Forms.CheckBox()
+        Me.ButtonSave = New System.Windows.Forms.Button()
         Me.GroupBoxWorDir.SuspendLayout()
         Me.GroupBoxInterface.SuspendLayout()
         Me.TabControl1.SuspendLayout()
@@ -92,14 +93,14 @@ Partial Class F_Preferences
         Me.ButtonOK.Name = "ButtonOK"
         Me.ButtonOK.Size = New System.Drawing.Size(75, 23)
         Me.ButtonOK.TabIndex = 0
-        Me.ButtonOK.Text = "Save"
+        Me.ButtonOK.Text = "OK"
         Me.ButtonOK.UseVisualStyleBackColor = True
         '
         'ButtonCancel
         '
         Me.ButtonCancel.Anchor = CType((System.Windows.Forms.AnchorStyles.Bottom Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles)
         Me.ButtonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel
-        Me.ButtonCancel.Location = New System.Drawing.Point(99, 248)
+        Me.ButtonCancel.Location = New System.Drawing.Point(355, 248)
         Me.ButtonCancel.Name = "ButtonCancel"
         Me.ButtonCancel.Size = New System.Drawing.Size(75, 23)
         Me.ButtonCancel.TabIndex = 1
@@ -241,6 +242,16 @@ Partial Class F_Preferences
         Me.GroupBox1.TabStop = False
         Me.GroupBox1.Text = "JSON"
         '
+        'hideUsername
+        '
+        Me.hideUsername.AutoSize = True
+        Me.hideUsername.Location = New System.Drawing.Point(6, 67)
+        Me.hideUsername.Name = "hideUsername"
+        Me.hideUsername.Size = New System.Drawing.Size(94, 17)
+        Me.hideUsername.TabIndex = 12
+        Me.hideUsername.Text = "hideUsername"
+        Me.hideUsername.UseVisualStyleBackColor = True
+        '
         'strictBodies
         '
         Me.strictBodies.AutoSize = True
@@ -297,15 +308,16 @@ Partial Class F_Preferences
         Me.ButtonReload.Text = "Reload"
         Me.ButtonReload.UseVisualStyleBackColor = True
         '
-        'hideUsername
+        'ButtonSave
         '
-        Me.hideUsername.AutoSize = True
-        Me.hideUsername.Location = New System.Drawing.Point(6, 67)
-        Me.hideUsername.Name = "hideUsername"
-        Me.hideUsername.Size = New System.Drawing.Size(94, 17)
-        Me.hideUsername.TabIndex = 12
-        Me.hideUsername.Text = "hideUsername"
-        Me.hideUsername.UseVisualStyleBackColor = True
+        Me.ButtonSave.Anchor = CType((System.Windows.Forms.AnchorStyles.Bottom Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles)
+        Me.ButtonSave.DialogResult = System.Windows.Forms.DialogResult.OK
+        Me.ButtonSave.Location = New System.Drawing.Point(99, 248)
+        Me.ButtonSave.Name = "ButtonSave"
+        Me.ButtonSave.Size = New System.Drawing.Size(75, 23)
+        Me.ButtonSave.TabIndex = 13
+        Me.ButtonSave.Text = "Save"
+        Me.ButtonSave.UseVisualStyleBackColor = True
         '
         'F_Preferences
         '
@@ -314,6 +326,7 @@ Partial Class F_Preferences
         Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
         Me.CancelButton = Me.ButtonCancel
         Me.ClientSize = New System.Drawing.Size(515, 283)
+        Me.Controls.Add(Me.ButtonSave)
         Me.Controls.Add(Me.TabControl1)
         Me.Controls.Add(Me.ButtonCancel)
         Me.Controls.Add(Me.ButtonReload)
@@ -363,4 +376,5 @@ Partial Class F_Preferences
     Friend WithEvents strictBodies As System.Windows.Forms.CheckBox
     Friend WithEvents includeSchemas As System.Windows.Forms.CheckBox
     Friend WithEvents hideUsername As System.Windows.Forms.CheckBox
+    Friend WithEvents ButtonSave As System.Windows.Forms.Button
 End Class
diff --git a/CSE/GUI/F_Preferences.vb b/CSE/GUI/F_Preferences.vb
index c2a6f71..cdae693 100644
--- a/CSE/GUI/F_Preferences.vb
+++ b/CSE/GUI/F_Preferences.vb
@@ -2,8 +2,7 @@ Imports Newtonsoft.Json.Linq
 
 Public Class F_Preferences
 
-    ' Load confic
-    Private Sub F03_Options_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
+    Private Sub FormLoadHandler(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
         Dim controlPairs As IList(Of Control()) = New List(Of Control())
         ''                CONTROL        LABEL
         controlPairs.Add({Me.workingDir, Me.GroupBoxWorDir})
@@ -15,37 +14,82 @@ Public Class F_Preferences
         controlPairs.Add({Me.strictBodies, Nothing})
         controlPairs.Add({Me.hideUsername, Nothing})
 
-        '' Add help-tooltips from Json-Schema.
+        '' Add help-tooltips from Json-Schema and 
+        '' dirty-check them.
         ''
         Dim schema = JObject.Parse(cPreferences.JSchemaStr)
         For Each row In controlPairs
             Dim ctrl = row(0)
             Dim Label = row(1)
             updateControlsFromSchema(schema, ctrl, Label)
+            If TypeOf ctrl Is CheckBox Then
+                AddHandler DirectCast(ctrl, CheckBox).CheckedChanged, AddressOf DirtyHandler
+            Else
+                AddHandler ctrl.TextChanged, AddressOf DirtyHandler
+            End If
         Next
 
         UI_PopulateFrom(Prefs)
     End Sub
 
+    Private Sub FormClosingHandler(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
+        If Me.Dirty Then
+            Dim res = MsgBox("Save changes?", MsgBoxStyle.YesNoCancel, "Preferences Changed")
+            Select Case res
+                Case MsgBoxResult.No
+                Case MsgBoxResult.Yes
+                    Try
+                        StorePrefs()
+                    Catch ex As Exception
+                        e.Cancel = True
+                        logme(9, True, format("Failed storing Preferences({0}) due to: {1} \n  Preferences left unmodified!", _
+                                                    PrefsPath, ex.Message), ex)
+                    End Try
+                Case Else
+                    e.Cancel = True
+            End Select
+        End If
+    End Sub
+
+    Private _Dirty
+    Private Property Dirty As Boolean
+        Get
+            Return _Dirty
+        End Get
+        Set(ByVal value As Boolean)
+            If _Dirty Xor value Then
+                Me.Text = "Preferences" & IIf(value, "*", "")
+            End If
+            _Dirty = value
+        End Set
+    End Property
+
+
+    Private Sub DirtyHandler(ByVal sender As Object, ByVal e As System.EventArgs)
+        Dirty = True
+    End Sub
+
     Private Sub UI_PopulateFrom(ByVal value As cPreferences)
         ' Allocate the data from the confic file (Only by the start)
         Me.workingDir.Text = value.workingDir
-        Me.editor.Text = value.Editor
-        Me.writeLog.Checked = value.WriteLog
-        Me.logLevel.Text = value.LogLevel
-        Me.logSize.Text = value.LogSize
-        Me.includeSchemas.Checked = value.IncludeSchemas
+        Me.editor.Text = value.editor
+        Me.writeLog.Checked = value.writeLog
+        Me.logLevel.Text = value.logLevel
+        Me.logSize.Text = value.logSize
+        Me.includeSchemas.Checked = value.includeSchemas
         Me.strictBodies.Checked = value.strictBodies
         Me.hideUsername.Checked = value.hideUsername
+
+        Me.Dirty = False
     End Sub
 
     Private Sub UI_PopulateTo(ByVal value As cPreferences)
         value.workingDir = Me.workingDir.Text
-        value.Editor = Me.editor.Text
-        value.WriteLog = Me.writeLog.Checked
-        value.LogLevel = Me.logLevel.Text
-        value.LogSize = Me.logSize.Text
-        value.IncludeSchemas = Me.includeSchemas.Checked
+        value.editor = Me.editor.Text
+        value.writeLog = Me.writeLog.Checked
+        value.logLevel = Me.logLevel.Text
+        value.logSize = Me.logSize.Text
+        value.includeSchemas = Me.includeSchemas.Checked
         value.strictBodies = Me.strictBodies.Checked
         value.hideUsername = Me.hideUsername.Checked
     End Sub
@@ -57,28 +101,34 @@ Public Class F_Preferences
         End If
     End Sub
 
-    ' Ok button
-    Private Sub StorePrefs(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButtonOK.Click
+    Private Sub SaveHandler(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButtonOK.Click, ButtonSave.Click
         Try
-            Dim newPrefs As cPreferences = Prefs.Clone()
-            UI_PopulateTo(newPrefs)
-
-            ' Write the config file
-            newPrefs.Store(PrefsPath, newPrefs)
-            Prefs = newPrefs           ' Replace active prefs if successful.
-
-            ' Message for the restart of VECTO
-            RestartN = True
-            logme(7, True, format("Stored Preferences({0}). \n\nDo you want to restart VECTO now?", PrefsPath))
-
-            ' Close the window
-            Me.Close()
+            '' OK-btn save when dirty, always closes-form.
+            '' Save-btn: always saves, burt not closes-form.
+            ''
+            If sender IsNot ButtonOK OrElse Me.Dirty Then
+                StorePrefs()
+            End If
+
+            If sender Is ButtonOK Then Me.Close()
         Catch ex As Exception
             logme(9, True, format("Failed storing Preferences({0}) due to: {1} \n  Preferences left unmodified!", _
                                         PrefsPath, ex.Message), ex)
         End Try
 
     End Sub
+    Private Sub StorePrefs()
+        Dim newPrefs As cPreferences = Prefs.Clone()
+        UI_PopulateTo(newPrefs)
+
+        ' Write the config file
+        newPrefs.Store(PrefsPath, newPrefs)
+        Prefs = newPrefs           ' Replace active prefs if successful.
+        Me.Dirty = False
+
+        ' Message for the restart of VECTO
+        logme(7, False, format("Stored Preferences({0}).", PrefsPath))
+    End Sub
 
     ' Ok button
     Private Sub ReloadPrefs(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButtonReload.Click
@@ -150,5 +200,6 @@ Public Class F_Preferences
     Private Sub TextBoxLogSize_Leave(ByVal sender As Object, ByVal e As System.EventArgs) Handles logSize.Leave, TextBox1.Leave
         If Me.logSize.Text = Nothing Then Me.logSize.Text = Prefs.PropDefault("logSize")
     End Sub
+
 End Class
 
diff --git a/CSE/IO/cJsonFile.vb b/CSE/IO/cJsonFile.vb
index 1c70ee8..cc69b79 100644
--- a/CSE/IO/cJsonFile.vb
+++ b/CSE/IO/cJsonFile.vb
@@ -10,8 +10,17 @@ Imports System.Globalization
 ''' and delegates the Body-building and its validation almost entirely to sub-classes.
 ''' </summary>
 ''' <remarks>
-''' The /Header/Strict boolean controls whether to allow additional-properties in Body, 
-''' so it can be used to debug input-files by manually setting it to 'true' with a text-editor.
+''' JSON files do not support comments, but help is provided by their accompanying json-schemas (see below).  
+''' They are splitted in two sections, Header and Body. The Header contains administrational fields or fields 
+''' related to the actual parsing of the file.
+''' 
+''' Of interest are the following 2 of Header’s properties:
+''' •	/Header/StrictBody: Controls whether the application will accept any unknown body-properties 
+'''     while reading the file. Set it to true to debug malformed input-files, ie to detect accidentally 
+'''     renamed properties.
+''' •	/Header/BodySchema: The JSON-schema of the body will be placed HERE, for documenting file.  
+'''     When true, it is always replaced by the Body's schema on the next save. When false, it overrides 
+''' application's choice and is not replaced ever.
 ''' </remarks>
 Public MustInherit Class cJsonFile
     Implements ICloneable
@@ -28,7 +37,7 @@ Public MustInherit Class cJsonFile
                 "AppVersion":   null,
                 "ModifiedDate": null,
                 "CreatedBy": null,
-                "StrictBody":   false,
+                "StrictBody":   null,
                 "BodySchema":   null,
             },
             "Body": null
@@ -70,17 +79,19 @@ Public MustInherit Class cJsonFile
                         },
                         "StrictBody": {
                             "title": "Validate body strictly",
-                            "type": "boolean",
-                            "description": "When True, the 'Body' does not accept unknown properties.",
-                            "default": false,
+                            "type": ["boolean", "null"],
+                            "description": "If set to true, the application will not accept any unknown body-properties while reading this file.
+When null/property missing, the application decides what to do.
+It is useful for debugging malformed input-files, ie to detect accidentally renamed properties.",
+                            "default": null,
                         },
                         "BodySchema": {
                             "title": "Body schema",
                             "type": ["boolean", "object", "null"],
-                            "description": "Body schema is included HERE, for documenting file. \n _
-                              When null/property missing, application decides what to do. \n _
-                              When True, it is always replaced by the Body's schema on the next save.\n _
-                              When False, it overrides Application's choice and is not replaced ever.", 
+                            "description": "Body schema is included HERE, for documenting file. 
+When null/property missing, the application decides what to do. 
+When True, it is always replaced by the Body's schema on the next save.
+When False, it overrides Application's choice and is not replaced ever.", 
                             "default": null,
                         },
                     }
@@ -177,8 +188,11 @@ Public MustInherit Class cJsonFile
         h("CreatedBy") = format("{0}{1}(lic: {2})", username, Lic.LicString, Lic.GUID)
 
         h("AppVersion") = AppVers
+
+        '' Ensure StrictBody element always there.
+        
         If h("StrictBody") Is Nothing Then
-            h("StrictBody") = False
+            h("StrictBody") = Nothing
         End If
 
         '' Decide whether to include body-schema in header (for documenting file),
@@ -210,8 +224,8 @@ Public MustInherit Class cJsonFile
     End Sub
 
     ''' <exception cref="FormatException">includes all validation errors</exception>
-    ''' <param name="prefs">It is there to be used when storing cPreferences themselfs.</param>
     ''' <param name="strictHeader">when false, relaxes Header's schema (used on Loading to be more accepting)</param>
+    ''' <param name="prefs">It is there just to be used when storing cPreferences themselfs.</param>
     Friend Sub Validate(Optional ByVal strictHeader As Boolean = False, Optional ByVal prefs As cPreferences = Nothing)
         If prefs Is Nothing Then prefs = CSE.Prefs
         Dim validateMsgs As IList(Of String) = New List(Of String)
@@ -227,9 +241,7 @@ Public MustInherit Class cJsonFile
         Dim dummy = New cSemanticVersion(Me.FileVersion) '' Just to ensure its syntax.
 
         '' Validate Body by subclass
-        Dim hsb = Me.Header("StrictBody")
-        Dim strictBody As Boolean = IIf(hsb Is Nothing, Prefs.strictBodies, hsb)
-        Me.ValidateBody(strictBody, validateMsgs)
+        Me.ValidateBody(Me.StrictBody, validateMsgs)
 
         If (validateMsgs.Any()) Then
             Throw New FormatException(format("Validating /Body failed due to: {0}", String.Join(vbCrLf, validateMsgs)))
@@ -249,7 +261,17 @@ Public MustInherit Class cJsonFile
         If obj Is Nothing OrElse Not Me.GetType().Equals(obj.GetType()) Then
             Return False
         Else
-            Return JToken.DeepEquals(Me.Content, DirectCast(obj, cJsonFile).Content)
+            Dim oj As cJsonFile = DirectCast(obj, cJsonFile)
+            If Not JToken.DeepEquals(Me.Body, oj.Body) Then Return False
+
+            '' Compare Headers without the 'ModifiedDate' field 
+            '' which would undoublfully different each time.
+            Dim mh As New JObject(Me.Header)
+            Dim oh As New JObject(oj.Header)
+
+            mh.Remove("ModifiedDate")
+            oh.Remove("ModifiedDate")
+            Return JToken.DeepEquals(mh, oh)
         End If
     End Function
 
@@ -331,12 +353,18 @@ Public MustInherit Class cJsonFile
 
     Public ReadOnly Property StrictBody As Boolean
         Get
-            Dim value = Me.Body("StrictBody")
-            Return IIf(value Is Nothing OrElse value.Type = JTokenType.Null, False, value)
+            Dim value = False
+            Dim jt = Me.Header("StrictBody")
+            If jt IsNot Nothing AndAlso jt.Type <> JTokenType.Null Then
+                value = jt
+            ElseIf Prefs IsNot Nothing Then
+                value = Prefs.strictBodies
+            End If
+            Return value
         End Get
     End Property
 
-    '' NO, logic behind it more complex, see UpdateHeader() instead.
+    '' NO, logic behind it too complex, see UpdateHeader() instead.
     'Public ReadOnly Property BodySchema As Boolean
     '    Get
     '        Dim value = Me.Body("BodySchema")
diff --git a/CSE/IO/cPreferences.vb b/CSE/IO/cPreferences.vb
index 22e37c4..3b5b24a 100644
--- a/CSE/IO/cPreferences.vb
+++ b/CSE/IO/cPreferences.vb
@@ -60,7 +60,7 @@ Public Class cPreferences
                     "minimum": 0,
                     "maximum": 10, "exclusiveMaximum": true,
                     "default": 2,
-                    "description": "Sets the threshold(Level) above (inclusive) from which log-messages are shown in the log-window.
+                    "description": "Sets the threshold(Level) above (inclusive) from which log-messages are shown in the log-window. 
     0     : All
     3-7   : No infos
     8     : No warnings
@@ -76,17 +76,17 @@ Public Class cPreferences
                     "title": "Strict Bodies",
                     "type": "boolean",
                     "default": false,
-                    "description": "When set to true, any unknown body-properties are not accepted when reading JSON-files. 
-It is useful for debugging malformed input-files, ie to detect 
-accidentally renamed properties.
+                    "description": "If set to true, the application will not accept any unknown body-properties when reading JSON-files. 
+It is useful for debugging malformed input-files, ie to detect accidentally renamed properties.
 Each file can override it by setting its `/Header/StrictBody` property.",
                 }, 
                 "includeSchemas": {
                     "title": "Include Schemas",
                     "type": "boolean",
                     "default": false,
-                    "description": "When set to true the JSON-files are self-documented by populating their `/Header/BodySchema` property.
-Each file can override it by setting its `/Header/BodySchema` property to false/true.",
+                    "description": "When set to true the JSON-files are self-documented by 
+populating their `/Header/BodySchema` property.  Each file can override it by 
+setting its `/Header/BodySchema` property to false/true.",
                 }, 
                 "hideUsername": {
                     "title": "Hide Username",
diff --git a/CSE/utils.vb b/CSE/utils.vb
index 788473f..5c37272 100644
--- a/CSE/utils.vb
+++ b/CSE/utils.vb
@@ -260,9 +260,11 @@ Module utils
 
     Private Sub updateLogWindow(ByVal logFileLevel As Integer, ByVal text As String, ByVal tabLabel As String, ByVal ex As Exception)
         ' Established the text wit the symbol from the style
+        Dim printEx = False
 
         If (ex IsNot Nothing) Then
-            text = text & " (Check log-file for details)"
+            printEx = (logFileLevel > 1 AndAlso Prefs.logLevel <= 2)
+            text = format("{0} (Check {1} for details)", text, IIf(printEx, "error/warn tab", "log-file"))
         End If
 
         ' Write to Log-windows
@@ -271,8 +273,14 @@ Module utils
                 F_Main.ListBoxMSG.Items.Add(text)
                 F_Main.ListBoxWar.Items.Add(text)
                 F_Main.TabPageWar.Text = format("Warnings({0})", F_Main.ListBoxWar.Items.Count)
+                If printEx Then
+                    F_Main.ListBoxWar.Items.Add(format("\i{0}", ex))
+                End If
             Case 3 ' Error
                 F_Main.ListBoxErr.Items.Add(text)
+                If printEx Then
+                    F_Main.ListBoxErr.Items.Add(format("\i{0}", ex))
+                End If
                 F_Main.TabPageErr.Text = format("Errors({0})", F_Main.ListBoxErr.Items.Count)
             Case Else
                 '' ignored
-- 
GitLab